framer-motion
Version:
An open source and production-ready motion library for React on the web
163 lines (138 loc) • 4.95 kB
text/typescript
import { isAnimationControls } from "../../animation/animation-controls"
import { MotionProps } from "../../motion"
import { isForcedMotionValue } from "../../motion/utils/is-forced-motion-value"
import { AxisBox2D, BoxDelta, Point2D } from "../../types/geometry"
import { axisBox, delta } from "../../utils/geometry"
import { isMotionValue } from "../../value/utils/is-motion-value"
import { ResolvedValues, VisualElement } from "../types"
import { checkIfControllingVariants, resolveVariantFromProps } from "./variants"
/**
* Represents the size and position we want to project a given visual
* element into.
*/
export interface TargetProjection {
/**
* Whether we should attempt to project into this target box.
*/
isEnabled: boolean
/**
* Whether this target box is locked. We might want to lock the box, for
* instance if the user is dragging or animating it. Otherwise
* we want to rebase the target box ontop of the measured layout.
*/
isTargetLocked: boolean
/**
* The viewport-relative box we want to project the element into.
*/
target: AxisBox2D
/**
* The viewport-relative box we want to project the element into after
* it's had x/y/scale transforms applied.
*/
targetFinal: AxisBox2D
}
export const createProjectionState = () => ({
isEnabled: false,
isTargetLocked: false,
target: axisBox(),
targetFinal: axisBox(),
})
export function createVisualState(
props: MotionProps,
parent?: VisualElement,
blockInitialAnimation?: boolean
) {
const { style } = props
const values: ResolvedValues = {}
for (const key in style) {
if (isMotionValue(style[key])) {
values[key] = style[key].get()
} else if (isForcedMotionValue(key, props)) {
values[key] = style[key]
}
}
let { initial, animate } = props
const isControllingVariants = checkIfControllingVariants(props)
const isVariantNode = isControllingVariants || props.variants
if (isVariantNode && !isControllingVariants && props.inherit !== false) {
const context = parent?.getVariantContext()
if (context) {
initial ??= context.initial
animate ??= context.animate
}
}
const initialToSet =
blockInitialAnimation || initial === false ? animate : initial
if (
initialToSet &&
typeof initialToSet !== "boolean" &&
!isAnimationControls(initialToSet)
) {
const list = Array.isArray(initialToSet) ? initialToSet : [initialToSet]
list.forEach((definition) => {
const resolved = resolveVariantFromProps(props, definition)
if (!resolved) return
const { transitionEnd, transition, ...target } = resolved
for (const key in target) values[key] = target[key]
for (const key in transitionEnd) values[key] = transitionEnd[key]
})
}
return values
}
/**
* Data about the element's current layout. Contains the latest measurements
* as well as the latest calculations of how to project from this layout
* into a given TargetProjection.
*/
export interface LayoutState {
/**
* Whether we've hydrated this state with the latest measurements.
*/
isHydrated: boolean
/**
* The latest viewport-box measurements of the element without transforms.
*/
layout: AxisBox2D
/**
* The measured viewport box as corrected by parent transforms up the
* visual element tree.
*/
layoutCorrected: AxisBox2D
/**
* The cumulative tree scale for this element. This starts at 1 per axis.
* When a transform is applied to an element we also apply it to the tree scale.
* The final value is used for scale-correcting values like border-radius,
* as well as ensuring calculated CSS translations are applied to compensate
* for this scale.
*/
treeScale: Point2D
/**
* A mutable piece of data that we write into the latest projection calculations
* that, when applied to an element, will project it from its layoutCorrected
* box into the provided TargetProjection.target
*/
delta: BoxDelta
/**
* A mutable piece of data that will project an element from layoutCorrected
* into TargetProjection.targetFinal.
*/
deltaFinal: BoxDelta
/**
* The latest generated delta transform. This is used to compare against
* the previously-generated transform to determine whether we need to trigger
* a render.
*/
deltaTransform: string
}
export function createLayoutState(): LayoutState {
return {
isHydrated: false,
layout: axisBox(),
layoutCorrected: axisBox(),
treeScale: { x: 1, y: 1 },
delta: delta(),
deltaFinal: delta(),
deltaTransform: "",
}
}
export const zeroLayout = createLayoutState()