This commit is contained in:
Iliyan Angelov
2025-09-14 23:24:25 +03:00
commit c67067a2a4
71311 changed files with 6800714 additions and 0 deletions

View File

@@ -0,0 +1,312 @@
import { isAnimationControls } from '../../animation/utils/is-animation-controls.mjs';
import { isKeyframesTarget } from '../../animation/utils/is-keyframes-target.mjs';
import { shallowCompare } from '../../utils/shallow-compare.mjs';
import { isVariantLabel } from './is-variant-label.mjs';
import { resolveVariant } from './resolve-dynamic-variants.mjs';
import { variantPriorityOrder } from './variant-props.mjs';
import { animateVisualElement } from '../../animation/interfaces/visual-element.mjs';
const reversePriorityOrder = [...variantPriorityOrder].reverse();
const numAnimationTypes = variantPriorityOrder.length;
function animateList(visualElement) {
return (animations) => Promise.all(animations.map(({ animation, options }) => animateVisualElement(visualElement, animation, options)));
}
function createAnimationState(visualElement) {
let animate = animateList(visualElement);
const state = createState();
let isInitialRender = true;
/**
* This function will be used to reduce the animation definitions for
* each active animation type into an object of resolved values for it.
*/
const buildResolvedTypeValues = (acc, definition) => {
const resolved = resolveVariant(visualElement, definition);
if (resolved) {
const { transition, transitionEnd, ...target } = resolved;
acc = { ...acc, ...target, ...transitionEnd };
}
return acc;
};
/**
* This just allows us to inject mocked animation functions
* @internal
*/
function setAnimateFunction(makeAnimator) {
animate = makeAnimator(visualElement);
}
/**
* When we receive new props, we need to:
* 1. Create a list of protected keys for each type. This is a directory of
* value keys that are currently being "handled" by types of a higher priority
* so that whenever an animation is played of a given type, these values are
* protected from being animated.
* 2. Determine if an animation type needs animating.
* 3. Determine if any values have been removed from a type and figure out
* what to animate those to.
*/
function animateChanges(options, changedActiveType) {
const props = visualElement.getProps();
const context = visualElement.getVariantContext(true) || {};
/**
* A list of animations that we'll build into as we iterate through the animation
* types. This will get executed at the end of the function.
*/
const animations = [];
/**
* Keep track of which values have been removed. Then, as we hit lower priority
* animation types, we can check if they contain removed values and animate to that.
*/
const removedKeys = new Set();
/**
* A dictionary of all encountered keys. This is an object to let us build into and
* copy it without iteration. Each time we hit an animation type we set its protected
* keys - the keys its not allowed to animate - to the latest version of this object.
*/
let encounteredKeys = {};
/**
* If a variant has been removed at a given index, and this component is controlling
* variant animations, we want to ensure lower-priority variants are forced to animate.
*/
let removedVariantIndex = Infinity;
/**
* Iterate through all animation types in reverse priority order. For each, we want to
* detect which values it's handling and whether or not they've changed (and therefore
* need to be animated). If any values have been removed, we want to detect those in
* lower priority props and flag for animation.
*/
for (let i = 0; i < numAnimationTypes; i++) {
const type = reversePriorityOrder[i];
const typeState = state[type];
const prop = props[type] !== undefined ? props[type] : context[type];
const propIsVariant = isVariantLabel(prop);
/**
* If this type has *just* changed isActive status, set activeDelta
* to that status. Otherwise set to null.
*/
const activeDelta = type === changedActiveType ? typeState.isActive : null;
if (activeDelta === false)
removedVariantIndex = i;
/**
* If this prop is an inherited variant, rather than been set directly on the
* component itself, we want to make sure we allow the parent to trigger animations.
*
* TODO: Can probably change this to a !isControllingVariants check
*/
let isInherited = prop === context[type] && prop !== props[type] && propIsVariant;
/**
*
*/
if (isInherited &&
isInitialRender &&
visualElement.manuallyAnimateOnMount) {
isInherited = false;
}
/**
* Set all encountered keys so far as the protected keys for this type. This will
* be any key that has been animated or otherwise handled by active, higher-priortiy types.
*/
typeState.protectedKeys = { ...encounteredKeys };
// Check if we can skip analysing this prop early
if (
// If it isn't active and hasn't *just* been set as inactive
(!typeState.isActive && activeDelta === null) ||
// If we didn't and don't have any defined prop for this animation type
(!prop && !typeState.prevProp) ||
// Or if the prop doesn't define an animation
isAnimationControls(prop) ||
typeof prop === "boolean") {
continue;
}
/**
* As we go look through the values defined on this type, if we detect
* a changed value or a value that was removed in a higher priority, we set
* this to true and add this prop to the animation list.
*/
const variantDidChange = checkVariantsDidChange(typeState.prevProp, prop);
let shouldAnimateType = variantDidChange ||
// If we're making this variant active, we want to always make it active
(type === changedActiveType &&
typeState.isActive &&
!isInherited &&
propIsVariant) ||
// If we removed a higher-priority variant (i is in reverse order)
(i > removedVariantIndex && propIsVariant);
let handledRemovedValues = false;
/**
* As animations can be set as variant lists, variants or target objects, we
* coerce everything to an array if it isn't one already
*/
const definitionList = Array.isArray(prop) ? prop : [prop];
/**
* Build an object of all the resolved values. We'll use this in the subsequent
* animateChanges calls to determine whether a value has changed.
*/
let resolvedValues = definitionList.reduce(buildResolvedTypeValues, {});
if (activeDelta === false)
resolvedValues = {};
/**
* Now we need to loop through all the keys in the prev prop and this prop,
* and decide:
* 1. If the value has changed, and needs animating
* 2. If it has been removed, and needs adding to the removedKeys set
* 3. If it has been removed in a higher priority type and needs animating
* 4. If it hasn't been removed in a higher priority but hasn't changed, and
* needs adding to the type's protectedKeys list.
*/
const { prevResolvedValues = {} } = typeState;
const allKeys = {
...prevResolvedValues,
...resolvedValues,
};
const markToAnimate = (key) => {
shouldAnimateType = true;
if (removedKeys.has(key)) {
handledRemovedValues = true;
removedKeys.delete(key);
}
typeState.needsAnimating[key] = true;
};
for (const key in allKeys) {
const next = resolvedValues[key];
const prev = prevResolvedValues[key];
// If we've already handled this we can just skip ahead
if (encounteredKeys.hasOwnProperty(key))
continue;
/**
* If the value has changed, we probably want to animate it.
*/
let valueHasChanged = false;
if (isKeyframesTarget(next) && isKeyframesTarget(prev)) {
valueHasChanged = !shallowCompare(next, prev);
}
else {
valueHasChanged = next !== prev;
}
if (valueHasChanged) {
if (next !== undefined) {
// If next is defined and doesn't equal prev, it needs animating
markToAnimate(key);
}
else {
// If it's undefined, it's been removed.
removedKeys.add(key);
}
}
else if (next !== undefined && removedKeys.has(key)) {
/**
* If next hasn't changed and it isn't undefined, we want to check if it's
* been removed by a higher priority
*/
markToAnimate(key);
}
else {
/**
* If it hasn't changed, we add it to the list of protected values
* to ensure it doesn't get animated.
*/
typeState.protectedKeys[key] = true;
}
}
/**
* Update the typeState so next time animateChanges is called we can compare the
* latest prop and resolvedValues to these.
*/
typeState.prevProp = prop;
typeState.prevResolvedValues = resolvedValues;
/**
*
*/
if (typeState.isActive) {
encounteredKeys = { ...encounteredKeys, ...resolvedValues };
}
if (isInitialRender && visualElement.blockInitialAnimation) {
shouldAnimateType = false;
}
/**
* If this is an inherited prop we want to hard-block animations
*/
if (shouldAnimateType && (!isInherited || handledRemovedValues)) {
animations.push(...definitionList.map((animation) => ({
animation: animation,
options: { type, ...options },
})));
}
}
/**
* If there are some removed value that haven't been dealt with,
* we need to create a new animation that falls back either to the value
* defined in the style prop, or the last read value.
*/
if (removedKeys.size) {
const fallbackAnimation = {};
removedKeys.forEach((key) => {
const fallbackTarget = visualElement.getBaseTarget(key);
if (fallbackTarget !== undefined) {
fallbackAnimation[key] = fallbackTarget;
}
});
animations.push({ animation: fallbackAnimation });
}
let shouldAnimate = Boolean(animations.length);
if (isInitialRender &&
(props.initial === false || props.initial === props.animate) &&
!visualElement.manuallyAnimateOnMount) {
shouldAnimate = false;
}
isInitialRender = false;
return shouldAnimate ? animate(animations) : Promise.resolve();
}
/**
* Change whether a certain animation type is active.
*/
function setActive(type, isActive, options) {
var _a;
// If the active state hasn't changed, we can safely do nothing here
if (state[type].isActive === isActive)
return Promise.resolve();
// Propagate active change to children
(_a = visualElement.variantChildren) === null || _a === void 0 ? void 0 : _a.forEach((child) => { var _a; return (_a = child.animationState) === null || _a === void 0 ? void 0 : _a.setActive(type, isActive); });
state[type].isActive = isActive;
const animations = animateChanges(options, type);
for (const key in state) {
state[key].protectedKeys = {};
}
return animations;
}
return {
animateChanges,
setActive,
setAnimateFunction,
getState: () => state,
};
}
function checkVariantsDidChange(prev, next) {
if (typeof next === "string") {
return next !== prev;
}
else if (Array.isArray(next)) {
return !shallowCompare(next, prev);
}
return false;
}
function createTypeState(isActive = false) {
return {
isActive,
protectedKeys: {},
needsAnimating: {},
prevResolvedValues: {},
};
}
function createState() {
return {
animate: createTypeState(true),
whileInView: createTypeState(),
whileHover: createTypeState(),
whileTap: createTypeState(),
whileDrag: createTypeState(),
whileFocus: createTypeState(),
exit: createTypeState(),
};
}
export { checkVariantsDidChange, createAnimationState };

View File

@@ -0,0 +1,3 @@
const compareByDepth = (a, b) => a.depth - b.depth;
export { compareByDepth };

View File

@@ -0,0 +1,24 @@
import { addUniqueItem, removeItem } from '../../utils/array.mjs';
import { compareByDepth } from './compare-by-depth.mjs';
class FlatTree {
constructor() {
this.children = [];
this.isDirty = false;
}
add(child) {
addUniqueItem(this.children, child);
this.isDirty = true;
}
remove(child) {
removeItem(this.children, child);
this.isDirty = true;
}
forEach(callback) {
this.isDirty && this.children.sort(compareByDepth);
this.isDirty = false;
this.children.forEach(callback);
}
}
export { FlatTree };

View File

@@ -0,0 +1,13 @@
import { isAnimationControls } from '../../animation/utils/is-animation-controls.mjs';
import { isVariantLabel } from './is-variant-label.mjs';
import { variantProps } from './variant-props.mjs';
function isControllingVariants(props) {
return (isAnimationControls(props.animate) ||
variantProps.some((name) => isVariantLabel(props[name])));
}
function isVariantNode(props) {
return Boolean(isControllingVariants(props) || props.variants);
}
export { isControllingVariants, isVariantNode };

View File

@@ -0,0 +1,8 @@
/**
* Decides if the supplied variable is variant label
*/
function isVariantLabel(v) {
return typeof v === "string" || Array.isArray(v);
}
export { isVariantLabel };

View File

@@ -0,0 +1,63 @@
import { isWillChangeMotionValue } from '../../value/use-will-change/is.mjs';
import { warnOnce } from '../../utils/warn-once.mjs';
import { motionValue } from '../../value/index.mjs';
import { isMotionValue } from '../../value/utils/is-motion-value.mjs';
function updateMotionValuesFromProps(element, next, prev) {
const { willChange } = next;
for (const key in next) {
const nextValue = next[key];
const prevValue = prev[key];
if (isMotionValue(nextValue)) {
/**
* If this is a motion value found in props or style, we want to add it
* to our visual element's motion value map.
*/
element.addValue(key, nextValue);
if (isWillChangeMotionValue(willChange)) {
willChange.add(key);
}
/**
* Check the version of the incoming motion value with this version
* and warn against mismatches.
*/
if (process.env.NODE_ENV === "development") {
warnOnce(nextValue.version === "10.18.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 10.18.0 may not work as expected.`);
}
}
else if (isMotionValue(prevValue)) {
/**
* If we're swapping from a motion value to a static value,
* create a new motion value from that
*/
element.addValue(key, motionValue(nextValue, { owner: element }));
if (isWillChangeMotionValue(willChange)) {
willChange.remove(key);
}
}
else if (prevValue !== nextValue) {
/**
* If this is a flat value that has changed, update the motion value
* or create one if it doesn't exist. We only want to do this if we're
* not handling the value with our animation state.
*/
if (element.hasValue(key)) {
const existingValue = element.getValue(key);
// TODO: Only update values that aren't being animated or even looked at
!existingValue.hasAnimated && existingValue.set(nextValue);
}
else {
const latestValue = element.getStaticValue(key);
element.addValue(key, motionValue(latestValue !== undefined ? latestValue : nextValue, { owner: element }));
}
}
}
// Handle removed values
for (const key in prev) {
if (next[key] === undefined)
element.removeValue(key);
}
return next;
}
export { updateMotionValuesFromProps };

View File

@@ -0,0 +1,24 @@
import { resolveVariantFromProps } from './resolve-variants.mjs';
/**
* Creates an object containing the latest state of every MotionValue on a VisualElement
*/
function getCurrent(visualElement) {
const current = {};
visualElement.values.forEach((value, key) => (current[key] = value.get()));
return current;
}
/**
* Creates an object containing the latest velocity of every MotionValue on a VisualElement
*/
function getVelocity(visualElement) {
const velocity = {};
visualElement.values.forEach((value, key) => (velocity[key] = value.getVelocity()));
return velocity;
}
function resolveVariant(visualElement, definition, custom) {
const props = visualElement.getProps();
return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, getCurrent(visualElement), getVelocity(visualElement));
}
export { resolveVariant };

View File

@@ -0,0 +1,26 @@
function resolveVariantFromProps(props, definition, custom, currentValues = {}, currentVelocity = {}) {
/**
* If the variant definition is a function, resolve.
*/
if (typeof definition === "function") {
definition = definition(custom !== undefined ? custom : props.custom, currentValues, currentVelocity);
}
/**
* If the variant definition is a variant label, or
* the function returned a variant label, resolve.
*/
if (typeof definition === "string") {
definition = props.variants && props.variants[definition];
}
/**
* At this point we've resolved both functions and variant labels,
* but the resolved variant label might itself have been a function.
* If so, resolve. This can only have returned a valid target object.
*/
if (typeof definition === "function") {
definition = definition(custom !== undefined ? custom : props.custom, currentValues, currentVelocity);
}
return definition;
}
export { resolveVariantFromProps };

View File

@@ -0,0 +1,124 @@
import { isNumericalString } from '../../utils/is-numerical-string.mjs';
import { isZeroValueString } from '../../utils/is-zero-value-string.mjs';
import { resolveFinalValueInKeyframes } from '../../utils/resolve-value.mjs';
import { motionValue } from '../../value/index.mjs';
import { complex } from '../../value/types/complex/index.mjs';
import { getAnimatableNone } from '../dom/value-types/animatable-none.mjs';
import { findValueType } from '../dom/value-types/find.mjs';
import { resolveVariant } from './resolve-dynamic-variants.mjs';
/**
* Set VisualElement's MotionValue, creating a new MotionValue for it if
* it doesn't exist.
*/
function setMotionValue(visualElement, key, value) {
if (visualElement.hasValue(key)) {
visualElement.getValue(key).set(value);
}
else {
visualElement.addValue(key, motionValue(value));
}
}
function setTarget(visualElement, definition) {
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);
}
}
function setVariants(visualElement, variantLabels) {
const reversedLabels = [...variantLabels].reverse();
reversedLabels.forEach((key) => {
const variant = visualElement.getVariant(key);
variant && setTarget(visualElement, variant);
if (visualElement.variantChildren) {
visualElement.variantChildren.forEach((child) => {
setVariants(child, variantLabels);
});
}
});
}
function setValues(visualElement, definition) {
if (Array.isArray(definition)) {
return setVariants(visualElement, definition);
}
else if (typeof definition === "string") {
return setVariants(visualElement, [definition]);
}
else {
setTarget(visualElement, definition);
}
}
function checkTargetForNewValues(visualElement, target, origin) {
var _a, _b;
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 = null;
/**
* If the target is a series of keyframes, we can use the first value
* in the array. If this first value is null, we'll still need to read from the DOM.
*/
if (Array.isArray(targetValue)) {
value = targetValue[0];
}
/**
* If the target isn't keyframes, or the first keyframe was null, we need to
* first check if an origin value was explicitly defined in the transition as "from",
* if not read the value from the DOM. As an absolute fallback, take the defined target value.
*/
if (value === null) {
value = (_b = (_a = origin[key]) !== null && _a !== void 0 ? _a : visualElement.readValue(key)) !== null && _b !== void 0 ? _b : target[key];
}
/**
* If value is still undefined or null, ignore it. Preferably this would throw,
* but this was causing issues in Framer.
*/
if (value === undefined || value === null)
continue;
if (typeof value === "string" &&
(isNumericalString(value) || isZeroValueString(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, { owner: visualElement }));
if (origin[key] === undefined) {
origin[key] = value;
}
if (value !== null)
visualElement.setBaseTarget(key, value);
}
}
function getOriginFromTransition(key, transition) {
if (!transition)
return;
const valueTransition = transition[key] || transition["default"] || transition;
return valueTransition.from;
}
function getOrigin(target, transition, visualElement) {
const origin = {};
for (const key in target) {
const transitionOrigin = getOriginFromTransition(key, transition);
if (transitionOrigin !== undefined) {
origin[key] = transitionOrigin;
}
else {
const value = visualElement.getValue(key);
if (value) {
origin[key] = value.get();
}
}
}
return origin;
}
export { checkTargetForNewValues, getOrigin, getOriginFromTransition, setTarget, setValues };

View File

@@ -0,0 +1,12 @@
const variantPriorityOrder = [
"animate",
"whileInView",
"whileFocus",
"whileHover",
"whileTap",
"whileDrag",
"exit",
];
const variantProps = ["initial", ...variantPriorityOrder];
export { variantPriorityOrder, variantProps };