framer-motion
Version:
An open source and production-ready motion library for React on the web
148 lines (127 loc) • 4.79 kB
text/typescript
import { invariant } from "hey-listen"
import { complex } from "style-value-types"
import {
Target,
TargetAndTransition,
TargetResolver,
TargetWithKeyframes,
Transition,
} from "../../types"
import { isNumericalString } from "../../utils/is-numerical-string"
import { resolveFinalValueInKeyframes } from "../../utils/resolve-value"
import { motionValue } from "../../value"
import { findValueType, getAnimatableNone } from "../dom/utils/value-types"
import { ResolvedValues, VisualElement } from "../types"
import { AnimationDefinition } from "./animation"
import { resolveVariant } from "./variants"
/**
* Set VisualElement's MotionValue, creating a new MotionValue for it if
* it doesn't exist.
*/
function setMotionValue(
visualElement: VisualElement,
key: string,
value: string | number
) {
if (visualElement.hasValue(key)) {
visualElement.getValue(key)!.set(value)
} else {
visualElement.addValue(key, motionValue(value))
}
}
export function setTarget(
visualElement: VisualElement,
definition: string | TargetAndTransition | TargetResolver
) {
const resolved = resolveVariant(visualElement, definition)
let { transitionEnd = {}, transition = {}, ...target } = resolved
? visualElement.makeTargetAnimatable(resolved, false)
: {}
target = { ...target, ...transitionEnd }
for (const key in target) {
const value = resolveFinalValueInKeyframes(target[key])
setMotionValue(visualElement, key, value as string | number)
}
}
function setVariants(visualElement: VisualElement, variantLabels: string[]) {
const reversedLabels = [...variantLabels].reverse()
reversedLabels.forEach((key) => {
const variant = visualElement.getVariant(key)
variant && setTarget(visualElement, variant)
visualElement.variantChildren?.forEach((child) => {
setVariants(child, variantLabels)
})
})
}
export function setValues(
visualElement: VisualElement,
definition: AnimationDefinition
) {
if (Array.isArray(definition)) {
return setVariants(visualElement, definition)
} else if (typeof definition === "string") {
return setVariants(visualElement, [definition])
} else {
setTarget(visualElement, definition as any)
}
}
export function checkTargetForNewValues(
visualElement: VisualElement,
target: TargetWithKeyframes,
origin: ResolvedValues
) {
const newValueKeys = Object.keys(target).filter(
(key) => !visualElement.hasValue(key)
)
const numNewValues = newValueKeys.length
if (!numNewValues) return
for (let i = 0; i < numNewValues; i++) {
const key = newValueKeys[i]
const targetValue = target[key]
let value: string | number | null = null
// If this is a keyframes value, we can attempt to use the first value in the
// array as that's going to be the first value of the animation anyway
if (Array.isArray(targetValue)) {
value = targetValue[0]
}
// If it isn't a keyframes or the first keyframes value was set as `null`, read the
// value from the DOM. It might be worth investigating whether to check props (for SVG)
// or props.style (for HTML) if the value exists there before attempting to read.
if (value === null) {
const readValue = origin[key] ?? visualElement.readValue(key)
value = readValue !== undefined ? readValue : target[key]
invariant(
value !== null,
`No initial value for "${key}" can be inferred. Ensure an initial value for "${key}" is defined on the component.`
)
}
if (typeof value === "string" && isNumericalString(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) && complex.test(targetValue)) {
value = getAnimatableNone(key, targetValue)
}
visualElement.addValue(key, motionValue(value))
;(origin as any)[key] ??= value
visualElement.setBaseTarget(key, value)
}
}
export function getOriginFromTransition(key: string, transition: Transition) {
if (!transition) return
const valueTransition =
transition[key] || transition["default"] || transition
return valueTransition.from
}
export function getOrigin(
target: Target,
transition: Transition,
visualElement: VisualElement
) {
const origin: Target = {}
for (const key in target) {
origin[key] =
getOriginFromTransition(key, transition) ??
visualElement.getValue(key)?.get()
}
return origin
}