framer-motion 12.42.1 → 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 +140 -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
|
}
|
|
@@ -6415,36 +6463,85 @@
|
|
|
6415
6463
|
const isMorphCrossfade = (name.type === "old" || name.type === "new") &&
|
|
6416
6464
|
!!stagger?.old &&
|
|
6417
6465
|
!!stagger?.new;
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
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);
|
|
6446
6506
|
animations.push(new NativeAnimationWrapper(animation));
|
|
6447
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
|
+
});
|
|
6448
6545
|
resolve(new GroupAnimation(animations));
|
|
6449
6546
|
})
|
|
6450
6547
|
.catch(() =>
|
|
@@ -7824,12 +7921,7 @@
|
|
|
7824
7921
|
const scaleCorrectors = {
|
|
7825
7922
|
borderRadius: {
|
|
7826
7923
|
...correctBorderRadius,
|
|
7827
|
-
applyTo: [
|
|
7828
|
-
"borderTopLeftRadius",
|
|
7829
|
-
"borderTopRightRadius",
|
|
7830
|
-
"borderBottomLeftRadius",
|
|
7831
|
-
"borderBottomRightRadius",
|
|
7832
|
-
],
|
|
7924
|
+
applyTo: [...cornerRadiusProps],
|
|
7833
7925
|
},
|
|
7834
7926
|
borderTopLeftRadius: correctBorderRadius,
|
|
7835
7927
|
borderTopRightRadius: correctBorderRadius,
|
|
@@ -8757,13 +8849,7 @@
|
|
|
8757
8849
|
return transform || "none";
|
|
8758
8850
|
}
|
|
8759
8851
|
|
|
8760
|
-
const
|
|
8761
|
-
"borderTopLeftRadius",
|
|
8762
|
-
"borderTopRightRadius",
|
|
8763
|
-
"borderBottomLeftRadius",
|
|
8764
|
-
"borderBottomRightRadius",
|
|
8765
|
-
];
|
|
8766
|
-
const numBorders = borderLabels.length;
|
|
8852
|
+
const numBorders = cornerRadiusProps.length;
|
|
8767
8853
|
const asNumber = (value) => typeof value === "string" ? parseFloat(value) : value;
|
|
8768
8854
|
const isPx = (value) => typeof value === "number" || px.test(value);
|
|
8769
8855
|
function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
|
|
@@ -8778,7 +8864,7 @@
|
|
|
8778
8864
|
* Mix border radius
|
|
8779
8865
|
*/
|
|
8780
8866
|
for (let i = 0; i < numBorders; i++) {
|
|
8781
|
-
const borderLabel =
|
|
8867
|
+
const borderLabel = cornerRadiusProps[i];
|
|
8782
8868
|
let followRadius = getRadius(follow, borderLabel);
|
|
8783
8869
|
let leadRadius = getRadius(lead, borderLabel);
|
|
8784
8870
|
if (followRadius === undefined && leadRadius === undefined)
|