framer-motion 12.42.0 → 12.42.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dom.js +1 -1
- package/dist/framer-motion.dev.js +165 -54
- package/dist/framer-motion.js +1 -1
- package/dist/size-rollup-animate.js +1 -1
- package/dist/size-rollup-animate.js.map +1 -1
- package/dist/size-rollup-dom-animation-assets.js +1 -1
- package/dist/size-rollup-dom-max-assets.js +1 -1
- package/dist/size-rollup-dom-max.js +1 -1
- package/dist/size-rollup-m.js +1 -1
- package/dist/size-rollup-m.js.map +1 -1
- package/dist/size-rollup-motion.js +1 -1
- package/dist/size-rollup-motion.js.map +1 -1
- package/dist/size-rollup-waapi-animate.js.map +1 -1
- package/package.json +3 -3
|
@@ -4595,6 +4595,19 @@
|
|
|
4595
4595
|
}
|
|
4596
4596
|
}
|
|
4597
4597
|
|
|
4598
|
+
/**
|
|
4599
|
+
* The four corner-radius longhands. Shared so the projection mixer, scale
|
|
4600
|
+
* corrector, WAAPI px-value set and view-transition crop pass don't each carry
|
|
4601
|
+
* their own copy. Order is irrelevant - every consumer mixes/corrects/animates
|
|
4602
|
+
* each corner independently.
|
|
4603
|
+
*/
|
|
4604
|
+
const cornerRadiusProps = [
|
|
4605
|
+
"borderTopLeftRadius",
|
|
4606
|
+
"borderTopRightRadius",
|
|
4607
|
+
"borderBottomRightRadius",
|
|
4608
|
+
"borderBottomLeftRadius",
|
|
4609
|
+
];
|
|
4610
|
+
|
|
4598
4611
|
const pxValues = new Set([
|
|
4599
4612
|
// Border props
|
|
4600
4613
|
"borderWidth",
|
|
@@ -4603,10 +4616,7 @@
|
|
|
4603
4616
|
"borderBottomWidth",
|
|
4604
4617
|
"borderLeftWidth",
|
|
4605
4618
|
"borderRadius",
|
|
4606
|
-
|
|
4607
|
-
"borderTopRightRadius",
|
|
4608
|
-
"borderBottomRightRadius",
|
|
4609
|
-
"borderBottomLeftRadius",
|
|
4619
|
+
...cornerRadiusProps,
|
|
4610
4620
|
// Positioning props
|
|
4611
4621
|
"width",
|
|
4612
4622
|
"maxWidth",
|
|
@@ -5897,6 +5907,12 @@
|
|
|
5897
5907
|
}
|
|
5898
5908
|
|
|
5899
5909
|
const definitionNames = ["layout", "enter", "exit", "new", "old"];
|
|
5910
|
+
/**
|
|
5911
|
+
* Whether a computed border-radius is square (every component zero). Splitting
|
|
5912
|
+
* on whitespace handles two-value/elliptical radii like "0px 20px" - a leading
|
|
5913
|
+
* `parseFloat` alone would misread the non-zero vertical radius as square.
|
|
5914
|
+
*/
|
|
5915
|
+
const isSquareRadius = (radius) => radius.split(" ").every((value) => parseFloat(value) === 0);
|
|
5900
5916
|
/**
|
|
5901
5917
|
* The `ViewTransitionTarget` buckets driving each generated layer type, in
|
|
5902
5918
|
* priority order - the inverse of `chooseLayerType`. The new view is driven by
|
|
@@ -6083,16 +6099,48 @@
|
|
|
6083
6099
|
return transition;
|
|
6084
6100
|
};
|
|
6085
6101
|
/**
|
|
6086
|
-
*
|
|
6087
|
-
*
|
|
6102
|
+
* Resolve a layer's group (`layout`) timing to plain WAAPI values: native
|
|
6103
|
+
* ms `delay`/`duration` and a baked `ease`. The single source of group
|
|
6104
|
+
* timing, shared by the generated-group retiming and the crop corner-radius
|
|
6105
|
+
* pass so the rounded clip animates on exactly the box's timing. It returns
|
|
6106
|
+
* no generator `type` (the WAAPI-only `NativeAnimation` rejects a string
|
|
6107
|
+
* type) nor `repeat`/`times` (which the group's `updateTiming` ignores), so
|
|
6108
|
+
* none of them can leak into the radius animation and desync it.
|
|
6109
|
+
*/
|
|
6110
|
+
const resolveGroupTiming = (name) => {
|
|
6111
|
+
const [index, total] = staggerPosition(name, "group");
|
|
6112
|
+
const transition = resolveLayerTransition(layerTargets.get(name), "group", "layout", index === -1 ? 0 : index, total);
|
|
6113
|
+
transition.duration && (transition.duration = secondsToMilliseconds(transition.duration));
|
|
6114
|
+
const { delay = 0, duration, ease } = applyGeneratorOptions(transition);
|
|
6115
|
+
return { delay: secondsToMilliseconds(delay), duration, ease };
|
|
6116
|
+
};
|
|
6117
|
+
/**
|
|
6118
|
+
* Each layer's measured box + corner radii per snapshot. The box lets
|
|
6119
|
+
* `finalizeCrop` tell whether a morph's aspect ratio changed (the only case
|
|
6120
|
+
* worth cropping); the radii let a cropped morph's group clip animate each
|
|
6121
|
+
* corner from the old element's radius to the new element's, keeping it
|
|
6122
|
+
* rounded where `overflow: clip` would otherwise square the corners.
|
|
6123
|
+
*
|
|
6124
|
+
* We never flatten the source for capture (a snapshot is a paint of the live
|
|
6125
|
+
* DOM, so squaring an element just for its capture would flash one real
|
|
6126
|
+
* square frame). For an aspect-changing morph `object-fit: cover` crops each
|
|
6127
|
+
* snapshot's own baked corners off-screen mid-morph, so the animated clip is
|
|
6128
|
+
* the only visible corner; a near-same-aspect forced crop (`.crop(true)`)
|
|
6129
|
+
* can't hide the outgoing snapshot's silhouette, but the endpoints coincide.
|
|
6088
6130
|
*/
|
|
6089
|
-
const
|
|
6131
|
+
const cropMeasurements = new Map();
|
|
6090
6132
|
const measureLayers = (phase) => nameRegistry.forEach((name, element) => {
|
|
6091
|
-
const
|
|
6133
|
+
const el = element;
|
|
6134
|
+
const rect = el.getBoundingClientRect?.();
|
|
6092
6135
|
if (rect && rect.height) {
|
|
6093
|
-
const
|
|
6094
|
-
|
|
6095
|
-
|
|
6136
|
+
const style = getComputedStyle(el);
|
|
6137
|
+
const radii = {};
|
|
6138
|
+
for (const corner of cornerRadiusProps) {
|
|
6139
|
+
radii[corner] = style[corner];
|
|
6140
|
+
}
|
|
6141
|
+
const entry = cropMeasurements.get(name) ?? {};
|
|
6142
|
+
entry[phase] = { width: rect.width, height: rect.height, radii };
|
|
6143
|
+
cropMeasurements.set(name, entry);
|
|
6096
6144
|
}
|
|
6097
6145
|
});
|
|
6098
6146
|
/**
|
|
@@ -6122,7 +6170,7 @@
|
|
|
6122
6170
|
* never "changed".
|
|
6123
6171
|
*/
|
|
6124
6172
|
const aspectChanged = (name) => {
|
|
6125
|
-
const box =
|
|
6173
|
+
const box = cropMeasurements.get(name);
|
|
6126
6174
|
if (!box?.old || !box?.new || !box.old.height || !box.new.height) {
|
|
6127
6175
|
return false;
|
|
6128
6176
|
}
|
|
@@ -6366,6 +6414,31 @@
|
|
|
6366
6414
|
}
|
|
6367
6415
|
continue;
|
|
6368
6416
|
}
|
|
6417
|
+
/**
|
|
6418
|
+
* Drop the orphaned half of the default crossfade. The UA
|
|
6419
|
+
* fades old out and new in as a *pair*; if the opposing half
|
|
6420
|
+
* was explicitly overridden with something other than an
|
|
6421
|
+
* opacity fade (a clip or transform reveal), this side's
|
|
6422
|
+
* default opacity fade has no partner - left to run it would
|
|
6423
|
+
* dissolve what should be a static backdrop (e.g.
|
|
6424
|
+
* `.new({ clipPath })` should reveal over a still old view,
|
|
6425
|
+
* not fade the old out around the growing clip). Cancel it -
|
|
6426
|
+
* and its `plus-lighter` sibling on the same pseudo, which
|
|
6427
|
+
* would otherwise flash bright where the two opaque layers
|
|
6428
|
+
* overlap. A genuine crossfade (the opposing side also fading
|
|
6429
|
+
* opacity) keeps both halves and is handled above.
|
|
6430
|
+
*/
|
|
6431
|
+
const opposite = name.type === "old"
|
|
6432
|
+
? "new"
|
|
6433
|
+
: name.type === "new"
|
|
6434
|
+
? "old"
|
|
6435
|
+
: undefined;
|
|
6436
|
+
if (opposite &&
|
|
6437
|
+
explicitlyAnimated.has(`${name.layer}:${opposite}`) &&
|
|
6438
|
+
!opacityAnimated.has(`${name.layer}:${opposite}`)) {
|
|
6439
|
+
animation.cancel();
|
|
6440
|
+
continue;
|
|
6441
|
+
}
|
|
6369
6442
|
/**
|
|
6370
6443
|
* Otherwise retime the browser-generated animation to
|
|
6371
6444
|
* Motion's timing. This auto-enables the layout (group)
|
|
@@ -6390,36 +6463,85 @@
|
|
|
6390
6463
|
const isMorphCrossfade = (name.type === "old" || name.type === "new") &&
|
|
6391
6464
|
!!stagger?.old &&
|
|
6392
6465
|
!!stagger?.new;
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6466
|
+
let timing;
|
|
6467
|
+
if (name.type.startsWith("group")) {
|
|
6468
|
+
// group + group-children follow the resolved group
|
|
6469
|
+
// timing - the single source shared with the crop
|
|
6470
|
+
// corner-radius pass below.
|
|
6471
|
+
const { delay, duration, ease } = resolveGroupTiming(name.layer);
|
|
6472
|
+
timing = {
|
|
6473
|
+
delay,
|
|
6474
|
+
duration,
|
|
6475
|
+
easing: mapEasingToNativeEasing(ease, duration),
|
|
6476
|
+
};
|
|
6477
|
+
}
|
|
6478
|
+
else {
|
|
6479
|
+
const timingType = isMorphCrossfade ? "group" : name.type;
|
|
6480
|
+
const [index, total] = staggerPosition(name.layer, timingType);
|
|
6481
|
+
const transitionName = timingType === "group" ? "layout" : "";
|
|
6482
|
+
let animationTransition = resolveLayerTransition(targetDefinition, timingType, transitionName, index === -1 ? 0 : index, total);
|
|
6483
|
+
/**
|
|
6484
|
+
* The crossfade should resolve at the spring's
|
|
6485
|
+
* *perceptual* (visual) duration - the geometry can keep
|
|
6486
|
+
* bouncing, but the opacity shouldn't drag through the
|
|
6487
|
+
* settle. So capture `visualDuration` before
|
|
6488
|
+
* `applyGeneratorOptions` replaces it with the full
|
|
6489
|
+
* overshoot duration, and use it for the fade.
|
|
6490
|
+
*/
|
|
6491
|
+
const visualDuration = animationTransition.visualDuration;
|
|
6492
|
+
animationTransition.duration && (animationTransition.duration = secondsToMilliseconds(animationTransition.duration));
|
|
6493
|
+
animationTransition =
|
|
6494
|
+
applyGeneratorOptions(animationTransition);
|
|
6495
|
+
timing = {
|
|
6496
|
+
delay: secondsToMilliseconds(animationTransition.delay ?? 0),
|
|
6497
|
+
duration: isMorphCrossfade && visualDuration !== undefined
|
|
6498
|
+
? secondsToMilliseconds(visualDuration)
|
|
6499
|
+
: animationTransition.duration,
|
|
6500
|
+
easing: isMorphCrossfade
|
|
6501
|
+
? "linear"
|
|
6502
|
+
: mapEasingToNativeEasing(animationTransition.ease, animationTransition.duration),
|
|
6503
|
+
};
|
|
6504
|
+
}
|
|
6505
|
+
effect.updateTiming(timing);
|
|
6421
6506
|
animations.push(new NativeAnimationWrapper(animation));
|
|
6422
6507
|
}
|
|
6508
|
+
/**
|
|
6509
|
+
* Round each cropped layer's clip. Its `::view-transition-group`
|
|
6510
|
+
* has `overflow: clip`, which would otherwise square the corners
|
|
6511
|
+
* mid-morph; animate each corner from the old element's radius to
|
|
6512
|
+
* the new element's so the crop stays rounded. Timed as the group
|
|
6513
|
+
* (`layout`) so the radius tracks the morphing box.
|
|
6514
|
+
*/
|
|
6515
|
+
cropMeasurements.forEach((entry, name) => {
|
|
6516
|
+
if (!croppedNames.has(name))
|
|
6517
|
+
return;
|
|
6518
|
+
// Reuse the group's resolved timing - native ms delay/
|
|
6519
|
+
// duration + a baked ease, with no generator `type` or
|
|
6520
|
+
// repeat/times to leak into (or throw inside) NativeAnimation.
|
|
6521
|
+
const { delay, duration, ease } = resolveGroupTiming(name);
|
|
6522
|
+
for (const corner of cornerRadiusProps) {
|
|
6523
|
+
// `||` (not `??`) so an empty measurement falls back to
|
|
6524
|
+
// the other snapshot rather than an invalid keyframe.
|
|
6525
|
+
const from = entry.old?.radii[corner] ||
|
|
6526
|
+
entry.new?.radii[corner] ||
|
|
6527
|
+
"0px";
|
|
6528
|
+
const to = entry.new?.radii[corner] ||
|
|
6529
|
+
entry.old?.radii[corner] ||
|
|
6530
|
+
"0px";
|
|
6531
|
+
// Nothing to round if the corner is square at both ends.
|
|
6532
|
+
if (isSquareRadius(from) && isSquareRadius(to))
|
|
6533
|
+
continue;
|
|
6534
|
+
animations.push(new NativeAnimation({
|
|
6535
|
+
element: document.documentElement,
|
|
6536
|
+
name: corner,
|
|
6537
|
+
pseudoElement: `::view-transition-group(${name})`,
|
|
6538
|
+
keyframes: [from, to],
|
|
6539
|
+
delay,
|
|
6540
|
+
duration,
|
|
6541
|
+
ease,
|
|
6542
|
+
}));
|
|
6543
|
+
}
|
|
6544
|
+
});
|
|
6423
6545
|
resolve(new GroupAnimation(animations));
|
|
6424
6546
|
})
|
|
6425
6547
|
.catch(() =>
|
|
@@ -7799,12 +7921,7 @@
|
|
|
7799
7921
|
const scaleCorrectors = {
|
|
7800
7922
|
borderRadius: {
|
|
7801
7923
|
...correctBorderRadius,
|
|
7802
|
-
applyTo: [
|
|
7803
|
-
"borderTopLeftRadius",
|
|
7804
|
-
"borderTopRightRadius",
|
|
7805
|
-
"borderBottomLeftRadius",
|
|
7806
|
-
"borderBottomRightRadius",
|
|
7807
|
-
],
|
|
7924
|
+
applyTo: [...cornerRadiusProps],
|
|
7808
7925
|
},
|
|
7809
7926
|
borderTopLeftRadius: correctBorderRadius,
|
|
7810
7927
|
borderTopRightRadius: correctBorderRadius,
|
|
@@ -8732,13 +8849,7 @@
|
|
|
8732
8849
|
return transform || "none";
|
|
8733
8850
|
}
|
|
8734
8851
|
|
|
8735
|
-
const
|
|
8736
|
-
"borderTopLeftRadius",
|
|
8737
|
-
"borderTopRightRadius",
|
|
8738
|
-
"borderBottomLeftRadius",
|
|
8739
|
-
"borderBottomRightRadius",
|
|
8740
|
-
];
|
|
8741
|
-
const numBorders = borderLabels.length;
|
|
8852
|
+
const numBorders = cornerRadiusProps.length;
|
|
8742
8853
|
const asNumber = (value) => typeof value === "string" ? parseFloat(value) : value;
|
|
8743
8854
|
const isPx = (value) => typeof value === "number" || px.test(value);
|
|
8744
8855
|
function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
|
|
@@ -8753,7 +8864,7 @@
|
|
|
8753
8864
|
* Mix border radius
|
|
8754
8865
|
*/
|
|
8755
8866
|
for (let i = 0; i < numBorders; i++) {
|
|
8756
|
-
const borderLabel =
|
|
8867
|
+
const borderLabel = cornerRadiusProps[i];
|
|
8757
8868
|
let followRadius = getRadius(follow, borderLabel);
|
|
8758
8869
|
let leadRadius = getRadius(lead, borderLabel);
|
|
8759
8870
|
if (followRadius === undefined && leadRadius === undefined)
|