: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
mixAxisDelta(targetDelta.y, delta.y, progress);
this.setTargetDelta(targetDelta);
if (this.relativeTarget &&
this.relativeTargetOrigin &&
this.relativeParent.layout) {
calcRelativePosition(relativeLayout, this.layout.layoutBox, this.relativeParent.layout.layoutBox);
mixBox(this.relativeTarget, this.relativeTargetOrigin, relativeLayout, progress);
* If this is an unchanged relative target we can consider the
if (prevRelativeTarget &&
boxEquals(this.relativeTarget, prevRelativeTarget)) {
this.isProjectionDirty = false;
prevRelativeTarget = createBox();
copyBoxInto(prevRelativeTarget, this.relativeTarget);
if (isSharedLayoutAnimation) {
this.animationValues = mixedValues;
mixValues(mixedValues, snapshotLatestValues, this.latestValues, progress, shouldCrossfadeOpacity, isOnlyMember);
this.root.scheduleUpdateProjection();
this.animationProgress = progress;
this.mixTargetDelta(this.options.layoutRoot ? 1000 : 0);
startAnimation(options) {
this.notifyListeners("animationStart");
this.currentAnimation && this.currentAnimation.stop();
if (this.resumingFrom && this.resumingFrom.currentAnimation) {
this.resumingFrom.currentAnimation.stop();
if (this.pendingAnimation) {
cancelFrame(this.pendingAnimation);
this.pendingAnimation = undefined;
* Start the animation in the next frame to have a frame with progress 0,
* where the target is the same as when the animation started, so we can
* calculate the relative positions correctly for instant transitions.
this.pendingAnimation = frame_frame.update(() => {
globalProjectionState.hasAnimatedSinceResize = true;
this.currentAnimation = animateSingleValue(0, animationTarget, {
this.mixTargetDelta(latest);
options.onUpdate && options.onUpdate(latest);
options.onComplete && options.onComplete();
this.completeAnimation();
this.resumingFrom.currentAnimation = this.currentAnimation;
this.pendingAnimation = undefined;
this.resumingFrom.currentAnimation = undefined;
this.resumingFrom.preserveOpacity = undefined;
const stack = this.getStack();
stack && stack.exitAnimationComplete();
this.notifyListeners("animationComplete");
if (this.currentAnimation) {
this.mixTargetDelta && this.mixTargetDelta(animationTarget);
this.currentAnimation.stop();
this.completeAnimation();
applyTransformsToTarget() {
const lead = this.getLead();
let { targetWithTransforms, target, layout, latestValues } = lead;
if (!targetWithTransforms || !target || !layout)
* If we're only animating position, and this element isn't the lead element,
* then instead of projecting into the lead box we instead want to calculate
* a new target that aligns the two boxes but maintains the layout shape.
shouldAnimatePositionOnly(this.options.animationType, this.layout.layoutBox, layout.layoutBox)) {
target = this.target || createBox();
const xLength = calcLength(this.layout.layoutBox.x);
target.x.min = lead.target.x.min;
target.x.max = target.x.min + xLength;
const yLength = calcLength(this.layout.layoutBox.y);
target.y.min = lead.target.y.min;
target.y.max = target.y.min + yLength;
copyBoxInto(targetWithTransforms, target);
* Apply the latest user-set transforms to the targetBox to produce the targetBoxFinal.
* This is the final box that we will then project into by calculating a transform delta and
* applying it to the corrected box.
transformBox(targetWithTransforms, latestValues);
* Update the delta between the corrected box and the final target box, after
* user-set transforms are applied to it. This will be used by the renderer to
* create a transform style that will reproject the element from its layout layout
* into the desired bounding box.
calcBoxDelta(this.projectionDeltaWithTransform, this.layoutCorrected, targetWithTransforms, latestValues);
registerSharedNode(layoutId, node) {
if (!this.sharedNodes.has(layoutId)) {
this.sharedNodes.set(layoutId, new NodeStack());
const stack = this.sharedNodes.get(layoutId);
const config = node.options.initialPromotionConfig;
transition: config ? config.transition : undefined,
preserveFollowOpacity: config && config.shouldPreserveFollowOpacity
? config.shouldPreserveFollowOpacity(node)
const stack = this.getStack();
return stack ? stack.lead === this : true;
const { layoutId } = this.options;
return layoutId ? ((_a = this.getStack()) === null || _a === void 0 ? void 0 : _a.lead) || this : this;
const { layoutId } = this.options;
return layoutId ? (_a = this.getStack()) === null || _a === void 0 ? void 0 : _a.prevLead : undefined;
const { layoutId } = this.options;
return this.root.sharedNodes.get(layoutId);
promote({ needsReset, transition, preserveFollowOpacity, } = {}) {
const stack = this.getStack();
stack.promote(this, preserveFollowOpacity);
this.projectionDelta = undefined;
this.setOptions({ transition });
const stack = this.getStack();
return stack.relegate(this);
const { visualElement } = this.options;
// If there's no detected skew or rotation values, we can early return without a forced render.
let hasDistortingTransform = false;
* An unrolled check for rotation values. Most elements don't have any rotation and
* skipping the nested loop and new object creation is 50% faster.
const { latestValues } = visualElement;
hasDistortingTransform = true;
// If there's no distorting values, we don't need to do any more.
if (!hasDistortingTransform)
resetDistortingTransform("z", visualElement, resetValues, this.animationValues);
// Check the skew and rotate value of all axes and reset to 0
for (let i = 0; i < transformAxes.length; i++) {
resetDistortingTransform(`rotate${transformAxes[i]}`, visualElement, resetValues, this.animationValues);
resetDistortingTransform(`skew${transformAxes[i]}`, visualElement, resetValues, this.animationValues);
// Force a render of this element to apply the transform with all skews and rotations
// Put back all the values we reset
for (const key in resetValues) {
visualElement.setStaticValue(key, resetValues[key]);
if (this.animationValues) {
this.animationValues[key] = resetValues[key];
// Schedule a render for the next frame. This ensures we won't visually
// see the element with the reset rotate value applied.
visualElement.scheduleRender();
getProjectionStyles(styleProp) {
if (!this.instance || this.isSVG)
const transformTemplate = this.getTransformTemplate();
resolveMotionValue(styleProp === null || styleProp === void 0 ? void 0 : styleProp.pointerEvents) || "";
styles.transform = transformTemplate
? transformTemplate(this.latestValues, "")
const lead = this.getLead();
if (!this.projectionDelta || !this.layout || !lead.target) {
if (this.options.layoutId) {
this.latestValues.opacity !== undefined
? this.latestValues.opacity
emptyStyles.pointerEvents =
resolveMotionValue(styleProp === null || styleProp === void 0 ? void 0 : styleProp.pointerEvents) || "";
if (this.hasProjected && !hasTransform(this.latestValues)) {
emptyStyles.transform = transformTemplate
? transformTemplate({}, "")
this.hasProjected = false;
const valuesToRender = lead.animationValues || lead.latestValues;
this.applyTransformsToTarget();
styles.transform = buildProjectionTransform(this.projectionDeltaWithTransform, this.treeScale, valuesToRender);
styles.transform = transformTemplate(valuesToRender, styles.transform);
const { x, y } = this.projectionDelta;
styles.transformOrigin = `${x.origin * 100}% ${y.origin * 100}% 0`;
if (lead.animationValues) {
* If the lead component is animating, assign this either the entering/leaving
? (_b = (_a = valuesToRender.opacity) !== null && _a !== void 0 ? _a : this.latestValues.opacity) !== null && _b !== void 0 ? _b : 1
? this.latestValues.opacity
: valuesToRender.opacityExit;
* Or we're not animating at all, set the lead component to its layout
* opacity and other components to hidden.
? valuesToRender.opacity !== undefined
: valuesToRender.opacityExit !== undefined
? valuesToRender.opacityExit
for (const key in scaleCorrectors) {
if (valuesToRender[key] === undefined)
const { correct, applyTo } = scaleCorrectors[key];
* Only apply scale correction to the value if we have an
* active projection transform. Otherwise these values become
* vulnerable to distortion if the element changes size without
* a corresponding layout animation.
const corrected = styles.transform === "none"
: correct(valuesToRender[key], lead);
const num = applyTo.length;
for (let i = 0; i < num; i++) {
styles[applyTo[i]] = corrected;
* Disable pointer events on follow components. This is to ensure
* that if a follow component covers a lead component it doesn't block
* pointer events on the lead.
if (this.options.layoutId) {
? resolveMotionValue(styleProp === null || styleProp === void 0 ? void 0 : styleProp.pointerEvents) || ""
this.resumeFrom = this.snapshot = undefined;
this.root.nodes.forEach((node) => { var _a; return (_a = node.currentAnimation) === null || _a === void 0 ? void 0 : _a.stop(); });
this.root.nodes.forEach(clearMeasurements);
this.root.sharedNodes.clear();
function updateLayout(node) {
function notifyLayoutUpdate(node) {
const snapshot = ((_a = node.resumeFrom) === null || _a === void 0 ? void 0 : _a.snapshot) || node.snapshot;
node.hasListeners("didUpdate")) {
const { layoutBox: layout, measuredBox: measuredLayout } = node.layout;
const { animationType } = node.options;
const isShared = snapshot.source !== node.layout.source;
// TODO Maybe we want to also resize the layout snapshot so we don't trigger
// animations for instance if layout="size" and an element has only changed position
if (animationType === "size") {
const axisSnapshot = isShared
? snapshot.measuredBox[axis]
: snapshot.layoutBox[axis];
const length = calcLength(axisSnapshot);
axisSnapshot.min = layout[axis].min;
axisSnapshot.max = axisSnapshot.min + length;
else if (shouldAnimatePositionOnly(animationType, snapshot.layoutBox, layout)) {
const axisSnapshot = isShared
? snapshot.measuredBox[axis]
: snapshot.layoutBox[axis];
const length = calcLength(layout[axis]);
axisSnapshot.max = axisSnapshot.min + length;
* Ensure relative target gets resized and rerendererd
if (node.relativeTarget && !node.currentAnimation) {
node.isProjectionDirty = true;
node.relativeTarget[axis].max =
node.relativeTarget[axis].min + length;
const layoutDelta = createDelta();
calcBoxDelta(layoutDelta, layout, snapshot.layoutBox);
const visualDelta = createDelta();
calcBoxDelta(visualDelta, node.applyTransform(measuredLayout, true), snapshot.measuredBox);
calcBoxDelta(visualDelta, layout, snapshot.layoutBox);
const hasLayoutChanged = !isDeltaZero(layoutDelta);
let hasRelativeTargetChanged = false;
const relativeParent = node.getClosestProjectingParent();
* If the relativeParent is itself resuming from a different element then
* the relative snapshot is not relavent
if (relativeParent && !relativeParent.resumeFrom) {
const { snapshot: parentSnapshot, layout: parentLayout } = relativeParent;
if (parentSnapshot && parentLayout) {
const relativeSnapshot = createBox();
calcRelativePosition(relativeSnapshot, snapshot.layoutBox, parentSnapshot.layoutBox);
const relativeLayout = createBox();
calcRelativePosition(relativeLayout, layout, parentLayout.layoutBox);
if (!boxEqualsRounded(relativeSnapshot, relativeLayout)) {
hasRelativeTargetChanged = true;
if (relativeParent.options.layoutRoot) {
node.relativeTarget = relativeLayout;
node.relativeTargetOrigin = relativeSnapshot;
node.relativeParent = relativeParent;
node.notifyListeners("didUpdate", {
hasRelativeTargetChanged,
else if (node.isLead()) {
const { onExitComplete } = node.options;
onExitComplete && onExitComplete();
* TODO: Investigate why this transition is being passed in as {type: false } from Framer
* and why we need it at all
node.options.transition = undefined;
function propagateDirtyNodes(node) {
* Increase debug counter for nodes encountered this frame
projectionFrameData.totalNodes++;
* If this node isn't projecting, propagate isProjectionDirty. It will have
* no performance impact but it will allow the next child that *is* projecting
* but *isn't* dirty to just check its parent to see if *any* ancestor needs
if (!node.isProjecting()) {
node.isProjectionDirty = node.parent.isProjectionDirty;
* Propagate isSharedProjectionDirty and isTransformDirty
* throughout the whole tree. A future revision can take another look at
* this but for safety we still recalcualte shared nodes.
node.isSharedProjectionDirty || (node.isSharedProjectionDirty = Boolean(node.isProjectionDirty ||
node.parent.isProjectionDirty ||
node.parent.isSharedProjectionDirty));
node.isTransformDirty || (node.isTransformDirty = node.parent.isTransformDirty);
function cleanDirtyNodes(node) {
node.isSharedProjectionDirty =
function clearSnapshot(node) {
function clearMeasurements(node) {
node.clearMeasurements();
function clearIsLayoutDirty(node) {
node.isLayoutDirty = false;
function resetTransformStyle(node) {
const { visualElement } = node.options;
if (visualElement && visualElement.getProps().onBeforeLayoutMeasure) {
visualElement.notify("BeforeLayoutMeasure");
function finishAnimation(node) {
node.targetDelta = node.relativeTarget = node.target = undefined;
node.isProjectionDirty = true;
function resolveTargetDelta(node) {
node.resolveTargetDelta();
function calcProjection(node) {