framer-motion 12.41.0 → 12.42.1
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 +207 -91
- package/dist/framer-motion.js +1 -1
- package/package.json +3 -3
|
@@ -5715,6 +5715,29 @@
|
|
|
5715
5715
|
element.style?.setProperty("view-transition-class", className);
|
|
5716
5716
|
classed.push(element);
|
|
5717
5717
|
}
|
|
5718
|
+
/**
|
|
5719
|
+
* Set the element's `view-transition-group` so its layer reconstructs the DOM
|
|
5720
|
+
* hierarchy in the pseudo-tree (`contain`) - or stays flat (`none`). Tracked in
|
|
5721
|
+
* `grouped` for cleanup. When the element clips (any non-`visible` overflow) its
|
|
5722
|
+
* name is recorded in `clipChildren` so the caller can clip the nested children
|
|
5723
|
+
* (`::view-transition-group-children(name)`), mirroring the live clip through
|
|
5724
|
+
* the whole transition rather than only at the live-DOM handoff.
|
|
5725
|
+
*
|
|
5726
|
+
* Ignored by browsers without nested view-transition groups, where it harmlessly
|
|
5727
|
+
* degrades to the flat default.
|
|
5728
|
+
*/
|
|
5729
|
+
function applyGroup(element, name, group, grouped, clipChildren) {
|
|
5730
|
+
if (!group)
|
|
5731
|
+
return;
|
|
5732
|
+
element.style?.setProperty("view-transition-group", group);
|
|
5733
|
+
grouped.push(element);
|
|
5734
|
+
if (group !== "none" && clipChildren) {
|
|
5735
|
+
const style = getComputedStyle(element);
|
|
5736
|
+
if (style.overflowX !== "visible" || style.overflowY !== "visible") {
|
|
5737
|
+
clipChildren.add(name);
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
}
|
|
5718
5741
|
/**
|
|
5719
5742
|
* Resolve a selector/Element to elements and ensure each one carries a
|
|
5720
5743
|
* `view-transition-name` we can target from script.
|
|
@@ -5728,7 +5751,7 @@
|
|
|
5728
5751
|
* across both captures (before and after the update), which is what allows a
|
|
5729
5752
|
* persistent element to animate as a single `group` layer.
|
|
5730
5753
|
*/
|
|
5731
|
-
function assignViewTransitionNames(definition, registry, assigned, forcedNames, className, classed = []) {
|
|
5754
|
+
function assignViewTransitionNames(definition, registry, assigned, forcedNames, className, classed = [], group, grouped = [], clipChildren) {
|
|
5732
5755
|
const elements = resolveElements(definition);
|
|
5733
5756
|
/**
|
|
5734
5757
|
* The new end of a paired morph: give each element the matching name from
|
|
@@ -5749,6 +5772,7 @@
|
|
|
5749
5772
|
assigned.push(element);
|
|
5750
5773
|
registry.set(element, name);
|
|
5751
5774
|
tagClass(element, className, classed);
|
|
5775
|
+
applyGroup(element, name, group, grouped, clipChildren);
|
|
5752
5776
|
return name;
|
|
5753
5777
|
});
|
|
5754
5778
|
}
|
|
@@ -5789,6 +5813,7 @@
|
|
|
5789
5813
|
}
|
|
5790
5814
|
registry.set(element, name);
|
|
5791
5815
|
tagClass(element, className, classed);
|
|
5816
|
+
applyGroup(element, name, group, grouped, clipChildren);
|
|
5792
5817
|
return name;
|
|
5793
5818
|
});
|
|
5794
5819
|
}
|
|
@@ -5798,13 +5823,16 @@
|
|
|
5798
5823
|
* (they're not in `assigned`). Safe to call more than once (e.g. on both a
|
|
5799
5824
|
* finished and an interrupted transition).
|
|
5800
5825
|
*/
|
|
5801
|
-
function releaseViewTransitionNames(assigned, classed = []) {
|
|
5826
|
+
function releaseViewTransitionNames(assigned, classed = [], grouped = []) {
|
|
5802
5827
|
for (const element of assigned) {
|
|
5803
5828
|
element.style?.removeProperty("view-transition-name");
|
|
5804
5829
|
}
|
|
5805
5830
|
for (const element of classed) {
|
|
5806
5831
|
element.style?.removeProperty("view-transition-class");
|
|
5807
5832
|
}
|
|
5833
|
+
for (const element of grouped) {
|
|
5834
|
+
element.style?.removeProperty("view-transition-group");
|
|
5835
|
+
}
|
|
5808
5836
|
}
|
|
5809
5837
|
|
|
5810
5838
|
function chooseLayerType(valueName) {
|
|
@@ -5889,14 +5917,14 @@
|
|
|
5889
5917
|
new: { opacity: 0, scale: 0.85 },
|
|
5890
5918
|
old: { opacity: 1, scale: 1 },
|
|
5891
5919
|
};
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5920
|
+
/**
|
|
5921
|
+
* How much two box aspect ratios must differ before a morph is treated as
|
|
5922
|
+
* aspect-changing (and so worth cropping). Matches the projection engine's
|
|
5923
|
+
* `preserve-aspect` threshold, so small layout jitter doesn't trigger a crop.
|
|
5924
|
+
*/
|
|
5925
|
+
const ASPECT_TOLERANCE = 0.2;
|
|
5898
5926
|
function startViewAnimation(builder) {
|
|
5899
|
-
const { update, targets, resolveDefs,
|
|
5927
|
+
const { update, targets, resolveDefs, cropOverride, pairs, classNames, flatGroups, options: defaultOptions, } = builder;
|
|
5900
5928
|
if (!document.startViewTransition) {
|
|
5901
5929
|
// An async IIFE (not `new Promise(async …)`) so a throwing/rejecting
|
|
5902
5930
|
// update rejects this promise rather than leaving it unsettled.
|
|
@@ -5920,8 +5948,21 @@
|
|
|
5920
5948
|
* ever stripping an author's own inline `view-transition-name`.
|
|
5921
5949
|
*/
|
|
5922
5950
|
const classed = [];
|
|
5951
|
+
/**
|
|
5952
|
+
* Elements we set a `view-transition-group` on (for nesting), tracked for
|
|
5953
|
+
* cleanup. `clipChildren` collects the names of nested parents that clip in
|
|
5954
|
+
* the live layout, so their `::view-transition-group-children` is clipped
|
|
5955
|
+
* through the transition - not just once the live DOM takes back over.
|
|
5956
|
+
*/
|
|
5957
|
+
const grouped = [];
|
|
5958
|
+
const clipChildren = new Set();
|
|
5923
5959
|
const layerTargets = new Map();
|
|
5924
5960
|
const croppedNames = new Set();
|
|
5961
|
+
/**
|
|
5962
|
+
* Each layer's explicit `.crop(true | false)` override (by resolved name),
|
|
5963
|
+
* so `finalizeCrop` can let an author's choice win over the morph default.
|
|
5964
|
+
*/
|
|
5965
|
+
const cropForName = new Map();
|
|
5925
5966
|
/**
|
|
5926
5967
|
* Each layer's stagger position (index + total) within its subject, per
|
|
5927
5968
|
* snapshot. Resolving against the snapshot the layer belongs to keeps
|
|
@@ -5943,6 +5984,18 @@
|
|
|
5943
5984
|
const resolveLayers = (phase) => {
|
|
5944
5985
|
targets.forEach((target, definition) => {
|
|
5945
5986
|
const className = classNames.get(definition);
|
|
5987
|
+
/**
|
|
5988
|
+
* Nest each resolved layer under its DOM-ancestor layer by default
|
|
5989
|
+
* (`contain`), so an ancestor's clip/transform/opacity reach it
|
|
5990
|
+
* through the transition; `.group(false)` opts a subject out (`none`)
|
|
5991
|
+
* to stay flat and escape. Skipped for root / pre-named layers, which
|
|
5992
|
+
* aren't elements we resolve and style.
|
|
5993
|
+
*/
|
|
5994
|
+
const group = definition === "root" || !resolveDefs.has(definition)
|
|
5995
|
+
? undefined
|
|
5996
|
+
: flatGroups.has(definition)
|
|
5997
|
+
? "none"
|
|
5998
|
+
: "contain";
|
|
5946
5999
|
let names;
|
|
5947
6000
|
if (definition === "root" || !resolveDefs.has(definition)) {
|
|
5948
6001
|
names = [definition];
|
|
@@ -5955,7 +6008,7 @@
|
|
|
5955
6008
|
*/
|
|
5956
6009
|
if (phase === "old") {
|
|
5957
6010
|
pairFrom.set(definition, resolveElements(definition));
|
|
5958
|
-
names = assignViewTransitionNames(definition, nameRegistry, assigned, undefined, className, classed);
|
|
6011
|
+
names = assignViewTransitionNames(definition, nameRegistry, assigned, undefined, className, classed, group, grouped, clipChildren);
|
|
5959
6012
|
pairNames.set(definition, names);
|
|
5960
6013
|
}
|
|
5961
6014
|
else {
|
|
@@ -5975,13 +6028,16 @@
|
|
|
5975
6028
|
*/
|
|
5976
6029
|
nameRegistry.delete(el);
|
|
5977
6030
|
}
|
|
5978
|
-
names = assignViewTransitionNames(pairs.get(definition), nameRegistry, assigned, pairNames.get(definition), className, classed);
|
|
6031
|
+
names = assignViewTransitionNames(pairs.get(definition), nameRegistry, assigned, pairNames.get(definition), className, classed, group, grouped, clipChildren);
|
|
5979
6032
|
}
|
|
5980
6033
|
}
|
|
5981
6034
|
else {
|
|
5982
|
-
names = assignViewTransitionNames(definition, nameRegistry, assigned, undefined, className, classed);
|
|
6035
|
+
names = assignViewTransitionNames(definition, nameRegistry, assigned, undefined, className, classed, group, grouped, clipChildren);
|
|
5983
6036
|
}
|
|
5984
|
-
|
|
6037
|
+
// Record any explicit `.crop(true | false)` per resolved name; the
|
|
6038
|
+
// crop set itself is computed later by `finalizeCrop` (it needs both
|
|
6039
|
+
// snapshots to know which morphs change aspect ratio).
|
|
6040
|
+
const override = cropOverride.get(definition);
|
|
5985
6041
|
names.forEach((name, index) => {
|
|
5986
6042
|
/**
|
|
5987
6043
|
* If two subjects resolve to the same element, merge their
|
|
@@ -5991,8 +6047,8 @@
|
|
|
5991
6047
|
layerTargets.set(name, existing && existing !== target
|
|
5992
6048
|
? { ...existing, ...target }
|
|
5993
6049
|
: target);
|
|
5994
|
-
if (
|
|
5995
|
-
|
|
6050
|
+
if (override !== undefined)
|
|
6051
|
+
cropForName.set(name, override);
|
|
5996
6052
|
const stagger = layerStagger.get(name) ?? {};
|
|
5997
6053
|
stagger[phase] = [index, names.length];
|
|
5998
6054
|
layerStagger.set(name, stagger);
|
|
@@ -6027,44 +6083,62 @@
|
|
|
6027
6083
|
return transition;
|
|
6028
6084
|
};
|
|
6029
6085
|
/**
|
|
6030
|
-
*
|
|
6031
|
-
*
|
|
6032
|
-
* shorthand) so mismatched/individual radii interpolate cleanly.
|
|
6086
|
+
* Each layer's box size per snapshot, so `finalizeCrop` can tell whether a
|
|
6087
|
+
* morph's aspect ratio changed (the only case worth cropping).
|
|
6033
6088
|
*/
|
|
6034
|
-
const
|
|
6035
|
-
const
|
|
6036
|
-
const
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
};
|
|
6089
|
+
const cropBox = new Map();
|
|
6090
|
+
const measureLayers = (phase) => nameRegistry.forEach((name, element) => {
|
|
6091
|
+
const rect = element.getBoundingClientRect?.();
|
|
6092
|
+
if (rect && rect.height) {
|
|
6093
|
+
const entry = cropBox.get(name) ?? {};
|
|
6094
|
+
entry[phase] = { width: rect.width, height: rect.height };
|
|
6095
|
+
cropBox.set(name, entry);
|
|
6096
|
+
}
|
|
6097
|
+
});
|
|
6043
6098
|
/**
|
|
6044
|
-
*
|
|
6045
|
-
*
|
|
6046
|
-
*
|
|
6047
|
-
*
|
|
6099
|
+
* With both snapshots measured, settle which layers crop. The default crops
|
|
6100
|
+
* only a morph whose aspect ratio *changes* between snapshots - the one case
|
|
6101
|
+
* where `object-fit: cover` does real work. A same-aspect morph or a
|
|
6102
|
+
* fade-only layer is left uncropped: its corners scale naturally (no flash
|
|
6103
|
+
* from squaring, no `overflow: clip` eating its shadow) and a backdrop can't
|
|
6104
|
+
* be clipped to nothing. An explicit `.crop(true | false)` overrides either
|
|
6105
|
+
* way. Runs after both snapshots are measured, since aspect needs both.
|
|
6048
6106
|
*/
|
|
6049
|
-
const
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6107
|
+
const finalizeCrop = () => {
|
|
6108
|
+
croppedNames.clear();
|
|
6109
|
+
for (const name of layerStagger.keys()) {
|
|
6110
|
+
if (name === "root")
|
|
6111
|
+
continue;
|
|
6112
|
+
// An explicit `.crop(true | false)` wins; otherwise crop a morph
|
|
6113
|
+
// whose aspect ratio changed.
|
|
6114
|
+
if (cropForName.get(name) ?? aspectChanged(name)) {
|
|
6115
|
+
croppedNames.add(name);
|
|
6055
6116
|
}
|
|
6056
|
-
}
|
|
6117
|
+
}
|
|
6118
|
+
};
|
|
6119
|
+
/**
|
|
6120
|
+
* Whether a layer is a morph whose box aspect ratio changed between
|
|
6121
|
+
* snapshots (beyond a small tolerance). Fade-only layers (one snapshot) are
|
|
6122
|
+
* never "changed".
|
|
6123
|
+
*/
|
|
6124
|
+
const aspectChanged = (name) => {
|
|
6125
|
+
const box = cropBox.get(name);
|
|
6126
|
+
if (!box?.old || !box?.new || !box.old.height || !box.new.height) {
|
|
6127
|
+
return false;
|
|
6128
|
+
}
|
|
6129
|
+
return (Math.abs(box.old.width / box.old.height -
|
|
6130
|
+
box.new.width / box.new.height) > ASPECT_TOLERANCE);
|
|
6057
6131
|
};
|
|
6058
6132
|
/**
|
|
6059
6133
|
* Write the persistent view-transition CSS: suppress root capture when the
|
|
6060
6134
|
* root has no animations of its own; force linear timing (baked into the
|
|
6061
6135
|
* keyframes, so we can retime later via updateTiming); and clip +
|
|
6062
6136
|
* object-fit: cover every cropped morph (the UA default overflows on
|
|
6063
|
-
* aspect-ratio change)
|
|
6137
|
+
* aspect-ratio change).
|
|
6064
6138
|
*
|
|
6065
6139
|
* `css.commit` replaces rather than appends, so we re-set the full rule set
|
|
6066
|
-
* each call - the
|
|
6067
|
-
*
|
|
6140
|
+
* each call - the crop rules are only known after `finalizeCrop` runs in the
|
|
6141
|
+
* update callback, so the second call writes them.
|
|
6068
6142
|
*/
|
|
6069
6143
|
const commitViewCSS = () => {
|
|
6070
6144
|
if (!hasTarget("root", targets)) {
|
|
@@ -6075,33 +6149,49 @@
|
|
|
6075
6149
|
css.set(`::view-transition-group(${name})`, { overflow: "clip" });
|
|
6076
6150
|
css.set(`::view-transition-old(${name}), ::view-transition-new(${name})`, { width: "100%", height: "100%", "object-fit": "cover" });
|
|
6077
6151
|
});
|
|
6152
|
+
/**
|
|
6153
|
+
* Clip the nested children of any layer that clips in the live layout,
|
|
6154
|
+
* so a wrapper crops its child for the whole morph (mirroring the DOM)
|
|
6155
|
+
* rather than only at the live-DOM handoff. No-op on browsers without
|
|
6156
|
+
* nested view-transition groups.
|
|
6157
|
+
*/
|
|
6158
|
+
clipChildren.forEach((name) => {
|
|
6159
|
+
css.set(`::view-transition-group-children(${name})`, {
|
|
6160
|
+
overflow: "clip",
|
|
6161
|
+
});
|
|
6162
|
+
});
|
|
6078
6163
|
css.commit(); // Write
|
|
6079
6164
|
};
|
|
6080
6165
|
const cleanup = () => {
|
|
6081
|
-
releaseViewTransitionNames(assigned, classed);
|
|
6166
|
+
releaseViewTransitionNames(assigned, classed, grouped);
|
|
6082
6167
|
css.remove(); // Write
|
|
6083
6168
|
};
|
|
6084
6169
|
const callback = async () => {
|
|
6085
6170
|
await update();
|
|
6086
6171
|
/**
|
|
6087
6172
|
* Re-resolve so elements created by the update are named for the new
|
|
6088
|
-
* snapshot, then measure
|
|
6173
|
+
* snapshot, then measure them. With both snapshots measured we can
|
|
6174
|
+
* settle the crop set (aspect-changing morphs + forced).
|
|
6089
6175
|
*/
|
|
6090
|
-
const croppedBefore = croppedNames.size;
|
|
6091
6176
|
resolveLayers("new");
|
|
6092
|
-
|
|
6177
|
+
measureLayers("new");
|
|
6178
|
+
finalizeCrop();
|
|
6093
6179
|
/**
|
|
6094
|
-
* Re-commit the crop CSS
|
|
6095
|
-
*
|
|
6096
|
-
*
|
|
6180
|
+
* Re-commit the crop CSS unconditionally: `finalizeCrop` is computed
|
|
6181
|
+
* here (after both snapshots are measured), so the clip rules must be
|
|
6182
|
+
* (re)written to match the settled set.
|
|
6097
6183
|
*/
|
|
6098
|
-
|
|
6099
|
-
commitViewCSS();
|
|
6184
|
+
commitViewCSS();
|
|
6100
6185
|
};
|
|
6101
6186
|
let transition;
|
|
6102
6187
|
try {
|
|
6103
6188
|
resolveLayers("old");
|
|
6104
|
-
|
|
6189
|
+
/**
|
|
6190
|
+
* Measure the old snapshot against the optimistic crop set (the new
|
|
6191
|
+
* snapshot doesn't exist yet, so aspect change can't be known here;
|
|
6192
|
+
* `finalizeCrop` settles it post-update).
|
|
6193
|
+
*/
|
|
6194
|
+
measureLayers("old");
|
|
6105
6195
|
commitViewCSS();
|
|
6106
6196
|
transition = document.startViewTransition(callback);
|
|
6107
6197
|
}
|
|
@@ -6276,6 +6366,31 @@
|
|
|
6276
6366
|
}
|
|
6277
6367
|
continue;
|
|
6278
6368
|
}
|
|
6369
|
+
/**
|
|
6370
|
+
* Drop the orphaned half of the default crossfade. The UA
|
|
6371
|
+
* fades old out and new in as a *pair*; if the opposing half
|
|
6372
|
+
* was explicitly overridden with something other than an
|
|
6373
|
+
* opacity fade (a clip or transform reveal), this side's
|
|
6374
|
+
* default opacity fade has no partner - left to run it would
|
|
6375
|
+
* dissolve what should be a static backdrop (e.g.
|
|
6376
|
+
* `.new({ clipPath })` should reveal over a still old view,
|
|
6377
|
+
* not fade the old out around the growing clip). Cancel it -
|
|
6378
|
+
* and its `plus-lighter` sibling on the same pseudo, which
|
|
6379
|
+
* would otherwise flash bright where the two opaque layers
|
|
6380
|
+
* overlap. A genuine crossfade (the opposing side also fading
|
|
6381
|
+
* opacity) keeps both halves and is handled above.
|
|
6382
|
+
*/
|
|
6383
|
+
const opposite = name.type === "old"
|
|
6384
|
+
? "new"
|
|
6385
|
+
: name.type === "new"
|
|
6386
|
+
? "old"
|
|
6387
|
+
: undefined;
|
|
6388
|
+
if (opposite &&
|
|
6389
|
+
explicitlyAnimated.has(`${name.layer}:${opposite}`) &&
|
|
6390
|
+
!opacityAnimated.has(`${name.layer}:${opposite}`)) {
|
|
6391
|
+
animation.cancel();
|
|
6392
|
+
continue;
|
|
6393
|
+
}
|
|
6279
6394
|
/**
|
|
6280
6395
|
* Otherwise retime the browser-generated animation to
|
|
6281
6396
|
* Motion's timing. This auto-enables the layout (group)
|
|
@@ -6330,38 +6445,6 @@
|
|
|
6330
6445
|
});
|
|
6331
6446
|
animations.push(new NativeAnimationWrapper(animation));
|
|
6332
6447
|
}
|
|
6333
|
-
/**
|
|
6334
|
-
* Animate each cropped layer's clip corners between the old and
|
|
6335
|
-
* new elements, so a cropped morph keeps rounded corners
|
|
6336
|
-
* (handling individual per-corner radii).
|
|
6337
|
-
*/
|
|
6338
|
-
cropRadii.forEach((radii, name) => {
|
|
6339
|
-
if (!radii.old && !radii.new)
|
|
6340
|
-
return;
|
|
6341
|
-
const target = layerTargets.get(name);
|
|
6342
|
-
const [index, total] = staggerPosition(name, "group");
|
|
6343
|
-
const radiusOptions = resolveLayerTransition(target, "group", "layout", index === -1 ? 0 : index, total);
|
|
6344
|
-
radiusOptions.duration && (radiusOptions.duration = secondsToMilliseconds(radiusOptions.duration));
|
|
6345
|
-
radiusOptions.delay && (radiusOptions.delay = secondsToMilliseconds(radiusOptions.delay));
|
|
6346
|
-
for (const corner of cornerProps) {
|
|
6347
|
-
// `||` (not `??`) so an empty measurement (e.g. an
|
|
6348
|
-
// un-rendered element) falls back rather than producing
|
|
6349
|
-
// an invalid keyframe.
|
|
6350
|
-
const from = radii.old?.[corner] || radii.new?.[corner] || "0px";
|
|
6351
|
-
const to = radii.new?.[corner] || radii.old?.[corner] || "0px";
|
|
6352
|
-
// Skip square corners - nothing to round.
|
|
6353
|
-
if (parseFloat(from) === 0 && parseFloat(to) === 0) {
|
|
6354
|
-
continue;
|
|
6355
|
-
}
|
|
6356
|
-
animations.push(new NativeAnimation({
|
|
6357
|
-
...radiusOptions,
|
|
6358
|
-
element: document.documentElement,
|
|
6359
|
-
name: corner,
|
|
6360
|
-
pseudoElement: `::view-transition-group(${name})`,
|
|
6361
|
-
keyframes: [from, to],
|
|
6362
|
-
}));
|
|
6363
|
-
}
|
|
6364
|
-
});
|
|
6365
6448
|
resolve(new GroupAnimation(animations));
|
|
6366
6449
|
})
|
|
6367
6450
|
.catch(() =>
|
|
@@ -6471,10 +6554,12 @@
|
|
|
6471
6554
|
*/
|
|
6472
6555
|
this.resolveDefs = new Set();
|
|
6473
6556
|
/**
|
|
6474
|
-
*
|
|
6475
|
-
* animated corner radii)
|
|
6557
|
+
* Per-subject crop override: `true` forces the crop (clip + object-fit:
|
|
6558
|
+
* cover + animated corner radii) on, `false` forces it off. A subject with
|
|
6559
|
+
* no entry uses the default - crop only a genuine morph (a layer present in
|
|
6560
|
+
* both snapshots), so a fade-only enter/exit isn't clipped to nothing.
|
|
6476
6561
|
*/
|
|
6477
|
-
this.
|
|
6562
|
+
this.cropOverride = new Map();
|
|
6478
6563
|
/**
|
|
6479
6564
|
* Subjects paired with a different new-snapshot target (the second `.add()`
|
|
6480
6565
|
* argument), so two distinct elements share one name and morph into each
|
|
@@ -6487,6 +6572,14 @@
|
|
|
6487
6572
|
* the opaque generated name.
|
|
6488
6573
|
*/
|
|
6489
6574
|
this.classNames = new Map();
|
|
6575
|
+
/**
|
|
6576
|
+
* Subjects opted out of automatic group nesting via `.group(false)`. Their
|
|
6577
|
+
* layer stays a flat top-level group (`view-transition-group: none`) instead
|
|
6578
|
+
* of nesting under its DOM-ancestor layer - so it animates independently and
|
|
6579
|
+
* escapes an ancestor's clip/transform (e.g. an element that lifts out of a
|
|
6580
|
+
* card and flies across, which nesting would clip to the card).
|
|
6581
|
+
*/
|
|
6582
|
+
this.flatGroups = new Set();
|
|
6490
6583
|
this.notifyReady = noop;
|
|
6491
6584
|
this.notifyReject = noop;
|
|
6492
6585
|
this.readyPromise = new Promise((resolve, reject) => {
|
|
@@ -6524,14 +6617,37 @@
|
|
|
6524
6617
|
return this;
|
|
6525
6618
|
}
|
|
6526
6619
|
/**
|
|
6527
|
-
*
|
|
6528
|
-
*
|
|
6529
|
-
*
|
|
6620
|
+
* Control this subject's crop (clip + `object-fit: cover` + animated
|
|
6621
|
+
* corners). By default a subject auto-crops only when it actually morphs -
|
|
6622
|
+
* present in both snapshots (a survivor, or an `.add(a, b)` pair). A
|
|
6623
|
+
* fade-only enter/exit has no second box to crop against, so it's left to
|
|
6624
|
+
* the browser default; in particular the `overflow: clip` a crop adds would
|
|
6625
|
+
* otherwise clip a mis-sized enter/exit layer to nothing.
|
|
6626
|
+
*
|
|
6627
|
+
* `.crop(false)` forces the crop off (e.g. a text morph, where
|
|
6628
|
+
* `object-fit: cover` clips glyphs as the box grows); `.crop(true)` forces
|
|
6629
|
+
* it on for a non-morph the default wouldn't otherwise crop.
|
|
6530
6630
|
*/
|
|
6531
6631
|
crop(enabled = true) {
|
|
6632
|
+
this.cropOverride.set(this.currentSubject, enabled);
|
|
6633
|
+
return this;
|
|
6634
|
+
}
|
|
6635
|
+
/**
|
|
6636
|
+
* By default a subject's layer nests under its nearest DOM-ancestor layer
|
|
6637
|
+
* (`view-transition-group: contain`), so the ancestor's clip/transform/opacity
|
|
6638
|
+
* apply to it through the transition - mirroring how the DOM actually paints,
|
|
6639
|
+
* and letting a wrapper crop its child for the whole morph rather than only
|
|
6640
|
+
* once the live DOM takes back over. (Needs a browser that supports nested
|
|
6641
|
+
* view-transition groups; elsewhere it degrades to the flat default.)
|
|
6642
|
+
*
|
|
6643
|
+
* Call `.group(false)` to opt out: the layer stays flat and top-level, so it
|
|
6644
|
+
* animates independently and escapes an ancestor's clip - e.g. an element
|
|
6645
|
+
* that should lift out of a card and fly across, which nesting would clip.
|
|
6646
|
+
*/
|
|
6647
|
+
group(enabled = true) {
|
|
6532
6648
|
enabled
|
|
6533
|
-
? this.
|
|
6534
|
-
: this.
|
|
6649
|
+
? this.flatGroups.delete(this.currentSubject)
|
|
6650
|
+
: this.flatGroups.add(this.currentSubject);
|
|
6535
6651
|
return this;
|
|
6536
6652
|
}
|
|
6537
6653
|
/**
|