motion 14.6.0 → 14.7.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/motion.dev.js +1332 -780
- package/dist/motion.js +1 -1
- package/package.json +4 -3
package/dist/motion.dev.js
CHANGED
|
@@ -299,13 +299,17 @@
|
|
|
299
299
|
const easingDefinitionToFunction = (definition) => {
|
|
300
300
|
if (isBezierDefinition(definition)) {
|
|
301
301
|
// If cubic bezier definition, create bezier curve
|
|
302
|
-
|
|
302
|
+
typeof process !== "undefined" &&
|
|
303
|
+
"development" !== "production" &&
|
|
304
|
+
exports.invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`, "cubic-bezier-length");
|
|
303
305
|
const [x1, y1, x2, y2] = definition;
|
|
304
306
|
return cubicBezier(x1, y1, x2, y2);
|
|
305
307
|
}
|
|
306
308
|
else if (isValidEasing(definition)) {
|
|
307
309
|
// Else lookup from table
|
|
308
|
-
|
|
310
|
+
typeof process !== "undefined" &&
|
|
311
|
+
"development" !== "production" &&
|
|
312
|
+
exports.invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`, "invalid-easing-type");
|
|
309
313
|
return easingLookup[definition];
|
|
310
314
|
}
|
|
311
315
|
return definition;
|
|
@@ -442,34 +446,21 @@
|
|
|
442
446
|
")",
|
|
443
447
|
};
|
|
444
448
|
|
|
449
|
+
/**
|
|
450
|
+
* #FF0000 has two characters per channel, #F00 one, which is doubled.
|
|
451
|
+
*/
|
|
452
|
+
const channel = (v, i, size) => {
|
|
453
|
+
const c = v.substring(1 + i * size, 1 + (i + 1) * size);
|
|
454
|
+
return size > 1 ? c : c + c;
|
|
455
|
+
};
|
|
445
456
|
function parseHex(v) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
let b = "";
|
|
449
|
-
let a = "";
|
|
450
|
-
// If we have 6 characters, ie #FF0000
|
|
451
|
-
if (v.length > 5) {
|
|
452
|
-
r = v.substring(1, 3);
|
|
453
|
-
g = v.substring(3, 5);
|
|
454
|
-
b = v.substring(5, 7);
|
|
455
|
-
a = v.substring(7, 9);
|
|
456
|
-
// Or we have 3 characters, ie #F00
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
r = v.substring(1, 2);
|
|
460
|
-
g = v.substring(2, 3);
|
|
461
|
-
b = v.substring(3, 4);
|
|
462
|
-
a = v.substring(4, 5);
|
|
463
|
-
r += r;
|
|
464
|
-
g += g;
|
|
465
|
-
b += b;
|
|
466
|
-
a += a;
|
|
467
|
-
}
|
|
457
|
+
const size = v.length > 5 ? 2 : 1;
|
|
458
|
+
const alpha = channel(v, 3, size);
|
|
468
459
|
return {
|
|
469
|
-
red: parseInt(
|
|
470
|
-
green: parseInt(
|
|
471
|
-
blue: parseInt(
|
|
472
|
-
alpha:
|
|
460
|
+
red: parseInt(channel(v, 0, size), 16),
|
|
461
|
+
green: parseInt(channel(v, 1, size), 16),
|
|
462
|
+
blue: parseInt(channel(v, 2, size), 16),
|
|
463
|
+
alpha: alpha ? parseInt(alpha, 16) / 255 : 1,
|
|
473
464
|
};
|
|
474
465
|
}
|
|
475
466
|
const hex = {
|
|
@@ -846,7 +837,6 @@
|
|
|
846
837
|
return orderedOrigin;
|
|
847
838
|
}
|
|
848
839
|
const mixComplex = (origin, target) => {
|
|
849
|
-
const template = complex.createTransformer(target);
|
|
850
840
|
const originStats = analyseComplexValue(origin);
|
|
851
841
|
const targetStats = analyseComplexValue(target);
|
|
852
842
|
const canInterpolate = originStats.indexes.var.length === targetStats.indexes.var.length &&
|
|
@@ -859,7 +849,7 @@
|
|
|
859
849
|
!originStats.values.length)) {
|
|
860
850
|
return mixVisibility(origin, target);
|
|
861
851
|
}
|
|
862
|
-
return pipe(mixArray(matchOrder(originStats, targetStats), targetStats.values),
|
|
852
|
+
return pipe(mixArray(matchOrder(originStats, targetStats), targetStats.values), buildTransformer(targetStats));
|
|
863
853
|
}
|
|
864
854
|
else {
|
|
865
855
|
exports.warning(true, `Complex values '${origin}' and '${target}' too different to mix. Ensure all colors are of the same type, and that each contains the same quantity of number and color values. Falling back to instant transition.`, "complex-values-different");
|
|
@@ -1151,37 +1141,31 @@
|
|
|
1151
1141
|
};
|
|
1152
1142
|
}
|
|
1153
1143
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
},
|
|
1172
|
-
// Limits
|
|
1173
|
-
minDuration: 0.01, // in seconds
|
|
1174
|
-
maxDuration: 10.0, // in seconds
|
|
1175
|
-
minDamping: 0.05,
|
|
1176
|
-
};
|
|
1144
|
+
// Default spring physics
|
|
1145
|
+
const defaultStiffness = 100;
|
|
1146
|
+
const defaultDamping = 10;
|
|
1147
|
+
const defaultMass = 1.0;
|
|
1148
|
+
// Default duration/bounce-based options
|
|
1149
|
+
const defaultDuration = 800; // in ms
|
|
1150
|
+
const defaultBounce = 0.3;
|
|
1151
|
+
const defaultVisualDuration = 0.3; // in seconds
|
|
1152
|
+
// Rest thresholds
|
|
1153
|
+
const restSpeedGranular = 0.01;
|
|
1154
|
+
const restSpeedDefault = 2;
|
|
1155
|
+
const restDeltaGranular = 0.005;
|
|
1156
|
+
const restDeltaDefault = 0.5;
|
|
1157
|
+
// Limits
|
|
1158
|
+
const minDuration = 0.01; // in seconds
|
|
1159
|
+
const maxDuration = 10.0; // in seconds
|
|
1160
|
+
const minDamping = 0.05;
|
|
1177
1161
|
/**
|
|
1178
1162
|
* Maps bounce to a damping ratio as SwiftUI does: 0 is critically damped, and
|
|
1179
1163
|
* it approaches undamped towards 1 and infinitely overdamped towards -1. Both
|
|
1180
1164
|
* ends are limited so the spring still moves and settles.
|
|
1181
1165
|
*/
|
|
1182
1166
|
const bounceToDampingRatio = (bounce) => bounce < 0
|
|
1183
|
-
? 1 / Math.max(1 + bounce,
|
|
1184
|
-
: Math.max(1 - bounce,
|
|
1167
|
+
? 1 / Math.max(1 + bounce, minDamping)
|
|
1168
|
+
: Math.max(1 - bounce, minDamping);
|
|
1185
1169
|
/**
|
|
1186
1170
|
* Scales an overdamped spring's undamped frequency so that, at its defining
|
|
1187
1171
|
* time, it has as far left to go as the critically damped spring that's
|
|
@@ -1224,12 +1208,12 @@
|
|
|
1224
1208
|
* ignore inherited velocity and only resolve without valid physics. Returns
|
|
1225
1209
|
* NaN physics when the root search doesn't converge.
|
|
1226
1210
|
*/
|
|
1227
|
-
function findSpring({ duration =
|
|
1211
|
+
function findSpring({ duration = defaultDuration, bounce = defaultBounce, }) {
|
|
1228
1212
|
let envelope;
|
|
1229
1213
|
let derivative;
|
|
1230
|
-
exports.warning(duration <= secondsToMilliseconds(
|
|
1214
|
+
exports.warning(duration <= secondsToMilliseconds(maxDuration), "Spring duration must be 10 seconds or less", "spring-duration-limit");
|
|
1231
1215
|
const dampingRatio = bounceToDampingRatio(bounce);
|
|
1232
|
-
duration = clamp(
|
|
1216
|
+
duration = clamp(minDuration, maxDuration, millisecondsToSeconds(duration));
|
|
1233
1217
|
if (dampingRatio < 1) {
|
|
1234
1218
|
/**
|
|
1235
1219
|
* Underdamped spring
|
|
@@ -1314,9 +1298,9 @@
|
|
|
1314
1298
|
const validMass = resolvePhysics(options.mass);
|
|
1315
1299
|
const springOptions = {
|
|
1316
1300
|
...options,
|
|
1317
|
-
stiffness: validStiffness ??
|
|
1318
|
-
damping: validDamping ??
|
|
1319
|
-
mass: validMass ??
|
|
1301
|
+
stiffness: validStiffness ?? defaultStiffness,
|
|
1302
|
+
damping: validDamping ?? defaultDamping,
|
|
1303
|
+
mass: validMass ?? defaultMass,
|
|
1320
1304
|
isResolvedFromDuration: false,
|
|
1321
1305
|
// stiffness/damping/mass overrides duration/bounce
|
|
1322
1306
|
isTimeDefined: (validStiffness ?? validDamping ?? validMass) === undefined &&
|
|
@@ -1344,13 +1328,13 @@
|
|
|
1344
1328
|
*/
|
|
1345
1329
|
if (!isValidPhysics(springOptions.stiffness) ||
|
|
1346
1330
|
!isValidPhysics(springOptions.damping, true)) {
|
|
1347
|
-
springOptions.stiffness =
|
|
1348
|
-
springOptions.damping =
|
|
1331
|
+
springOptions.stiffness = defaultStiffness;
|
|
1332
|
+
springOptions.damping = defaultDamping;
|
|
1349
1333
|
}
|
|
1350
1334
|
}
|
|
1351
1335
|
return springOptions;
|
|
1352
1336
|
}
|
|
1353
|
-
function springGenerator(optionsOrVisualDuration =
|
|
1337
|
+
function springGenerator(optionsOrVisualDuration = defaultVisualDuration, bounce = defaultBounce) {
|
|
1354
1338
|
const options = typeof optionsOrVisualDuration !== "object"
|
|
1355
1339
|
? {
|
|
1356
1340
|
visualDuration: optionsOrVisualDuration,
|
|
@@ -1400,14 +1384,10 @@
|
|
|
1400
1384
|
const isGranularScale = Math.abs(s.delta) < 5;
|
|
1401
1385
|
s.restSpeed =
|
|
1402
1386
|
options.restSpeed ||
|
|
1403
|
-
(isGranularScale
|
|
1404
|
-
? springDefaults.restSpeed.granular
|
|
1405
|
-
: springDefaults.restSpeed.default);
|
|
1387
|
+
(isGranularScale ? restSpeedGranular : restSpeedDefault);
|
|
1406
1388
|
s.restDelta =
|
|
1407
1389
|
options.restDelta ||
|
|
1408
|
-
(isGranularScale
|
|
1409
|
-
? springDefaults.restDelta.granular
|
|
1410
|
-
: springDefaults.restDelta.default);
|
|
1390
|
+
(isGranularScale ? restDeltaGranular : restDeltaDefault);
|
|
1411
1391
|
};
|
|
1412
1392
|
setRestThresholds();
|
|
1413
1393
|
let resolveSpring;
|
|
@@ -1476,11 +1456,16 @@
|
|
|
1476
1456
|
* dampedAngularFreq * t passes 300, both terms decay at the damping
|
|
1477
1457
|
* rate. Heavily overdamped drag springs (dragElastic: 0,
|
|
1478
1458
|
* dragTransition) rely on this to settle almost at once rather than
|
|
1479
|
-
* creep at stiffness / damping.
|
|
1480
|
-
*
|
|
1459
|
+
* creep at stiffness / damping. It only applies when reached within
|
|
1460
|
+
* 300ms (a dampedAngularFreq of 1 rad/ms), so it reads as a stiff
|
|
1461
|
+
* spring snapping into place: any later and it cuts off a slow spring
|
|
1462
|
+
* that's visibly still moving (#1207). Negative bounce needs the
|
|
1463
|
+
* exact curve, so time-defined springs are exempt.
|
|
1481
1464
|
*/
|
|
1482
|
-
const limit =
|
|
1483
|
-
|
|
1465
|
+
const limit = dampedAngularFreq < 1 || isTimeDefined
|
|
1466
|
+
? Infinity
|
|
1467
|
+
: 300 / dampedAngularFreq;
|
|
1468
|
+
const decayAt = (rate, t) => Math.exp(t > limit ? (decay - rate) * limit - decay * t : -rate * t);
|
|
1484
1469
|
const c = { S: 0, F: 0 };
|
|
1485
1470
|
update = () => {
|
|
1486
1471
|
const P = (s.velocity + decay * s.delta) / dampedAngularFreq;
|
|
@@ -1561,7 +1546,7 @@
|
|
|
1561
1546
|
};
|
|
1562
1547
|
let amplitude = power * velocity;
|
|
1563
1548
|
const ideal = origin + amplitude;
|
|
1564
|
-
const target = modifyTarget
|
|
1549
|
+
const target = modifyTarget ? modifyTarget(ideal) : ideal;
|
|
1565
1550
|
/**
|
|
1566
1551
|
* If the target has changed we need to re-calculate the amplitude, otherwise
|
|
1567
1552
|
* the animation will start from the wrong position.
|
|
@@ -1572,7 +1557,8 @@
|
|
|
1572
1557
|
const applyFriction = (t) => {
|
|
1573
1558
|
const delta = calcDelta(t);
|
|
1574
1559
|
state.done = Math.abs(delta) <= restDelta;
|
|
1575
|
-
|
|
1560
|
+
// target + delta can be off from origin by a float error at t = 0
|
|
1561
|
+
state.value = state.done ? target : t ? target + delta : origin;
|
|
1576
1562
|
};
|
|
1577
1563
|
/**
|
|
1578
1564
|
* Ideally this would resolve for t in a stateless way, we could
|
|
@@ -2018,17 +2004,12 @@
|
|
|
2018
2004
|
this.generator = generator;
|
|
2019
2005
|
}
|
|
2020
2006
|
updateTime(timestamp) {
|
|
2021
|
-
|
|
2022
|
-
//
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
// Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 =
|
|
2028
|
-
// 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for
|
|
2029
|
-
// example.
|
|
2030
|
-
this.currentTime = animationTime;
|
|
2031
|
-
}
|
|
2007
|
+
// Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 =
|
|
2008
|
+
// 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for
|
|
2009
|
+
// example.
|
|
2010
|
+
this.currentTime =
|
|
2011
|
+
this.holdTime ??
|
|
2012
|
+
Math.round(timestamp - this.startTime) * this.playbackSpeed;
|
|
2032
2013
|
}
|
|
2033
2014
|
tick(timestamp, sample = false) {
|
|
2034
2015
|
const { generator, totalDuration, calculatedDuration } = this;
|
|
@@ -2069,10 +2050,17 @@
|
|
|
2069
2050
|
}
|
|
2070
2051
|
const state = this.generate(this.currentTime, isInDelayPhase);
|
|
2071
2052
|
let { done } = state;
|
|
2072
|
-
|
|
2053
|
+
/**
|
|
2054
|
+
* A generator that takes longer than maxGeneratorDuration to finish
|
|
2055
|
+
* has an Infinity duration, so played forwards it finishes when it
|
|
2056
|
+
* says it's done.
|
|
2057
|
+
*/
|
|
2058
|
+
if (!isInDelayPhase) {
|
|
2073
2059
|
done =
|
|
2074
2060
|
this.playbackSpeed >= 0
|
|
2075
|
-
?
|
|
2061
|
+
? calculatedDuration < Infinity
|
|
2062
|
+
? this.currentTime >= totalDuration
|
|
2063
|
+
: done
|
|
2076
2064
|
: this.currentTime <= 0;
|
|
2077
2065
|
}
|
|
2078
2066
|
const isAnimationFinished = this.holdTime === null &&
|
|
@@ -2178,20 +2166,11 @@
|
|
|
2178
2166
|
}
|
|
2179
2167
|
return state;
|
|
2180
2168
|
}
|
|
2181
|
-
/**
|
|
2182
|
-
* Allows the returned animation to be awaited or promise-chained. Currently
|
|
2183
|
-
* resolves when the animation finishes at all but in a future update could/should
|
|
2184
|
-
* reject if its cancels.
|
|
2185
|
-
*/
|
|
2186
|
-
then(resolve, reject) {
|
|
2187
|
-
return this.finished.then(resolve, reject);
|
|
2188
|
-
}
|
|
2189
2169
|
get duration() {
|
|
2190
2170
|
return millisecondsToSeconds(this.calculatedDuration);
|
|
2191
2171
|
}
|
|
2192
2172
|
get iterationDuration() {
|
|
2193
|
-
|
|
2194
|
-
return this.duration + millisecondsToSeconds(delay);
|
|
2173
|
+
return this.duration + millisecondsToSeconds(this.options.delay ?? 0);
|
|
2195
2174
|
}
|
|
2196
2175
|
get time() {
|
|
2197
2176
|
return millisecondsToSeconds(this.currentTime);
|
|
@@ -2295,14 +2274,9 @@
|
|
|
2295
2274
|
}
|
|
2296
2275
|
teardown() {
|
|
2297
2276
|
this.state = "idle";
|
|
2298
|
-
this.
|
|
2299
|
-
this.startTime = this.holdTime = null;
|
|
2300
|
-
}
|
|
2301
|
-
stopDriver() {
|
|
2302
|
-
if (!this.driver)
|
|
2303
|
-
return;
|
|
2304
|
-
this.driver.stop();
|
|
2277
|
+
this.driver?.stop();
|
|
2305
2278
|
this.driver = undefined;
|
|
2279
|
+
this.startTime = this.holdTime = null;
|
|
2306
2280
|
}
|
|
2307
2281
|
sample(sampleTime) {
|
|
2308
2282
|
this.startTime = 0;
|
|
@@ -2559,23 +2533,15 @@
|
|
|
2559
2533
|
if (!transform || transform === "none") {
|
|
2560
2534
|
return defaultTransformValue(name);
|
|
2561
2535
|
}
|
|
2562
|
-
const
|
|
2563
|
-
let parsers;
|
|
2564
|
-
let match;
|
|
2565
|
-
if (matrix3dMatch) {
|
|
2566
|
-
parsers = matrix3dParsers;
|
|
2567
|
-
match = matrix3dMatch;
|
|
2568
|
-
}
|
|
2569
|
-
else {
|
|
2570
|
-
const matrix2dMatch = transform.match(/^matrix\(([-\d.e\s,]+)\)$/u);
|
|
2571
|
-
parsers = matrix2dParsers;
|
|
2572
|
-
match = matrix2dMatch;
|
|
2573
|
-
}
|
|
2536
|
+
const match = transform.match(/^matrix(3d)?\(([-\d.e\s,]+)\)$/u);
|
|
2574
2537
|
if (!match) {
|
|
2575
2538
|
return defaultTransformValue(name);
|
|
2576
2539
|
}
|
|
2577
|
-
const valueParser =
|
|
2578
|
-
|
|
2540
|
+
const valueParser = (match[1] ? matrix3dParsers : matrix2dParsers)[name];
|
|
2541
|
+
/**
|
|
2542
|
+
* parseFloat skips leading whitespace and stops at trailing whitespace.
|
|
2543
|
+
*/
|
|
2544
|
+
const values = match[2].split(",").map((v) => parseFloat(v));
|
|
2579
2545
|
return typeof valueParser === "function"
|
|
2580
2546
|
? valueParser(values)
|
|
2581
2547
|
: values[valueParser];
|
|
@@ -2584,9 +2550,6 @@
|
|
|
2584
2550
|
const { transform = "none" } = getComputedStyle(instance);
|
|
2585
2551
|
return parseValueFromTransform(transform, name);
|
|
2586
2552
|
};
|
|
2587
|
-
function convertTransformToNumber(value) {
|
|
2588
|
-
return parseFloat(value.trim());
|
|
2589
|
-
}
|
|
2590
2553
|
|
|
2591
2554
|
/**
|
|
2592
2555
|
* Generate a list of every possible transform key.
|
|
@@ -2989,7 +2952,9 @@
|
|
|
2989
2952
|
this.isPseudoElement = Boolean(pseudoElement);
|
|
2990
2953
|
this.allowFlatten = allowFlatten;
|
|
2991
2954
|
this.options = options;
|
|
2992
|
-
|
|
2955
|
+
typeof process !== "undefined" &&
|
|
2956
|
+
"development" !== "production" &&
|
|
2957
|
+
exports.invariant(typeof options.type !== "string", `Mini animate() doesn't support "type" as a string.`, "mini-spring");
|
|
2993
2958
|
const transition = applyGeneratorOptions(options);
|
|
2994
2959
|
this.animation = startWaapiAnimation(element, name, keyframes, transition, pseudoElement);
|
|
2995
2960
|
if (transition.autoplay === false) {
|
|
@@ -2999,9 +2964,7 @@
|
|
|
2999
2964
|
this.finishedTime = this.time;
|
|
3000
2965
|
if (!pseudoElement) {
|
|
3001
2966
|
const keyframe = getFinalKeyframe(keyframes, this.options, finalKeyframe, this.speed);
|
|
3002
|
-
|
|
3003
|
-
this.updateMotionValue(keyframe);
|
|
3004
|
-
}
|
|
2967
|
+
this.updateMotionValue?.(keyframe);
|
|
3005
2968
|
/**
|
|
3006
2969
|
* If we can, we want to commit the final style as set by the user,
|
|
3007
2970
|
* rather than the computed keyframe value supplied by the animation.
|
|
@@ -3322,6 +3285,7 @@
|
|
|
3322
3285
|
if (!isOriginAnimatable || !isTargetAnimatable) {
|
|
3323
3286
|
// Only build the message when we're going to warn
|
|
3324
3287
|
isOriginAnimatable !== isTargetAnimatable &&
|
|
3288
|
+
"development" !== "production" &&
|
|
3325
3289
|
exports.warning(false, `You are trying to animate ${name} from "${originKeyframe}" to "${targetKeyframe}". "${isOriginAnimatable ? targetKeyframe : originKeyframe}" is not an animatable value.`, "value-not-animatable");
|
|
3326
3290
|
return false;
|
|
3327
3291
|
}
|
|
@@ -3336,13 +3300,16 @@
|
|
|
3336
3300
|
|
|
3337
3301
|
/**
|
|
3338
3302
|
* A list of values that can be hardware-accelerated.
|
|
3303
|
+
*
|
|
3304
|
+
* Colors aren't included: WAAPI mixes them in sRGB, but JS animations
|
|
3305
|
+
* (and the sampler that estimates a WAAPI value on interrupt) mix them
|
|
3306
|
+
* in linear color space, so interrupting one would visibly jump.
|
|
3339
3307
|
*/
|
|
3340
3308
|
const acceleratedValues = new Set([
|
|
3341
3309
|
"opacity",
|
|
3342
3310
|
"clipPath",
|
|
3343
3311
|
"filter",
|
|
3344
3312
|
"transform",
|
|
3345
|
-
"backgroundColor",
|
|
3346
3313
|
]);
|
|
3347
3314
|
|
|
3348
3315
|
const browserColorFunctions = /^(?:oklch|oklab|lab|lch|color|color-mix|light-dark)\(/;
|
|
@@ -3434,6 +3401,20 @@
|
|
|
3434
3401
|
transformPerspective: "perspective",
|
|
3435
3402
|
};
|
|
3436
3403
|
const numTransforms = transformPropOrder.length;
|
|
3404
|
+
/**
|
|
3405
|
+
* Cache of `translateX(`-style openers so each transform function is a few
|
|
3406
|
+
* string concatenations rather than a template.
|
|
3407
|
+
*/
|
|
3408
|
+
const openers = {};
|
|
3409
|
+
/**
|
|
3410
|
+
* A transform function, e.g. `translateX(10px)` for `x: 10`.
|
|
3411
|
+
*/
|
|
3412
|
+
const transformFunction = (key, value, valueAsType = getValueAsType(value, transformValueTypes[key])) => (openers[key] || (openers[key] = (translateAlias[key] || key) + "(")) + valueAsType + ")";
|
|
3413
|
+
/**
|
|
3414
|
+
* Whether a transform value is at its default, so can be left out.
|
|
3415
|
+
*/
|
|
3416
|
+
const isDefaultTransform = (key, value) => (typeof value === "number" ? value : parseFloat(value)) ===
|
|
3417
|
+
(key.startsWith("scale") ? 1 : 0);
|
|
3437
3418
|
/**
|
|
3438
3419
|
* Build a CSS transform style from individual x/y/scale etc properties.
|
|
3439
3420
|
*
|
|
@@ -3443,7 +3424,6 @@
|
|
|
3443
3424
|
function buildTransform$1(latestValues, transform, transformTemplate) {
|
|
3444
3425
|
// The transform string we're going to build into.
|
|
3445
3426
|
let transformString = "";
|
|
3446
|
-
let transformIsDefault = true;
|
|
3447
3427
|
/**
|
|
3448
3428
|
* Loop over all possible transforms in order, adding the ones that
|
|
3449
3429
|
* are present to the transform string.
|
|
@@ -3453,24 +3433,11 @@
|
|
|
3453
3433
|
const value = latestValues[key];
|
|
3454
3434
|
if (value === undefined)
|
|
3455
3435
|
continue;
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
const parsed = parseFloat(value);
|
|
3462
|
-
valueIsDefault = key.startsWith("scale") ? parsed === 1 : parsed === 0;
|
|
3463
|
-
}
|
|
3464
|
-
if (!valueIsDefault || transformTemplate) {
|
|
3465
|
-
const valueAsType = getValueAsType(value, numberValueTypes[key]);
|
|
3466
|
-
if (!valueIsDefault) {
|
|
3467
|
-
transformIsDefault = false;
|
|
3468
|
-
const transformName = translateAlias[key] || key;
|
|
3469
|
-
transformString += `${transformName}(${valueAsType}) `;
|
|
3470
|
-
}
|
|
3471
|
-
if (transformTemplate) {
|
|
3472
|
-
transform[key] = valueAsType;
|
|
3473
|
-
}
|
|
3436
|
+
const valueAsType = transformTemplate &&
|
|
3437
|
+
(transform[key] = getValueAsType(value, numberValueTypes[key]));
|
|
3438
|
+
if (!isDefaultTransform(key, value)) {
|
|
3439
|
+
transformString +=
|
|
3440
|
+
transformFunction(key, value, valueAsType || undefined) + " ";
|
|
3474
3441
|
}
|
|
3475
3442
|
}
|
|
3476
3443
|
// `pathRotation` composes onto `rotate` as a separate additive term so
|
|
@@ -3478,19 +3445,14 @@
|
|
|
3478
3445
|
// `transformPropOrder`.
|
|
3479
3446
|
const pathRotation = latestValues.pathRotation;
|
|
3480
3447
|
if (pathRotation) {
|
|
3481
|
-
|
|
3482
|
-
transformString += `rotate(${getValueAsType(pathRotation, numberValueTypes.pathRotation)}) `;
|
|
3448
|
+
transformString += transformFunction("rotate", pathRotation);
|
|
3483
3449
|
}
|
|
3484
3450
|
transformString = transformString.trim();
|
|
3485
3451
|
// If we have a custom `transform` template, pass our transform values and
|
|
3486
3452
|
// generated transformString to that before returning
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
else if (transformIsDefault) {
|
|
3491
|
-
transformString = "none";
|
|
3492
|
-
}
|
|
3493
|
-
return transformString;
|
|
3453
|
+
return transformTemplate
|
|
3454
|
+
? transformTemplate(transform, transformString)
|
|
3455
|
+
: transformString || "none";
|
|
3494
3456
|
}
|
|
3495
3457
|
|
|
3496
3458
|
const transformGroups = new WeakMap();
|
|
@@ -3832,27 +3794,30 @@
|
|
|
3832
3794
|
return values;
|
|
3833
3795
|
};
|
|
3834
3796
|
/**
|
|
3835
|
-
* Build
|
|
3836
|
-
* is written, even at its default, so each keyframe has the
|
|
3837
|
-
* of functions and the browser interpolates them one by one.
|
|
3797
|
+
* Build a transform from values in transformPropOrder. For a keyframe,
|
|
3798
|
+
* every key is written, even at its default, so each keyframe has the
|
|
3799
|
+
* same list of functions and the browser interpolates them one by one.
|
|
3800
|
+
* As an inline style, defaults are left out, as buildTransform does.
|
|
3838
3801
|
*/
|
|
3839
|
-
const
|
|
3802
|
+
const toTransform = (values, isStyle) => {
|
|
3840
3803
|
let transform = "";
|
|
3841
|
-
const values = valuesAt(timestamp, settled);
|
|
3842
3804
|
for (const key in values) {
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
") ";
|
|
3805
|
+
const value = values[key];
|
|
3806
|
+
if (!isStyle || !isDefaultTransform(key, value)) {
|
|
3807
|
+
transform += transformFunction(key, value) + " ";
|
|
3808
|
+
}
|
|
3848
3809
|
}
|
|
3849
|
-
return transform || "none";
|
|
3810
|
+
return (isStyle ? transform.trim() : transform) || "none";
|
|
3850
3811
|
};
|
|
3812
|
+
/**
|
|
3813
|
+
* Build the transform at a timestamp.
|
|
3814
|
+
*/
|
|
3815
|
+
const compose = (timestamp, settled) => toTransform(valuesAt(timestamp, settled));
|
|
3851
3816
|
/**
|
|
3852
3817
|
* Write the transform as an inline style, as the renderer would.
|
|
3853
3818
|
*/
|
|
3854
3819
|
const commit = (values = valuesAt()) => {
|
|
3855
|
-
element.style.transform =
|
|
3820
|
+
element.style.transform = toTransform(values, true);
|
|
3856
3821
|
};
|
|
3857
3822
|
/**
|
|
3858
3823
|
* Cancel the WAAPI animations, by default first committing the
|
|
@@ -3863,6 +3828,13 @@
|
|
|
3863
3828
|
segments.forEach(({ animation }) => animation.cancel());
|
|
3864
3829
|
segments = [];
|
|
3865
3830
|
};
|
|
3831
|
+
/**
|
|
3832
|
+
* A WAAPI animation that add() starts by setting its startTime. Playing
|
|
3833
|
+
* it first, as element.animate() does, would leave WebKit holding that
|
|
3834
|
+
* play's ready time once startTime ends the play, and it uses that for
|
|
3835
|
+
* the next updatePlaybackRate(), so the animation would jump.
|
|
3836
|
+
*/
|
|
3837
|
+
const create = (keyframes, options) => new Animation(new KeyframeEffect(element, keyframes, options));
|
|
3866
3838
|
const add = (animation, offset, end = Infinity) => {
|
|
3867
3839
|
const segment = { animation, offset, end };
|
|
3868
3840
|
animation.startTime = time.now() + offset;
|
|
@@ -4055,7 +4027,7 @@
|
|
|
4055
4027
|
commit(valuesAt(from, settled));
|
|
4056
4028
|
keyframes = null;
|
|
4057
4029
|
}
|
|
4058
|
-
return
|
|
4030
|
+
return create(keyframes, {
|
|
4059
4031
|
...options,
|
|
4060
4032
|
duration: to - from,
|
|
4061
4033
|
easing: easing || "linear",
|
|
@@ -4145,9 +4117,7 @@
|
|
|
4145
4117
|
* empty animations that end with them.
|
|
4146
4118
|
*/
|
|
4147
4119
|
ends.forEach((trackEnd) => trackEnd < end - 1 &&
|
|
4148
|
-
add(
|
|
4149
|
-
duration: trackEnd - now,
|
|
4150
|
-
}), 0, trackEnd - now));
|
|
4120
|
+
add(create(null, { duration: trackEnd - now }), 0, trackEnd - now));
|
|
4151
4121
|
}
|
|
4152
4122
|
if (cycle) {
|
|
4153
4123
|
/**
|
|
@@ -4221,12 +4191,29 @@
|
|
|
4221
4191
|
if (isDone)
|
|
4222
4192
|
continue;
|
|
4223
4193
|
const local = elapsed - offset;
|
|
4224
|
-
if (rate
|
|
4194
|
+
if (rate &&
|
|
4195
|
+
animation.updatePlaybackRate &&
|
|
4196
|
+
animation.playState === "running" &&
|
|
4197
|
+
Math.abs(Number(animation.currentTime) - local) < 20) {
|
|
4198
|
+
/**
|
|
4199
|
+
* Only the speed changed. Setting playbackRate and
|
|
4200
|
+
* startTime restarts a running compositor animation, which
|
|
4201
|
+
* can render the underlying value for a frame.
|
|
4202
|
+
*/
|
|
4203
|
+
animation.playbackRate !== rate &&
|
|
4204
|
+
animation.updatePlaybackRate(rate);
|
|
4205
|
+
}
|
|
4206
|
+
else if (rate) {
|
|
4225
4207
|
animation.playbackRate = rate;
|
|
4226
4208
|
animation.startTime = now - local / rate;
|
|
4227
4209
|
}
|
|
4228
4210
|
else {
|
|
4229
|
-
|
|
4211
|
+
/**
|
|
4212
|
+
* Hold it without pause(), whose pending pause setting
|
|
4213
|
+
* currentTime would end at once, leaving WebKit holding its
|
|
4214
|
+
* ready time (see create()).
|
|
4215
|
+
*/
|
|
4216
|
+
animation.startTime = null;
|
|
4230
4217
|
animation.currentTime = local;
|
|
4231
4218
|
}
|
|
4232
4219
|
}
|
|
@@ -4373,6 +4360,7 @@
|
|
|
4373
4360
|
built = new Map();
|
|
4374
4361
|
stop();
|
|
4375
4362
|
tracks.forEach((track) => track.state === "running" && track.js?.start());
|
|
4363
|
+
return true;
|
|
4376
4364
|
},
|
|
4377
4365
|
};
|
|
4378
4366
|
transformGroups.set(element, group);
|
|
@@ -4417,9 +4405,8 @@
|
|
|
4417
4405
|
* If we can't animate this value with the resolved keyframes
|
|
4418
4406
|
* then we should complete it immediately.
|
|
4419
4407
|
*/
|
|
4420
|
-
|
|
4421
|
-
if (!
|
|
4422
|
-
canAnimateValue = false;
|
|
4408
|
+
const canAnimateValue = canAnimate(keyframes, name, type, velocity);
|
|
4409
|
+
if (!canAnimateValue) {
|
|
4423
4410
|
if (MotionGlobalConfig.instantAnimations || !delay) {
|
|
4424
4411
|
onUpdate?.(getFinalKeyframe(keyframes, options, finalKeyframe));
|
|
4425
4412
|
}
|
|
@@ -4563,9 +4550,7 @@
|
|
|
4563
4550
|
this.animation.complete();
|
|
4564
4551
|
}
|
|
4565
4552
|
cancel() {
|
|
4566
|
-
|
|
4567
|
-
this.animation.cancel();
|
|
4568
|
-
}
|
|
4553
|
+
this._animation?.cancel();
|
|
4569
4554
|
this.keyframeResolver?.cancel();
|
|
4570
4555
|
}
|
|
4571
4556
|
}
|
|
@@ -4748,7 +4733,7 @@
|
|
|
4748
4733
|
this.setCurrent(v);
|
|
4749
4734
|
// Update update subscribers
|
|
4750
4735
|
if (this.current !== this.prev) {
|
|
4751
|
-
this.
|
|
4736
|
+
this.dirty();
|
|
4752
4737
|
if (this.dependents) {
|
|
4753
4738
|
for (const dependent of this.dependents) {
|
|
4754
4739
|
dependent.dirty();
|
|
@@ -4886,12 +4871,9 @@
|
|
|
4886
4871
|
* @public
|
|
4887
4872
|
*/
|
|
4888
4873
|
set(v) {
|
|
4889
|
-
|
|
4890
|
-
this.
|
|
4891
|
-
|
|
4892
|
-
else {
|
|
4893
|
-
this.passiveEffect(v, this.updateAndNotify);
|
|
4894
|
-
}
|
|
4874
|
+
this.passiveEffect
|
|
4875
|
+
? this.passiveEffect(v, this.updateAndNotify)
|
|
4876
|
+
: this.updateAndNotify(v);
|
|
4895
4877
|
}
|
|
4896
4878
|
setWithVelocity(prev, current, delta) {
|
|
4897
4879
|
this.set(current);
|
|
@@ -4908,13 +4890,9 @@
|
|
|
4908
4890
|
this.prev = v;
|
|
4909
4891
|
this.prevUpdatedAt = this.prevFrameValue = undefined;
|
|
4910
4892
|
endAnimation && this.stop();
|
|
4911
|
-
|
|
4912
|
-
this.stopPassiveEffect();
|
|
4893
|
+
this.stopPassiveEffect?.();
|
|
4913
4894
|
}
|
|
4914
4895
|
dirty() {
|
|
4915
|
-
this.notifyChange();
|
|
4916
|
-
}
|
|
4917
|
-
notifyChange() {
|
|
4918
4896
|
const { current, changeSubscriber } = this;
|
|
4919
4897
|
/**
|
|
4920
4898
|
* One or the other exists. Subscribing during the direct
|
|
@@ -4929,15 +4907,10 @@
|
|
|
4929
4907
|
}
|
|
4930
4908
|
}
|
|
4931
4909
|
addDependent(dependent) {
|
|
4932
|
-
|
|
4933
|
-
this.dependents = new Set();
|
|
4934
|
-
}
|
|
4935
|
-
this.dependents.add(dependent);
|
|
4910
|
+
(this.dependents || (this.dependents = new Set())).add(dependent);
|
|
4936
4911
|
}
|
|
4937
4912
|
removeDependent(dependent) {
|
|
4938
|
-
|
|
4939
|
-
this.dependents.delete(dependent);
|
|
4940
|
-
}
|
|
4913
|
+
this.dependents?.delete(dependent);
|
|
4941
4914
|
}
|
|
4942
4915
|
/**
|
|
4943
4916
|
* Returns the latest state of `MotionValue`
|
|
@@ -4947,9 +4920,7 @@
|
|
|
4947
4920
|
* @public
|
|
4948
4921
|
*/
|
|
4949
4922
|
get() {
|
|
4950
|
-
|
|
4951
|
-
collectMotionValues.current.push(this);
|
|
4952
|
-
}
|
|
4923
|
+
collectMotionValues.current?.push(this);
|
|
4953
4924
|
/**
|
|
4954
4925
|
* A value animating on the compositor isn't updated every frame,
|
|
4955
4926
|
* so read it from its animation.
|
|
@@ -5015,7 +4986,7 @@
|
|
|
5015
4986
|
isComplete = true;
|
|
5016
4987
|
this.events.animationComplete?.notify();
|
|
5017
4988
|
if (this.animation === animation)
|
|
5018
|
-
this.
|
|
4989
|
+
this.animation = undefined;
|
|
5019
4990
|
resolve();
|
|
5020
4991
|
});
|
|
5021
4992
|
if (!isComplete)
|
|
@@ -5031,11 +5002,10 @@
|
|
|
5031
5002
|
stop() {
|
|
5032
5003
|
if (this.animation) {
|
|
5033
5004
|
this.animation.stop();
|
|
5034
|
-
|
|
5035
|
-
this.events.animationCancel.notify();
|
|
5036
|
-
}
|
|
5005
|
+
this.events.animationCancel?.notify();
|
|
5037
5006
|
}
|
|
5038
|
-
|
|
5007
|
+
// Assign rather than delete to keep the object's shape stable
|
|
5008
|
+
this.animation = undefined;
|
|
5039
5009
|
}
|
|
5040
5010
|
/**
|
|
5041
5011
|
* Returns `true` if this value is currently animating.
|
|
@@ -5045,10 +5015,6 @@
|
|
|
5045
5015
|
isAnimating() {
|
|
5046
5016
|
return !!this.animation;
|
|
5047
5017
|
}
|
|
5048
|
-
clearAnimation() {
|
|
5049
|
-
// Assign rather than delete to keep the object's shape stable
|
|
5050
|
-
this.animation = undefined;
|
|
5051
|
-
}
|
|
5052
5018
|
/**
|
|
5053
5019
|
* Destroy and clean up subscribers to this `MotionValue`.
|
|
5054
5020
|
*
|
|
@@ -5063,9 +5029,7 @@
|
|
|
5063
5029
|
this.events.destroy?.notify();
|
|
5064
5030
|
this.clearListeners();
|
|
5065
5031
|
this.stop();
|
|
5066
|
-
|
|
5067
|
-
this.stopPassiveEffect();
|
|
5068
|
-
}
|
|
5032
|
+
this.stopPassiveEffect?.();
|
|
5069
5033
|
}
|
|
5070
5034
|
}
|
|
5071
5035
|
function motionValue(init, options) {
|
|
@@ -5143,6 +5107,7 @@
|
|
|
5143
5107
|
"repeatDelay",
|
|
5144
5108
|
"from",
|
|
5145
5109
|
"elapsed",
|
|
5110
|
+
"out",
|
|
5146
5111
|
]);
|
|
5147
5112
|
/**
|
|
5148
5113
|
* Decide whether a transition is defined on a given Transition.
|
|
@@ -5613,7 +5578,7 @@
|
|
|
5613
5578
|
}
|
|
5614
5579
|
const valueTransition = {
|
|
5615
5580
|
delay,
|
|
5616
|
-
...getValueTransition$1(transition || {}, key),
|
|
5581
|
+
...getValueTransition$1(animationTypeState?.out[key] || transition || {}, key),
|
|
5617
5582
|
};
|
|
5618
5583
|
if (skipAnimations)
|
|
5619
5584
|
valueTransition.skipAnimations = true;
|
|
@@ -5783,8 +5748,9 @@
|
|
|
5783
5748
|
}
|
|
5784
5749
|
|
|
5785
5750
|
class DOMKeyframesResolver extends KeyframeResolver {
|
|
5786
|
-
constructor(
|
|
5787
|
-
super(
|
|
5751
|
+
constructor() {
|
|
5752
|
+
super(...arguments);
|
|
5753
|
+
this.isAsync = true;
|
|
5788
5754
|
}
|
|
5789
5755
|
/**
|
|
5790
5756
|
* Keyframes that don't need reading from the DOM, or measuring,
|
|
@@ -5812,7 +5778,7 @@
|
|
|
5812
5778
|
}
|
|
5813
5779
|
readKeyframes() {
|
|
5814
5780
|
const { unresolvedKeyframes, element, name } = this;
|
|
5815
|
-
if (!element
|
|
5781
|
+
if (!element?.current)
|
|
5816
5782
|
return;
|
|
5817
5783
|
super.readKeyframes();
|
|
5818
5784
|
/**
|
|
@@ -5907,11 +5873,10 @@
|
|
|
5907
5873
|
}
|
|
5908
5874
|
measureInitialState() {
|
|
5909
5875
|
const { element, unresolvedKeyframes, name } = this;
|
|
5910
|
-
if (!element
|
|
5876
|
+
if (!element?.current)
|
|
5911
5877
|
return;
|
|
5912
|
-
if (name === "height")
|
|
5878
|
+
if (name === "height")
|
|
5913
5879
|
this.suspendedScrollY = window.pageYOffset;
|
|
5914
|
-
}
|
|
5915
5880
|
this.measuredOrigin = this.measure();
|
|
5916
5881
|
unresolvedKeyframes[0] = this.measuredOrigin;
|
|
5917
5882
|
// Set final key frame to measure after next render
|
|
@@ -5922,7 +5887,7 @@
|
|
|
5922
5887
|
}
|
|
5923
5888
|
measureEndState() {
|
|
5924
5889
|
const { element, unresolvedKeyframes } = this;
|
|
5925
|
-
if (!element
|
|
5890
|
+
if (!element?.current)
|
|
5926
5891
|
return;
|
|
5927
5892
|
this.motionValue?.jump(this.measuredOrigin, false);
|
|
5928
5893
|
const finalKeyframeIndex = unresolvedKeyframes.length - 1;
|
|
@@ -5931,14 +5896,6 @@
|
|
|
5931
5896
|
if (finalKeyframe !== null && this.finalKeyframe === undefined) {
|
|
5932
5897
|
this.finalKeyframe = finalKeyframe;
|
|
5933
5898
|
}
|
|
5934
|
-
// If we removed transform values, reapply them before the next render
|
|
5935
|
-
if (this.removedTransforms?.length) {
|
|
5936
|
-
this.removedTransforms.forEach(([unsetTransformName, unsetTransformValue]) => {
|
|
5937
|
-
element
|
|
5938
|
-
.getValue(unsetTransformName)
|
|
5939
|
-
.set(unsetTransformValue);
|
|
5940
|
-
});
|
|
5941
|
-
}
|
|
5942
5899
|
this.resolveNoneKeyframes();
|
|
5943
5900
|
}
|
|
5944
5901
|
}
|
|
@@ -6043,7 +6000,8 @@
|
|
|
6043
6000
|
*/
|
|
6044
6001
|
const effects = [];
|
|
6045
6002
|
function addEffect(effect) {
|
|
6046
|
-
exports.invariant(typeof effect.test === "function" &&
|
|
6003
|
+
exports.invariant(typeof effect.test === "function" &&
|
|
6004
|
+
typeof effect.read === "function", "Effects passed to animate.addEffect() need test() and read().", "effect-missing-test");
|
|
6047
6005
|
removeEffect(effect);
|
|
6048
6006
|
effects.unshift(effect);
|
|
6049
6007
|
}
|
|
@@ -6370,11 +6328,6 @@
|
|
|
6370
6328
|
});
|
|
6371
6329
|
}
|
|
6372
6330
|
|
|
6373
|
-
/**
|
|
6374
|
-
* Cache of `translateX(`-style openers so each render is a few string
|
|
6375
|
-
* concatenations rather than a template per transform.
|
|
6376
|
-
*/
|
|
6377
|
-
const openers = {};
|
|
6378
6331
|
function buildTransform(state) {
|
|
6379
6332
|
let transform = "";
|
|
6380
6333
|
const { transformKeys: keys = [], transformValues: values = {} } = state;
|
|
@@ -6387,14 +6340,8 @@
|
|
|
6387
6340
|
const value = values[key].getCurrent();
|
|
6388
6341
|
if (value === undefined)
|
|
6389
6342
|
continue;
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
transform +=
|
|
6393
|
-
(transform && " ") +
|
|
6394
|
-
(openers[key] ||
|
|
6395
|
-
(openers[key] = (translateAlias[key] || key) + "(")) +
|
|
6396
|
-
getValueAsType(value, transformValueTypes[key]) +
|
|
6397
|
-
")";
|
|
6343
|
+
if (!isDefaultTransform(key, value)) {
|
|
6344
|
+
transform += (transform && " ") + transformFunction(key, value);
|
|
6398
6345
|
}
|
|
6399
6346
|
}
|
|
6400
6347
|
// See build-transform.ts: additive `rotate()` so user `rotate` isn't
|
|
@@ -6402,10 +6349,7 @@
|
|
|
6402
6349
|
const pathRotation = state.get("pathRotation")?.get();
|
|
6403
6350
|
if (pathRotation) {
|
|
6404
6351
|
transform +=
|
|
6405
|
-
(transform && " ") +
|
|
6406
|
-
"rotate(" +
|
|
6407
|
-
getValueAsType(pathRotation, transformValueTypes.pathRotation) +
|
|
6408
|
-
")";
|
|
6352
|
+
(transform && " ") + transformFunction("rotate", pathRotation);
|
|
6409
6353
|
}
|
|
6410
6354
|
return transform || "none";
|
|
6411
6355
|
}
|
|
@@ -6808,10 +6752,24 @@
|
|
|
6808
6752
|
!isIdentityScale(scaleX) ||
|
|
6809
6753
|
!isIdentityScale(scaleY));
|
|
6810
6754
|
}
|
|
6755
|
+
/**
|
|
6756
|
+
* Rotations and skews, which projection can't correct for.
|
|
6757
|
+
*/
|
|
6758
|
+
const angleTransforms = [
|
|
6759
|
+
"rotate",
|
|
6760
|
+
"rotateX",
|
|
6761
|
+
"rotateY",
|
|
6762
|
+
"rotateZ",
|
|
6763
|
+
"skewX",
|
|
6764
|
+
"skewY",
|
|
6765
|
+
];
|
|
6811
6766
|
function hasTransform(values) {
|
|
6812
6767
|
return (hasScale(values) ||
|
|
6813
6768
|
has2DTranslate(values) ||
|
|
6814
|
-
values
|
|
6769
|
+
hasDistortingTransform(values));
|
|
6770
|
+
}
|
|
6771
|
+
function hasDistortingTransform(values) {
|
|
6772
|
+
return (values.z ||
|
|
6815
6773
|
values.rotate ||
|
|
6816
6774
|
values.rotateX ||
|
|
6817
6775
|
values.rotateY ||
|
|
@@ -6864,6 +6822,11 @@
|
|
|
6864
6822
|
* in a tree upon our box before then calculating how to project it into our desired viewport-relative box
|
|
6865
6823
|
*
|
|
6866
6824
|
* This is the final nested loop within updateLayoutDelta for future refactoring
|
|
6825
|
+
*
|
|
6826
|
+
* isSharedTransition is true for a shared layout animation in page space, which
|
|
6827
|
+
* also applies ancestor scroll and transforms. Or it's the layoutRoot a shared
|
|
6828
|
+
* animation is relative to, so only transforms of ancestors not shared with it
|
|
6829
|
+
* (or above it) are applied.
|
|
6867
6830
|
*/
|
|
6868
6831
|
function applyTreeDeltas(box, treeScale, treePath, isSharedTransition = false) {
|
|
6869
6832
|
const treeLength = treePath.length;
|
|
@@ -6880,13 +6843,10 @@
|
|
|
6880
6843
|
* TODO: Prefer to remove this, but currently we have motion components with
|
|
6881
6844
|
* display: contents in Framer.
|
|
6882
6845
|
*/
|
|
6883
|
-
|
|
6884
|
-
if (visualElement &&
|
|
6885
|
-
visualElement.props.style &&
|
|
6886
|
-
visualElement.props.style.display === "contents") {
|
|
6846
|
+
if (node.options.visualElement?.props.style?.display === "contents") {
|
|
6887
6847
|
continue;
|
|
6888
6848
|
}
|
|
6889
|
-
if (isSharedTransition &&
|
|
6849
|
+
if (isSharedTransition === true &&
|
|
6890
6850
|
node.options.layoutScroll &&
|
|
6891
6851
|
node.scroll &&
|
|
6892
6852
|
node !== node.root) {
|
|
@@ -6900,7 +6860,9 @@
|
|
|
6900
6860
|
// Apply each ancestor's calculated delta into this component's recorded layout box
|
|
6901
6861
|
applyBoxDelta(box, delta);
|
|
6902
6862
|
}
|
|
6903
|
-
if (isSharedTransition &&
|
|
6863
|
+
if (isSharedTransition &&
|
|
6864
|
+
(isSharedTransition.path?.[i] || isSharedTransition) !== node &&
|
|
6865
|
+
hasTransform(node.latestValues)) {
|
|
6904
6866
|
transformBox(box, node.latestValues, node.layout?.layoutBox);
|
|
6905
6867
|
}
|
|
6906
6868
|
}
|
|
@@ -7167,15 +7129,6 @@
|
|
|
7167
7129
|
const asNumber = (v) => typeof v === "number" ? v : parseFloat(v);
|
|
7168
7130
|
|
|
7169
7131
|
const numBorders = cornerRadiusProps.length;
|
|
7170
|
-
const mixedTransforms = [
|
|
7171
|
-
"rotate",
|
|
7172
|
-
"rotateX",
|
|
7173
|
-
"rotateY",
|
|
7174
|
-
"rotateZ",
|
|
7175
|
-
"skewX",
|
|
7176
|
-
"skewY",
|
|
7177
|
-
];
|
|
7178
|
-
const numMixedTransforms = mixedTransforms.length;
|
|
7179
7132
|
const isPx = (value) => typeof value === "number" || px.test(value);
|
|
7180
7133
|
function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
|
|
7181
7134
|
if (shouldCrossfadeOpacity) {
|
|
@@ -7213,8 +7166,8 @@
|
|
|
7213
7166
|
* Mix rotation and skew. These can't be projected, so mix them every
|
|
7214
7167
|
* frame rather than leaving them frozen at the animation-start snapshot.
|
|
7215
7168
|
*/
|
|
7216
|
-
for (let i = 0; i <
|
|
7217
|
-
const key =
|
|
7169
|
+
for (let i = 0; i < angleTransforms.length; i++) {
|
|
7170
|
+
const key = angleTransforms[i];
|
|
7218
7171
|
if (follow[key] || lead[key]) {
|
|
7219
7172
|
target[key] = mixNumber$1(follow[key] || 0, lead[key] || 0, progress);
|
|
7220
7173
|
}
|
|
@@ -7297,8 +7250,8 @@
|
|
|
7297
7250
|
}
|
|
7298
7251
|
}
|
|
7299
7252
|
function calcBoxDelta(delta, source, target, origin) {
|
|
7300
|
-
calcAxisDelta(delta.x, source.x, target.x, origin
|
|
7301
|
-
calcAxisDelta(delta.y, source.y, target.y, origin
|
|
7253
|
+
calcAxisDelta(delta.x, source.x, target.x, origin?.originX);
|
|
7254
|
+
calcAxisDelta(delta.y, source.y, target.y, origin?.originY);
|
|
7302
7255
|
}
|
|
7303
7256
|
function calcRelativeAxis(target, relative, parent, anchor = 0) {
|
|
7304
7257
|
const anchorPoint = anchor
|
|
@@ -7368,8 +7321,8 @@
|
|
|
7368
7321
|
* and acts as a bridge between motion values and removeAxisDelta
|
|
7369
7322
|
*/
|
|
7370
7323
|
function removeBoxTransforms(box, transforms, originBox, sourceBox) {
|
|
7371
|
-
removeAxisTransforms(box.x, transforms, xKeys, originBox
|
|
7372
|
-
removeAxisTransforms(box.y, transforms, yKeys, originBox
|
|
7324
|
+
removeAxisTransforms(box.x, transforms, xKeys, originBox?.x, sourceBox?.x);
|
|
7325
|
+
removeAxisTransforms(box.y, transforms, yKeys, originBox?.y, sourceBox?.y);
|
|
7373
7326
|
}
|
|
7374
7327
|
|
|
7375
7328
|
const createAxisDelta = () => ({
|
|
@@ -7599,24 +7552,18 @@
|
|
|
7599
7552
|
transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `;
|
|
7600
7553
|
}
|
|
7601
7554
|
if (latestTransform) {
|
|
7602
|
-
const { transformPerspective
|
|
7555
|
+
const { transformPerspective } = latestTransform;
|
|
7603
7556
|
if (transformPerspective)
|
|
7604
7557
|
transform = `perspective(${transformPerspective}px) ${transform}`;
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
if (rotateZ)
|
|
7615
|
-
transform += `rotateZ(${rotateZ}deg) `;
|
|
7616
|
-
if (skewX)
|
|
7617
|
-
transform += `skewX(${skewX}deg) `;
|
|
7618
|
-
if (skewY)
|
|
7619
|
-
transform += `skewY(${skewY}deg) `;
|
|
7558
|
+
for (let i = 0; i < angleTransforms.length; i++) {
|
|
7559
|
+
const key = angleTransforms[i];
|
|
7560
|
+
const value = latestTransform[key];
|
|
7561
|
+
if (value)
|
|
7562
|
+
transform += `${key}(${value}deg) `;
|
|
7563
|
+
// Additive `rotate()` so user `rotate` isn't clobbered.
|
|
7564
|
+
if (!i && latestTransform.pathRotation)
|
|
7565
|
+
transform += `rotate(${latestTransform.pathRotation}deg) `;
|
|
7566
|
+
}
|
|
7620
7567
|
}
|
|
7621
7568
|
/**
|
|
7622
7569
|
* Apply scale to match the size of the element to the size we want it.
|
|
@@ -7679,7 +7626,10 @@
|
|
|
7679
7626
|
calculatedTargetDeltas: 0,
|
|
7680
7627
|
calculatedProjections: 0,
|
|
7681
7628
|
};
|
|
7682
|
-
|
|
7629
|
+
/**
|
|
7630
|
+
* Transforms that distort an element's layout, so must be reset before measuring.
|
|
7631
|
+
*/
|
|
7632
|
+
const distortingTransforms = ["z", "skew", "skewZ"];
|
|
7683
7633
|
/**
|
|
7684
7634
|
* We use 1000 as the animation target as 0-1000 maps better to pixels than 0-1
|
|
7685
7635
|
* which has a noticeable difference in spring animations
|
|
@@ -7697,7 +7647,15 @@
|
|
|
7697
7647
|
}
|
|
7698
7648
|
}
|
|
7699
7649
|
}
|
|
7700
|
-
|
|
7650
|
+
/**
|
|
7651
|
+
* Move accelerated transforms back to the main thread. The WAAPI animation
|
|
7652
|
+
* hid startUpdate()'s reset of rotations, skews and z, and moving off it
|
|
7653
|
+
* writes the whole transform back, so reset them again before this node
|
|
7654
|
+
* is measured.
|
|
7655
|
+
*/
|
|
7656
|
+
const demoteTransforms = (node) => node.instance &&
|
|
7657
|
+
transformGroups.get(node.instance)?.demote() &&
|
|
7658
|
+
node.resetSkewAndRotation();
|
|
7701
7659
|
function cancelTreeOptimisedTransformAnimations(projectionNode) {
|
|
7702
7660
|
projectionNode.hasCheckedOptimisedAppear = true;
|
|
7703
7661
|
if (projectionNode.root === projectionNode)
|
|
@@ -7887,8 +7845,7 @@
|
|
|
7887
7845
|
return this.eventHandlers.get(name).add(handler);
|
|
7888
7846
|
}
|
|
7889
7847
|
notifyListeners(name, ...args) {
|
|
7890
|
-
|
|
7891
|
-
subscriptionManager && subscriptionManager.notify(...args);
|
|
7848
|
+
this.eventHandlers.get(name)?.notify(...args);
|
|
7892
7849
|
}
|
|
7893
7850
|
hasListeners(name) {
|
|
7894
7851
|
return this.eventHandlers.has(name);
|
|
@@ -7906,7 +7863,7 @@
|
|
|
7906
7863
|
visualElement.mount(instance);
|
|
7907
7864
|
}
|
|
7908
7865
|
this.root.nodes.add(this);
|
|
7909
|
-
this.parent
|
|
7866
|
+
this.parent?.children.add(this);
|
|
7910
7867
|
if (this.root.hasTreeAnimated && (layout || layoutId)) {
|
|
7911
7868
|
this.isLayoutDirty = true;
|
|
7912
7869
|
}
|
|
@@ -7924,7 +7881,7 @@
|
|
|
7924
7881
|
return;
|
|
7925
7882
|
innerWidth = newInnerWidth;
|
|
7926
7883
|
this.root.updateBlockedByResize = true;
|
|
7927
|
-
cancelDelay
|
|
7884
|
+
cancelDelay?.();
|
|
7928
7885
|
cancelDelay = delay(resizeUnblockUpdate, 250);
|
|
7929
7886
|
if (globalProjectionState.hasAnimatedSinceResize) {
|
|
7930
7887
|
globalProjectionState.hasAnimatedSinceResize = false;
|
|
@@ -8015,9 +7972,7 @@
|
|
|
8015
7972
|
if (!hasLayoutChanged) {
|
|
8016
7973
|
finishAnimation(this);
|
|
8017
7974
|
}
|
|
8018
|
-
|
|
8019
|
-
this.options.onExitComplete();
|
|
8020
|
-
}
|
|
7975
|
+
this.isLead() && this.options.onExitComplete?.();
|
|
8021
7976
|
}
|
|
8022
7977
|
this.targetLayout = newLayout;
|
|
8023
7978
|
});
|
|
@@ -8025,9 +7980,8 @@
|
|
|
8025
7980
|
unmount() {
|
|
8026
7981
|
this.options.layoutId && this.willUpdate();
|
|
8027
7982
|
this.root.nodes.remove(this);
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
this.parent && this.parent.children.delete(this);
|
|
7983
|
+
this.getStack()?.remove(this);
|
|
7984
|
+
this.parent?.children.delete(this);
|
|
8031
7985
|
this.instance = undefined;
|
|
8032
7986
|
this.eventHandlers.clear();
|
|
8033
7987
|
this.hasLayoutListener = false;
|
|
@@ -8048,7 +8002,7 @@
|
|
|
8048
8002
|
if (this.isUpdateBlocked())
|
|
8049
8003
|
return;
|
|
8050
8004
|
this.isUpdating = true;
|
|
8051
|
-
this.nodes
|
|
8005
|
+
this.nodes?.forEach(resetSkewAndRotation);
|
|
8052
8006
|
this.animationId++;
|
|
8053
8007
|
}
|
|
8054
8008
|
getTransformTemplate() {
|
|
@@ -8058,7 +8012,7 @@
|
|
|
8058
8012
|
willUpdate(shouldNotifyListeners = true) {
|
|
8059
8013
|
this.root.hasTreeAnimated = true;
|
|
8060
8014
|
if (this.root.isUpdateBlocked()) {
|
|
8061
|
-
this.options.onExitComplete
|
|
8015
|
+
this.options.onExitComplete?.();
|
|
8062
8016
|
return;
|
|
8063
8017
|
}
|
|
8064
8018
|
/**
|
|
@@ -8275,24 +8229,23 @@
|
|
|
8275
8229
|
this.projectionDelta = undefined;
|
|
8276
8230
|
this.notifyListeners("measure", this.layout.layoutBox);
|
|
8277
8231
|
const { visualElement } = this.options;
|
|
8278
|
-
visualElement
|
|
8279
|
-
visualElement.notify("LayoutMeasure", this.layout.layoutBox, prevLayout ? prevLayout.layoutBox : undefined);
|
|
8232
|
+
visualElement?.notify("LayoutMeasure", this.layout.layoutBox, prevLayout?.layoutBox);
|
|
8280
8233
|
}
|
|
8281
8234
|
updateScroll(phase = "measure") {
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
const isRoot = checkIsScrollRoot(
|
|
8235
|
+
const { scroll, instance } = this;
|
|
8236
|
+
const { animationId } = this.root;
|
|
8237
|
+
if (this.options.layoutScroll &&
|
|
8238
|
+
instance &&
|
|
8239
|
+
!(scroll &&
|
|
8240
|
+
scroll.animationId === animationId &&
|
|
8241
|
+
scroll.phase === phase)) {
|
|
8242
|
+
const isRoot = checkIsScrollRoot(instance);
|
|
8290
8243
|
this.scroll = {
|
|
8291
|
-
animationId
|
|
8244
|
+
animationId,
|
|
8292
8245
|
phase,
|
|
8293
8246
|
isRoot,
|
|
8294
|
-
offset: measureScroll(
|
|
8295
|
-
wasRoot:
|
|
8247
|
+
offset: measureScroll(instance),
|
|
8248
|
+
wasRoot: scroll ? scroll.isRoot : isRoot,
|
|
8296
8249
|
};
|
|
8297
8250
|
}
|
|
8298
8251
|
}
|
|
@@ -8382,10 +8335,10 @@
|
|
|
8382
8335
|
}
|
|
8383
8336
|
return boxWithoutScroll;
|
|
8384
8337
|
}
|
|
8385
|
-
applyTransform(box, transformOnly = false, output, skipOwnTransform) {
|
|
8338
|
+
applyTransform(box, transformOnly = false, output, skipOwnTransform, start = 0) {
|
|
8386
8339
|
const withTransforms = output || createBox();
|
|
8387
8340
|
copyBoxInto(withTransforms, box);
|
|
8388
|
-
for (let i =
|
|
8341
|
+
for (let i = start; i < this.path.length; i++) {
|
|
8389
8342
|
const node = this.path[i];
|
|
8390
8343
|
if (!transformOnly &&
|
|
8391
8344
|
node.options.layoutScroll &&
|
|
@@ -8557,8 +8510,13 @@
|
|
|
8557
8510
|
this.attemptToResolveRelativeTarget = false;
|
|
8558
8511
|
if (this.options.layoutAnchor !== false &&
|
|
8559
8512
|
relativeParent &&
|
|
8560
|
-
|
|
8561
|
-
|
|
8513
|
+
(!relativeParent.resumingFrom === !this.resumingFrom ||
|
|
8514
|
+
/**
|
|
8515
|
+
* Shared layout animations within a layoutRoot
|
|
8516
|
+
* animate relative to it.
|
|
8517
|
+
*/
|
|
8518
|
+
(relativeParent.options.layoutRoot &&
|
|
8519
|
+
!relativeParent.resumingFrom)) &&
|
|
8562
8520
|
!relativeParent.options.layoutScroll &&
|
|
8563
8521
|
relativeParent.target &&
|
|
8564
8522
|
this.animationProgress !== 1) {
|
|
@@ -8638,7 +8596,7 @@
|
|
|
8638
8596
|
* If this section of the tree isn't animating we can
|
|
8639
8597
|
* delete our target sources for the following frame.
|
|
8640
8598
|
*/
|
|
8641
|
-
this.isTreeAnimating = Boolean(
|
|
8599
|
+
this.isTreeAnimating = Boolean(this.parent?.isTreeAnimating ||
|
|
8642
8600
|
this.currentAnimation ||
|
|
8643
8601
|
this.pendingAnimation);
|
|
8644
8602
|
if (!this.isTreeAnimating) {
|
|
@@ -8659,8 +8617,17 @@
|
|
|
8659
8617
|
/**
|
|
8660
8618
|
* Apply all the parent deltas to this box to produce the corrected box. This
|
|
8661
8619
|
* is the layout box, as it will appear on screen as a result of the transforms of its parents.
|
|
8620
|
+
*
|
|
8621
|
+
* A shared target is usually in page space, so ancestor transforms are applied
|
|
8622
|
+
* too. But a target relative to a parent that isn't itself shared (like a
|
|
8623
|
+
* layoutRoot) is in that parent's layout space. So the transforms of the
|
|
8624
|
+
* parent and its ancestors, which move both elements alike, are skipped,
|
|
8625
|
+
* but those of other ancestors (between it and either element) apply.
|
|
8662
8626
|
*/
|
|
8663
|
-
applyTreeDeltas(this.layoutCorrected, this.treeScale, this.path, isShared
|
|
8627
|
+
applyTreeDeltas(this.layoutCorrected, this.treeScale, this.path, isShared &&
|
|
8628
|
+
(lead.relativeTarget && !lead.relativeParent.resumingFrom
|
|
8629
|
+
? lead.relativeParent
|
|
8630
|
+
: true));
|
|
8664
8631
|
/**
|
|
8665
8632
|
* If this layer needs to perform scale correction but doesn't have a target,
|
|
8666
8633
|
* use the layout as the target.
|
|
@@ -8727,8 +8694,7 @@
|
|
|
8727
8694
|
scheduleRender(notifyAll = true) {
|
|
8728
8695
|
this.options.visualElement?.scheduleRender();
|
|
8729
8696
|
if (notifyAll) {
|
|
8730
|
-
|
|
8731
|
-
stack && stack.scheduleRender();
|
|
8697
|
+
this.getStack()?.scheduleRender();
|
|
8732
8698
|
}
|
|
8733
8699
|
if (this.resumingFrom && !this.resumingFrom.instance) {
|
|
8734
8700
|
this.resumingFrom = undefined;
|
|
@@ -8750,9 +8716,7 @@
|
|
|
8750
8716
|
}
|
|
8751
8717
|
this.attemptToResolveRelativeTarget = !hasOnlyRelativeTargetChanged;
|
|
8752
8718
|
const relativeLayout = createBox();
|
|
8753
|
-
const
|
|
8754
|
-
const layoutSource = this.layout ? this.layout.source : undefined;
|
|
8755
|
-
const isSharedLayoutAnimation = snapshotSource !== layoutSource;
|
|
8719
|
+
const isSharedLayoutAnimation = snapshot?.source !== this.layout?.source;
|
|
8756
8720
|
const stack = this.getStack();
|
|
8757
8721
|
const isOnlyMember = !stack || stack.members.length <= 1;
|
|
8758
8722
|
const shouldCrossfadeOpacity = Boolean(isSharedLayoutAnimation &&
|
|
@@ -8767,19 +8731,11 @@
|
|
|
8767
8731
|
this.mixTargetDelta = (latest) => {
|
|
8768
8732
|
const progress = latest / 1000;
|
|
8769
8733
|
const point = interpolate?.(progress);
|
|
8734
|
+
mixAxisDeltaLinear(targetDelta.x, delta.x, progress);
|
|
8735
|
+
mixAxisDeltaLinear(targetDelta.y, delta.y, progress);
|
|
8770
8736
|
if (point) {
|
|
8771
8737
|
targetDelta.x.translate = point.x;
|
|
8772
|
-
targetDelta.x.scale = mixNumber$1(delta.x.scale, 1, progress);
|
|
8773
|
-
targetDelta.x.origin = delta.x.origin;
|
|
8774
|
-
targetDelta.x.originPoint = delta.x.originPoint;
|
|
8775
8738
|
targetDelta.y.translate = point.y;
|
|
8776
|
-
targetDelta.y.scale = mixNumber$1(delta.y.scale, 1, progress);
|
|
8777
|
-
targetDelta.y.origin = delta.y.origin;
|
|
8778
|
-
targetDelta.y.originPoint = delta.y.originPoint;
|
|
8779
|
-
}
|
|
8780
|
-
else {
|
|
8781
|
-
mixAxisDeltaLinear(targetDelta.x, delta.x, progress);
|
|
8782
|
-
mixAxisDeltaLinear(targetDelta.y, delta.y, progress);
|
|
8783
8739
|
}
|
|
8784
8740
|
this.setTargetDelta(targetDelta);
|
|
8785
8741
|
if (this.relativeTarget &&
|
|
@@ -8805,7 +8761,7 @@
|
|
|
8805
8761
|
this.animationValues = mixedValues;
|
|
8806
8762
|
mixValues(mixedValues, snapshotLatestValues, this.latestValues, progress, shouldCrossfadeOpacity, isOnlyMember);
|
|
8807
8763
|
}
|
|
8808
|
-
if (point
|
|
8764
|
+
if (point?.rotate !== undefined) {
|
|
8809
8765
|
// Dedicated `pathRotation` channel, not `rotate`, so an
|
|
8810
8766
|
// animating `rotate` is composed with, never clobbered.
|
|
8811
8767
|
if (!this.animationValues)
|
|
@@ -8870,7 +8826,7 @@
|
|
|
8870
8826
|
}
|
|
8871
8827
|
finishAnimation() {
|
|
8872
8828
|
if (this.currentAnimation) {
|
|
8873
|
-
this.mixTargetDelta
|
|
8829
|
+
this.mixTargetDelta?.(animationTarget);
|
|
8874
8830
|
this.currentAnimation.stop();
|
|
8875
8831
|
}
|
|
8876
8832
|
this.completeAnimation();
|
|
@@ -8890,13 +8846,11 @@
|
|
|
8890
8846
|
this.layout &&
|
|
8891
8847
|
layout &&
|
|
8892
8848
|
shouldAnimatePositionOnly(this.options.animationType, this.layout.layoutBox, layout.layoutBox)) {
|
|
8849
|
+
const { layoutBox } = this.layout;
|
|
8850
|
+
const leadTarget = target;
|
|
8893
8851
|
target = this.target || createBox();
|
|
8894
|
-
|
|
8895
|
-
target.
|
|
8896
|
-
target.x.max = target.x.min + xLength;
|
|
8897
|
-
const yLength = calcLength(this.layout.layoutBox.y);
|
|
8898
|
-
target.y.min = lead.target.y.min;
|
|
8899
|
-
target.y.max = target.y.min + yLength;
|
|
8852
|
+
alignAxis(target.x, leadTarget.x, layoutBox.x);
|
|
8853
|
+
alignAxis(target.y, leadTarget.y, layoutBox.y);
|
|
8900
8854
|
}
|
|
8901
8855
|
copyBoxInto(targetWithTransforms, target);
|
|
8902
8856
|
/**
|
|
@@ -8921,8 +8875,8 @@
|
|
|
8921
8875
|
stack.add(node);
|
|
8922
8876
|
const config = node.options.initialPromotionConfig;
|
|
8923
8877
|
node.promote({
|
|
8924
|
-
transition: config
|
|
8925
|
-
preserveFollowOpacity: config
|
|
8878
|
+
transition: config?.transition,
|
|
8879
|
+
preserveFollowOpacity: config?.shouldPreserveFollowOpacity
|
|
8926
8880
|
? config.shouldPreserveFollowOpacity(node)
|
|
8927
8881
|
: undefined,
|
|
8928
8882
|
});
|
|
@@ -8945,9 +8899,7 @@
|
|
|
8945
8899
|
return this.root.sharedNodes.get(layoutId);
|
|
8946
8900
|
}
|
|
8947
8901
|
promote({ needsReset, transition, preserveFollowOpacity, } = {}) {
|
|
8948
|
-
|
|
8949
|
-
if (stack)
|
|
8950
|
-
stack.promote(this, preserveFollowOpacity);
|
|
8902
|
+
this.getStack()?.promote(this, preserveFollowOpacity);
|
|
8951
8903
|
if (needsReset) {
|
|
8952
8904
|
this.projectionDelta = undefined;
|
|
8953
8905
|
this.needsReset = true;
|
|
@@ -8956,45 +8908,27 @@
|
|
|
8956
8908
|
this.setOptions({ transition });
|
|
8957
8909
|
}
|
|
8958
8910
|
relegate() {
|
|
8959
|
-
|
|
8960
|
-
if (stack) {
|
|
8961
|
-
return stack.relegate(this);
|
|
8962
|
-
}
|
|
8963
|
-
else {
|
|
8964
|
-
return false;
|
|
8965
|
-
}
|
|
8911
|
+
return this.getStack()?.relegate(this) || false;
|
|
8966
8912
|
}
|
|
8967
8913
|
resetSkewAndRotation() {
|
|
8968
8914
|
const { visualElement } = this.options;
|
|
8969
8915
|
if (!visualElement)
|
|
8970
8916
|
return;
|
|
8971
|
-
// If there's no detected skew or rotation values, we can early return without a forced render.
|
|
8972
|
-
let hasDistortingTransform = false;
|
|
8973
8917
|
/**
|
|
8974
8918
|
* An unrolled check for rotation values. Most elements don't have any rotation and
|
|
8975
|
-
* skipping the
|
|
8919
|
+
* skipping the loop and new object creation is 50% faster.
|
|
8920
|
+
* If there's no distorting values, we don't need to do any more.
|
|
8976
8921
|
*/
|
|
8977
8922
|
const { latestValues } = visualElement;
|
|
8978
|
-
if (latestValues
|
|
8979
|
-
latestValues.rotate ||
|
|
8980
|
-
latestValues.rotateX ||
|
|
8981
|
-
latestValues.rotateY ||
|
|
8982
|
-
latestValues.rotateZ ||
|
|
8983
|
-
latestValues.skewX ||
|
|
8984
|
-
latestValues.skewY) {
|
|
8985
|
-
hasDistortingTransform = true;
|
|
8986
|
-
}
|
|
8987
|
-
// If there's no distorting values, we don't need to do any more.
|
|
8988
|
-
if (!hasDistortingTransform)
|
|
8923
|
+
if (!hasDistortingTransform(latestValues))
|
|
8989
8924
|
return;
|
|
8990
8925
|
const resetValues = {};
|
|
8991
|
-
if (latestValues.z) {
|
|
8992
|
-
resetDistortingTransform("z", visualElement, resetValues, this.animationValues);
|
|
8993
|
-
}
|
|
8994
8926
|
// Check the skew and rotate value of all axes and reset to 0
|
|
8995
|
-
for (let i = 0; i <
|
|
8996
|
-
resetDistortingTransform(
|
|
8997
|
-
|
|
8927
|
+
for (let i = 0; i < distortingTransforms.length; i++) {
|
|
8928
|
+
resetDistortingTransform(distortingTransforms[i], visualElement, resetValues, this.animationValues);
|
|
8929
|
+
}
|
|
8930
|
+
for (let i = 0; i < angleTransforms.length; i++) {
|
|
8931
|
+
resetDistortingTransform(angleTransforms[i], visualElement, resetValues, this.animationValues);
|
|
8998
8932
|
}
|
|
8999
8933
|
// Force a render of this element to apply the transform with all skews and rotations
|
|
9000
8934
|
// set to 0.
|
|
@@ -9014,7 +8948,13 @@
|
|
|
9014
8948
|
styleProp) {
|
|
9015
8949
|
if (!this.instance || this.isSVG)
|
|
9016
8950
|
return;
|
|
9017
|
-
|
|
8951
|
+
const lead = this.getLead();
|
|
8952
|
+
/**
|
|
8953
|
+
* Without crossfade, a new lead hides the previous one, as it's
|
|
8954
|
+
* usually unmounting. One that gave its layoutId away keeps
|
|
8955
|
+
* rendering, so only hide nodes that follow another.
|
|
8956
|
+
*/
|
|
8957
|
+
if (!this.isVisible && lead !== this) {
|
|
9018
8958
|
targetStyle.visibility = "hidden";
|
|
9019
8959
|
return;
|
|
9020
8960
|
}
|
|
@@ -9030,9 +8970,10 @@
|
|
|
9030
8970
|
: "none";
|
|
9031
8971
|
return;
|
|
9032
8972
|
}
|
|
9033
|
-
const lead = this.getLead();
|
|
9034
8973
|
if (!this.projectionDelta || !this.layout || !lead.target) {
|
|
9035
|
-
if (this.options.layoutId) {
|
|
8974
|
+
if (this.options.layoutId || this.needsShow) {
|
|
8975
|
+
this.needsShow = false;
|
|
8976
|
+
targetStyle.visibility = "";
|
|
9036
8977
|
targetStyle.opacity =
|
|
9037
8978
|
this.latestValues.opacity !== undefined
|
|
9038
8979
|
? this.latestValues.opacity
|
|
@@ -9166,52 +9107,49 @@
|
|
|
9166
9107
|
const isShared = snapshot.source !== node.layout.source;
|
|
9167
9108
|
// TODO Maybe we want to also resize the layout snapshot so we don't trigger
|
|
9168
9109
|
// animations for instance if layout="size" and an element has only changed position
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
|
|
9198
|
-
node.relativeTarget[axis].min + length;
|
|
9199
|
-
}
|
|
9200
|
-
});
|
|
9201
|
-
}
|
|
9110
|
+
const resizeSnapshot = (box, to) => {
|
|
9111
|
+
if (animationType === "size") {
|
|
9112
|
+
eachAxis((axis) => {
|
|
9113
|
+
const length = calcLength(box[axis]);
|
|
9114
|
+
box[axis].min = to[axis].min;
|
|
9115
|
+
box[axis].max = box[axis].min + length;
|
|
9116
|
+
});
|
|
9117
|
+
}
|
|
9118
|
+
else if (animationType === "x" || animationType === "y") {
|
|
9119
|
+
const snapAxis = animationType === "x" ? "y" : "x";
|
|
9120
|
+
copyAxisInto(box[snapAxis], to[snapAxis]);
|
|
9121
|
+
}
|
|
9122
|
+
else if (shouldAnimatePositionOnly(animationType, snapshot.layoutBox, layout)) {
|
|
9123
|
+
eachAxis((axis) => {
|
|
9124
|
+
const length = calcLength(to[axis]);
|
|
9125
|
+
box[axis].max = box[axis].min + length;
|
|
9126
|
+
/**
|
|
9127
|
+
* Ensure relative target gets resized and rerendererd
|
|
9128
|
+
*/
|
|
9129
|
+
if (node.relativeTarget && !node.currentAnimation) {
|
|
9130
|
+
node.isProjectionDirty = true;
|
|
9131
|
+
node.relativeTarget[axis].max =
|
|
9132
|
+
node.relativeTarget[axis].min + length;
|
|
9133
|
+
}
|
|
9134
|
+
});
|
|
9135
|
+
}
|
|
9136
|
+
};
|
|
9137
|
+
const snapshotBox = isShared ? snapshot.measuredBox : snapshot.layoutBox;
|
|
9138
|
+
resizeSnapshot(snapshotBox, layout);
|
|
9202
9139
|
const layoutDelta = createDelta();
|
|
9203
9140
|
calcBoxDelta(layoutDelta, layout, snapshot.layoutBox);
|
|
9204
9141
|
const visualDelta = createDelta();
|
|
9205
|
-
|
|
9206
|
-
calcBoxDelta(visualDelta, node.applyTransform(measuredLayout, true), snapshot.measuredBox);
|
|
9207
|
-
}
|
|
9208
|
-
else {
|
|
9209
|
-
calcBoxDelta(visualDelta, layout, snapshot.layoutBox);
|
|
9210
|
-
}
|
|
9142
|
+
calcBoxDelta(visualDelta, isShared ? node.applyTransform(measuredLayout, true) : layout, snapshotBox);
|
|
9211
9143
|
const hasLayoutChanged = !isDeltaZero(layoutDelta);
|
|
9212
9144
|
let hasRelativeLayoutChanged = false;
|
|
9213
|
-
|
|
9214
|
-
|
|
9145
|
+
const relativeParent = node.getClosestProjectingParent();
|
|
9146
|
+
/**
|
|
9147
|
+
* A snapshot resumed from another element is only relative to this
|
|
9148
|
+
* node's relativeParent if it's a layoutRoot that contains both.
|
|
9149
|
+
*/
|
|
9150
|
+
if (!node.resumeFrom ||
|
|
9151
|
+
(relativeParent?.options.layoutRoot &&
|
|
9152
|
+
node.resumeFrom.path.includes(relativeParent))) {
|
|
9215
9153
|
/**
|
|
9216
9154
|
* If the relativeParent is itself resuming from a different element then
|
|
9217
9155
|
* the relative snapshot is not relavent
|
|
@@ -9220,10 +9158,22 @@
|
|
|
9220
9158
|
const { snapshot: parentSnapshot, layout: parentLayout } = relativeParent;
|
|
9221
9159
|
if (parentSnapshot && parentLayout) {
|
|
9222
9160
|
const anchor = node.options.layoutAnchor || undefined;
|
|
9161
|
+
/**
|
|
9162
|
+
* A shared snapshot includes the transforms of the old
|
|
9163
|
+
* element's ancestors below the layoutRoot, as they
|
|
9164
|
+
* aren't shared with the new element.
|
|
9165
|
+
*/
|
|
9223
9166
|
const relativeSnapshot = createBox();
|
|
9224
|
-
calcRelativePosition(relativeSnapshot,
|
|
9167
|
+
calcRelativePosition(relativeSnapshot, isShared
|
|
9168
|
+
? node.resumeFrom.applyTransform(snapshot.layoutBox, true, undefined, true, relativeParent.depth + 1)
|
|
9169
|
+
: snapshot.layoutBox, parentSnapshot.layoutBox, anchor);
|
|
9225
9170
|
const relativeLayout = createBox();
|
|
9226
9171
|
calcRelativePosition(relativeLayout, layout, parentLayout.layoutBox, anchor);
|
|
9172
|
+
/**
|
|
9173
|
+
* A shared snapshot is resized in page space, above, so
|
|
9174
|
+
* resize it relative to the layoutRoot too.
|
|
9175
|
+
*/
|
|
9176
|
+
isShared && resizeSnapshot(relativeSnapshot, relativeLayout);
|
|
9227
9177
|
if (!boxEqualsRounded(relativeSnapshot, relativeLayout)) {
|
|
9228
9178
|
hasRelativeLayoutChanged = true;
|
|
9229
9179
|
}
|
|
@@ -9245,8 +9195,7 @@
|
|
|
9245
9195
|
});
|
|
9246
9196
|
}
|
|
9247
9197
|
else if (node.isLead()) {
|
|
9248
|
-
|
|
9249
|
-
onExitComplete && onExitComplete();
|
|
9198
|
+
node.options.onExitComplete?.();
|
|
9250
9199
|
}
|
|
9251
9200
|
/**
|
|
9252
9201
|
* Clearing transition
|
|
@@ -9352,7 +9301,7 @@
|
|
|
9352
9301
|
}
|
|
9353
9302
|
function resetTransformStyle(node) {
|
|
9354
9303
|
const { visualElement } = node.options;
|
|
9355
|
-
if (visualElement
|
|
9304
|
+
if (visualElement?.getProps().onBeforeLayoutMeasure) {
|
|
9356
9305
|
visualElement.notify("BeforeLayoutMeasure");
|
|
9357
9306
|
}
|
|
9358
9307
|
node.resetTransform();
|
|
@@ -9386,7 +9335,7 @@
|
|
|
9386
9335
|
mixAxis(output.y, from.y, to.y, p);
|
|
9387
9336
|
}
|
|
9388
9337
|
function hasOpacityCrossfade(node) {
|
|
9389
|
-
return
|
|
9338
|
+
return node.animationValues?.opacityExit !== undefined;
|
|
9390
9339
|
}
|
|
9391
9340
|
const defaultLayoutTransition = {
|
|
9392
9341
|
duration: 0.45,
|
|
@@ -9412,6 +9361,13 @@
|
|
|
9412
9361
|
roundAxis(box.x);
|
|
9413
9362
|
roundAxis(box.y);
|
|
9414
9363
|
}
|
|
9364
|
+
/**
|
|
9365
|
+
* Align an axis to the start of another, keeping the length of a layout axis.
|
|
9366
|
+
* The length is read first, as output can be the layout axis itself.
|
|
9367
|
+
*/
|
|
9368
|
+
function alignAxis(output, from, layout) {
|
|
9369
|
+
output.max = calcLength(layout) + (output.min = from.min);
|
|
9370
|
+
}
|
|
9415
9371
|
function shouldAnimatePositionOnly(animationType, snapshot, layout) {
|
|
9416
9372
|
return (animationType === "position" ||
|
|
9417
9373
|
(animationType === "preserve-aspect" &&
|
|
@@ -9462,7 +9418,7 @@
|
|
|
9462
9418
|
resetTransform: (instance, value) => {
|
|
9463
9419
|
instance.style.transform = value !== undefined ? value : "none";
|
|
9464
9420
|
},
|
|
9465
|
-
checkIsScrollRoot: (instance) =>
|
|
9421
|
+
checkIsScrollRoot: (instance) => window.getComputedStyle(instance).position === "fixed",
|
|
9466
9422
|
});
|
|
9467
9423
|
|
|
9468
9424
|
const visualElementStore = new WeakMap();
|
|
@@ -9748,9 +9704,7 @@
|
|
|
9748
9704
|
}
|
|
9749
9705
|
this.current = instance;
|
|
9750
9706
|
visualElementStore.set(instance, this);
|
|
9751
|
-
|
|
9752
|
-
this.projection.mount(instance);
|
|
9753
|
-
}
|
|
9707
|
+
this.projection?.instance || this.projection?.mount(instance);
|
|
9754
9708
|
/**
|
|
9755
9709
|
* Join the closest variant node above, so its variant changes
|
|
9756
9710
|
* propagate here, unless inherit={false} cuts the chain.
|
|
@@ -9796,12 +9750,12 @@
|
|
|
9796
9750
|
this.hasBeenMounted = true;
|
|
9797
9751
|
}
|
|
9798
9752
|
unmount() {
|
|
9799
|
-
this.projection
|
|
9753
|
+
this.projection?.unmount();
|
|
9800
9754
|
cancelFrame(this.notifyUpdate);
|
|
9801
9755
|
cancelFrame(this.render);
|
|
9802
9756
|
this.valueSubscriptions.forEach((remove) => remove());
|
|
9803
9757
|
this.valueSubscriptions.clear();
|
|
9804
|
-
this.removeFromVariantTree
|
|
9758
|
+
this.removeFromVariantTree?.();
|
|
9805
9759
|
this.parent?.removeChild(this);
|
|
9806
9760
|
for (const key in this.events) {
|
|
9807
9761
|
this.events[key].clear();
|
|
@@ -9817,17 +9771,14 @@
|
|
|
9817
9771
|
}
|
|
9818
9772
|
addChild(child) {
|
|
9819
9773
|
this.children.add(child);
|
|
9820
|
-
this.enteringChildren
|
|
9821
|
-
this.enteringChildren.add(child);
|
|
9774
|
+
(this.enteringChildren || (this.enteringChildren = new Set())).add(child);
|
|
9822
9775
|
}
|
|
9823
9776
|
removeChild(child) {
|
|
9824
9777
|
this.children.delete(child);
|
|
9825
|
-
this.enteringChildren
|
|
9778
|
+
this.enteringChildren?.delete(child);
|
|
9826
9779
|
}
|
|
9827
9780
|
bindToMotionValue(key, value) {
|
|
9828
|
-
|
|
9829
|
-
this.valueSubscriptions.get(key)();
|
|
9830
|
-
}
|
|
9781
|
+
this.valueSubscriptions.get(key)?.();
|
|
9831
9782
|
if (value.accelerate &&
|
|
9832
9783
|
acceleratedValues.has(key) &&
|
|
9833
9784
|
this.current instanceof HTMLElement) {
|
|
@@ -9869,8 +9820,7 @@
|
|
|
9869
9820
|
}
|
|
9870
9821
|
this.valueSubscriptions.set(key, () => {
|
|
9871
9822
|
removeOnChange();
|
|
9872
|
-
|
|
9873
|
-
removeSyncCheck();
|
|
9823
|
+
removeSyncCheck?.();
|
|
9874
9824
|
// Defer to MotionValue.on("change") auto-stop so React 19 remounts
|
|
9875
9825
|
// can resubscribe before the animation is cancelled (#3315).
|
|
9876
9826
|
});
|
|
@@ -9963,10 +9913,8 @@
|
|
|
9963
9913
|
*/
|
|
9964
9914
|
for (let i = 0; i < propEventHandlers.length; i++) {
|
|
9965
9915
|
const key = propEventHandlers[i];
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
delete this.propEventSubscriptions[key];
|
|
9969
|
-
}
|
|
9916
|
+
this.propEventSubscriptions[key]?.();
|
|
9917
|
+
delete this.propEventSubscriptions[key];
|
|
9970
9918
|
const listenerName = ("on" + key);
|
|
9971
9919
|
const listener = props[listenerName];
|
|
9972
9920
|
if (listener) {
|
|
@@ -9974,9 +9922,7 @@
|
|
|
9974
9922
|
}
|
|
9975
9923
|
}
|
|
9976
9924
|
this.prevMotionValues = updateMotionValuesFromProps(this, this.scrapeMotionValuesFromProps(props, this.prevProps || {}, this), this.prevMotionValues);
|
|
9977
|
-
|
|
9978
|
-
this.handleChildMotionValue();
|
|
9979
|
-
}
|
|
9925
|
+
this.handleChildMotionValue?.();
|
|
9980
9926
|
}
|
|
9981
9927
|
getProps() {
|
|
9982
9928
|
return this.props;
|
|
@@ -9985,7 +9931,7 @@
|
|
|
9985
9931
|
* Returns the variant definition with a given name.
|
|
9986
9932
|
*/
|
|
9987
9933
|
getVariant(name) {
|
|
9988
|
-
return this.props.variants
|
|
9934
|
+
return this.props.variants?.[name];
|
|
9989
9935
|
}
|
|
9990
9936
|
/**
|
|
9991
9937
|
* Returns the defined default transition on this component.
|
|
@@ -10023,11 +9969,8 @@
|
|
|
10023
9969
|
*/
|
|
10024
9970
|
removeValue(key) {
|
|
10025
9971
|
this.values.delete(key);
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
unsubscribe();
|
|
10029
|
-
this.valueSubscriptions.delete(key);
|
|
10030
|
-
}
|
|
9972
|
+
this.valueSubscriptions.get(key)?.();
|
|
9973
|
+
this.valueSubscriptions.delete(key);
|
|
10031
9974
|
delete this.latestValues[key];
|
|
10032
9975
|
this.removeValueFromRenderState(key, this.renderState);
|
|
10033
9976
|
}
|
|
@@ -10038,10 +9981,10 @@
|
|
|
10038
9981
|
return this.values.has(key);
|
|
10039
9982
|
}
|
|
10040
9983
|
getValue(key, defaultValue) {
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
9984
|
+
let value = this.props.values?.[key];
|
|
9985
|
+
if (value)
|
|
9986
|
+
return value;
|
|
9987
|
+
value = this.values.get(key);
|
|
10045
9988
|
if (value === undefined && defaultValue !== undefined) {
|
|
10046
9989
|
value = motionValue(defaultValue ?? this.getDefaultValue?.(key), {
|
|
10047
9990
|
owner: this,
|
|
@@ -10091,15 +10034,11 @@
|
|
|
10091
10034
|
this.baseTarget[key] = value;
|
|
10092
10035
|
}
|
|
10093
10036
|
on(eventName, callback) {
|
|
10094
|
-
|
|
10095
|
-
this.events[eventName] = new SubscriptionManager();
|
|
10096
|
-
}
|
|
10097
|
-
return this.events[eventName].add(callback);
|
|
10037
|
+
return (this.events[eventName] ||
|
|
10038
|
+
(this.events[eventName] = new SubscriptionManager())).add(callback);
|
|
10098
10039
|
}
|
|
10099
10040
|
notify(eventName, ...args) {
|
|
10100
|
-
|
|
10101
|
-
this.events[eventName].notify(...args);
|
|
10102
|
-
}
|
|
10041
|
+
this.events[eventName]?.notify(...args);
|
|
10103
10042
|
}
|
|
10104
10043
|
scheduleRenderMicrotask() {
|
|
10105
10044
|
microtask.render(this.render);
|
|
@@ -10272,7 +10211,7 @@
|
|
|
10272
10211
|
let ancestor = element.parentElement;
|
|
10273
10212
|
while (ancestor) {
|
|
10274
10213
|
const node = layoutNodes.get(ancestor);
|
|
10275
|
-
if (node
|
|
10214
|
+
if (node?.instance)
|
|
10276
10215
|
return node;
|
|
10277
10216
|
ancestor = ancestor.parentElement;
|
|
10278
10217
|
}
|
|
@@ -10538,7 +10477,7 @@
|
|
|
10538
10477
|
hasSurvivor &&
|
|
10539
10478
|
!newMemberIds.has(layoutId)) {
|
|
10540
10479
|
if (restore && restore.parent.isConnected) {
|
|
10541
|
-
restore.parent.insertBefore(element, restore.next
|
|
10480
|
+
restore.parent.insertBefore(element, restore.next?.parentNode === restore.parent
|
|
10542
10481
|
? restore.next
|
|
10543
10482
|
: null);
|
|
10544
10483
|
node.isPresent = false;
|
|
@@ -10743,10 +10682,6 @@
|
|
|
10743
10682
|
* Scroll positions of scrollable ancestors and window.
|
|
10744
10683
|
*/
|
|
10745
10684
|
this.scrollPositions = new Map();
|
|
10746
|
-
/**
|
|
10747
|
-
* Cleanup function for scroll listeners.
|
|
10748
|
-
*/
|
|
10749
|
-
this.removeScrollListeners = null;
|
|
10750
10685
|
this.onElementScroll = (event) => {
|
|
10751
10686
|
this.handleScroll(event.target);
|
|
10752
10687
|
};
|
|
@@ -10774,10 +10709,10 @@
|
|
|
10774
10709
|
this.history.push({ ...point, timestamp: time.now() });
|
|
10775
10710
|
const { onStart, onMove } = this.handlers;
|
|
10776
10711
|
if (!isPanStarted) {
|
|
10777
|
-
onStart
|
|
10712
|
+
onStart?.(this.lastMoveEvent, info);
|
|
10778
10713
|
this.startEvent = this.lastMoveEvent;
|
|
10779
10714
|
}
|
|
10780
|
-
onMove
|
|
10715
|
+
onMove?.(this.lastMoveEvent, info);
|
|
10781
10716
|
};
|
|
10782
10717
|
this.handlePointerMove = (event, info) => {
|
|
10783
10718
|
/**
|
|
@@ -10802,17 +10737,15 @@
|
|
|
10802
10737
|
// Resume animation if dragSnapToOrigin is set OR if no drag started (user just clicked)
|
|
10803
10738
|
// This ensures constraint animations continue when interrupted by a click
|
|
10804
10739
|
if (this.dragSnapToOrigin || !this.startEvent) {
|
|
10805
|
-
resumeAnimation
|
|
10740
|
+
resumeAnimation?.();
|
|
10806
10741
|
}
|
|
10807
10742
|
if (!(this.lastMoveEvent && this.lastMoveEventInfo))
|
|
10808
10743
|
return;
|
|
10809
10744
|
const panInfo = getPanInfo(event.type === "pointercancel"
|
|
10810
10745
|
? this.lastMoveEventInfo
|
|
10811
10746
|
: transformPoint(info, this.transformPagePoint), this.history);
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
}
|
|
10815
|
-
onSessionEnd && onSessionEnd(event, panInfo);
|
|
10747
|
+
this.startEvent && onEnd?.(event, panInfo);
|
|
10748
|
+
onSessionEnd?.(event, panInfo);
|
|
10816
10749
|
};
|
|
10817
10750
|
// If we have more than one touch, don't start detecting this gesture
|
|
10818
10751
|
if (!isPrimaryPointer(event))
|
|
@@ -10825,8 +10758,13 @@
|
|
|
10825
10758
|
const info = extractEventInfo(event);
|
|
10826
10759
|
const initialInfo = transformPoint(info, this.transformPagePoint);
|
|
10827
10760
|
const { point } = initialInfo;
|
|
10828
|
-
|
|
10829
|
-
|
|
10761
|
+
/**
|
|
10762
|
+
* Stamp the origin with time.now(), not frameData.timestamp: when
|
|
10763
|
+
* the frameloop is idle, frameData.timestamp is from the last frame
|
|
10764
|
+
* that ran, which can be seconds old, and a fast flick from a
|
|
10765
|
+
* standstill would then report near-zero velocity. See #1747.
|
|
10766
|
+
*/
|
|
10767
|
+
this.history = [{ ...point, timestamp: time.now() }];
|
|
10830
10768
|
const { onSessionStart } = handlers;
|
|
10831
10769
|
onSessionStart &&
|
|
10832
10770
|
onSessionStart(event, getPanInfo(initialInfo, this.history));
|
|
@@ -10850,30 +10788,19 @@
|
|
|
10850
10788
|
const style = getComputedStyle(current);
|
|
10851
10789
|
if (overflowStyles.has(style.overflowX) ||
|
|
10852
10790
|
overflowStyles.has(style.overflowY)) {
|
|
10853
|
-
this.scrollPositions.set(current,
|
|
10854
|
-
x: current.scrollLeft,
|
|
10855
|
-
y: current.scrollTop,
|
|
10856
|
-
});
|
|
10791
|
+
this.scrollPositions.set(current, getScroll(current));
|
|
10857
10792
|
}
|
|
10858
10793
|
current = current.parentElement;
|
|
10859
10794
|
}
|
|
10860
10795
|
// Track window scroll
|
|
10861
|
-
this.scrollPositions.set(window,
|
|
10862
|
-
|
|
10863
|
-
y: window.scrollY,
|
|
10864
|
-
});
|
|
10796
|
+
this.scrollPositions.set(window, getScroll(window));
|
|
10797
|
+
this.removeScrollListeners = pipe(
|
|
10865
10798
|
// Capture listener catches element scroll events as they bubble
|
|
10866
|
-
window
|
|
10799
|
+
addDomEvent(window, "scroll", this.onElementScroll, {
|
|
10867
10800
|
capture: true,
|
|
10868
|
-
})
|
|
10801
|
+
}),
|
|
10869
10802
|
// Direct window scroll listener (window scroll doesn't bubble)
|
|
10870
|
-
window
|
|
10871
|
-
this.removeScrollListeners = () => {
|
|
10872
|
-
window.removeEventListener("scroll", this.onElementScroll, {
|
|
10873
|
-
capture: true,
|
|
10874
|
-
});
|
|
10875
|
-
window.removeEventListener("scroll", this.onWindowScroll);
|
|
10876
|
-
};
|
|
10803
|
+
addDomEvent(window, "scroll", this.onWindowScroll, {}));
|
|
10877
10804
|
}
|
|
10878
10805
|
/**
|
|
10879
10806
|
* Handle scroll compensation during drag.
|
|
@@ -10886,12 +10813,7 @@
|
|
|
10886
10813
|
if (!initial)
|
|
10887
10814
|
return;
|
|
10888
10815
|
const isWindow = target === window;
|
|
10889
|
-
const current =
|
|
10890
|
-
? { x: window.scrollX, y: window.scrollY }
|
|
10891
|
-
: {
|
|
10892
|
-
x: target.scrollLeft,
|
|
10893
|
-
y: target.scrollTop,
|
|
10894
|
-
};
|
|
10816
|
+
const current = getScroll(target);
|
|
10895
10817
|
const delta = { x: current.x - initial.x, y: current.y - initial.y };
|
|
10896
10818
|
if (delta.x === 0 && delta.y === 0)
|
|
10897
10819
|
return;
|
|
@@ -10916,12 +10838,18 @@
|
|
|
10916
10838
|
this.handlers = handlers;
|
|
10917
10839
|
}
|
|
10918
10840
|
end() {
|
|
10919
|
-
this.removeListeners
|
|
10920
|
-
this.removeScrollListeners
|
|
10841
|
+
this.removeListeners?.();
|
|
10842
|
+
this.removeScrollListeners?.();
|
|
10921
10843
|
this.scrollPositions.clear();
|
|
10922
10844
|
cancelFrame(this.updatePoint);
|
|
10923
10845
|
}
|
|
10924
10846
|
}
|
|
10847
|
+
const getScroll = (target) => target === window
|
|
10848
|
+
? { x: window.scrollX, y: window.scrollY }
|
|
10849
|
+
: {
|
|
10850
|
+
x: target.scrollLeft,
|
|
10851
|
+
y: target.scrollTop,
|
|
10852
|
+
};
|
|
10925
10853
|
function transformPoint(info, transformPagePoint) {
|
|
10926
10854
|
return transformPagePoint ? { point: transformPagePoint(info.point) } : info;
|
|
10927
10855
|
}
|
|
@@ -11178,6 +11106,25 @@
|
|
|
11178
11106
|
: dragElastic[label] || 0;
|
|
11179
11107
|
}
|
|
11180
11108
|
|
|
11109
|
+
/**
|
|
11110
|
+
* Re-read the scroll of the projection's ancestors, and return that of the
|
|
11111
|
+
* `layoutScroll` ones as a key that changes when any of them scroll.
|
|
11112
|
+
*
|
|
11113
|
+
* Clear the cached scroll first so `updateScroll` bypasses its
|
|
11114
|
+
* per-animationId cache and re-reads the live value.
|
|
11115
|
+
*/
|
|
11116
|
+
function measureScroll(projection) {
|
|
11117
|
+
let key = "";
|
|
11118
|
+
for (const node of projection.path) {
|
|
11119
|
+
node.scroll = undefined;
|
|
11120
|
+
node.updateScroll();
|
|
11121
|
+
const { scroll } = node;
|
|
11122
|
+
if (node !== projection.root && scroll) {
|
|
11123
|
+
key += scroll.offset.x + "," + scroll.offset.y + ",";
|
|
11124
|
+
}
|
|
11125
|
+
}
|
|
11126
|
+
return key;
|
|
11127
|
+
}
|
|
11181
11128
|
const elementDragControls = new WeakMap();
|
|
11182
11129
|
class VisualElementDragControls {
|
|
11183
11130
|
constructor(visualElement, readProps = () => visualElement.getProps()) {
|
|
@@ -11209,8 +11156,7 @@
|
|
|
11209
11156
|
/**
|
|
11210
11157
|
* Don't start dragging if this component is exiting
|
|
11211
11158
|
*/
|
|
11212
|
-
|
|
11213
|
-
if (presenceContext && presenceContext.isPresent === false)
|
|
11159
|
+
if (this.visualElement.presenceContext?.isPresent === false)
|
|
11214
11160
|
return;
|
|
11215
11161
|
const onSessionStart = (event) => {
|
|
11216
11162
|
if (snapToCursor)
|
|
@@ -11221,8 +11167,7 @@
|
|
|
11221
11167
|
// Attempt to grab the global drag gesture lock - maybe make this part of PanSession
|
|
11222
11168
|
const { drag, dragPropagation, onDragStart } = this.getProps();
|
|
11223
11169
|
if (drag && !dragPropagation) {
|
|
11224
|
-
|
|
11225
|
-
this.openDragLock();
|
|
11170
|
+
this.openDragLock?.();
|
|
11226
11171
|
this.openDragLock = setDragLock(drag);
|
|
11227
11172
|
// If we don 't have the lock, don't start dragging
|
|
11228
11173
|
if (!this.openDragLock)
|
|
@@ -11233,9 +11178,10 @@
|
|
|
11233
11178
|
this.isDragging = true;
|
|
11234
11179
|
this.currentDirection = null;
|
|
11235
11180
|
this.resolveConstraints();
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
|
|
11181
|
+
const { projection } = this.visualElement;
|
|
11182
|
+
if (projection) {
|
|
11183
|
+
projection.isAnimationBlocked = true;
|
|
11184
|
+
projection.target = undefined;
|
|
11239
11185
|
}
|
|
11240
11186
|
/**
|
|
11241
11187
|
* Record gesture origin and pointer offset
|
|
@@ -11257,13 +11203,11 @@
|
|
|
11257
11203
|
current = parseFloat(current);
|
|
11258
11204
|
}
|
|
11259
11205
|
else if (percent.test(current)) {
|
|
11260
|
-
const
|
|
11261
|
-
if (
|
|
11262
|
-
|
|
11263
|
-
|
|
11264
|
-
|
|
11265
|
-
current = length * (parseFloat(current) / 100);
|
|
11266
|
-
}
|
|
11206
|
+
const measuredAxis = projection?.layout?.layoutBox[axis];
|
|
11207
|
+
if (measuredAxis) {
|
|
11208
|
+
current =
|
|
11209
|
+
calcLength(measuredAxis) *
|
|
11210
|
+
(parseFloat(current) / 100);
|
|
11267
11211
|
}
|
|
11268
11212
|
}
|
|
11269
11213
|
this.originPoint[axis] = current;
|
|
@@ -11273,8 +11217,7 @@
|
|
|
11273
11217
|
frame.update(() => onDragStart(event, info), false, true);
|
|
11274
11218
|
}
|
|
11275
11219
|
addValueToWillChange(this.visualElement, "transform");
|
|
11276
|
-
|
|
11277
|
-
animationState && animationState.setActive("whileDrag", true);
|
|
11220
|
+
this.visualElement.animationState?.setActive("whileDrag", true);
|
|
11278
11221
|
};
|
|
11279
11222
|
const onMove = (event, info) => {
|
|
11280
11223
|
this.latestPointerEvent = event;
|
|
@@ -11297,8 +11240,8 @@
|
|
|
11297
11240
|
onDirectionLock && onDirectionLock(this.currentDirection);
|
|
11298
11241
|
}
|
|
11299
11242
|
// Update each point with the latest position
|
|
11300
|
-
this.updateAxis("x",
|
|
11301
|
-
this.updateAxis("y",
|
|
11243
|
+
this.updateAxis("x", offset);
|
|
11244
|
+
this.updateAxis("y", offset);
|
|
11302
11245
|
/**
|
|
11303
11246
|
* Ideally we would leave the renderer to fire naturally at the end of
|
|
11304
11247
|
* this frame but if the element is about to change layout as the result
|
|
@@ -11367,12 +11310,11 @@
|
|
|
11367
11310
|
projection.isAnimationBlocked = false;
|
|
11368
11311
|
}
|
|
11369
11312
|
this.endPanSession();
|
|
11370
|
-
|
|
11371
|
-
if (!dragPropagation && this.openDragLock) {
|
|
11313
|
+
if (!this.getProps().dragPropagation && this.openDragLock) {
|
|
11372
11314
|
this.openDragLock();
|
|
11373
11315
|
this.openDragLock = null;
|
|
11374
11316
|
}
|
|
11375
|
-
animationState
|
|
11317
|
+
animationState?.setActive("whileDrag", false);
|
|
11376
11318
|
}
|
|
11377
11319
|
/**
|
|
11378
11320
|
* Clean up the pan session without modifying other drag state.
|
|
@@ -11380,31 +11322,42 @@
|
|
|
11380
11322
|
* without affecting projection animations or drag locks.
|
|
11381
11323
|
*/
|
|
11382
11324
|
endPanSession() {
|
|
11383
|
-
this.panSession
|
|
11325
|
+
this.panSession?.end();
|
|
11384
11326
|
this.panSession = undefined;
|
|
11385
11327
|
}
|
|
11386
|
-
updateAxis(axis,
|
|
11387
|
-
|
|
11328
|
+
updateAxis(axis, offset) {
|
|
11329
|
+
/**
|
|
11330
|
+
* Read drag per axis: a callback fired while updating the x axis
|
|
11331
|
+
* can synchronously re-render with new props.
|
|
11332
|
+
*/
|
|
11333
|
+
const { drag } = this.readProps();
|
|
11388
11334
|
// If we're not dragging this axis, do an early return.
|
|
11389
11335
|
if (!offset || !shouldDrag(axis, drag, this.currentDirection))
|
|
11390
11336
|
return;
|
|
11391
11337
|
const axisValue = this.getAxisMotionValue(axis);
|
|
11392
11338
|
let next = this.originPoint[axis] + offset[axis];
|
|
11393
11339
|
// Apply constraints
|
|
11394
|
-
|
|
11395
|
-
|
|
11340
|
+
const constraints = this.constraints && this.constraints[axis];
|
|
11341
|
+
if (constraints) {
|
|
11342
|
+
next = applyConstraints(next, constraints, this.elastic[axis]);
|
|
11396
11343
|
}
|
|
11397
11344
|
axisValue.set(next);
|
|
11398
11345
|
}
|
|
11399
11346
|
resolveConstraints() {
|
|
11400
11347
|
const { dragConstraints, dragElastic } = this.getProps();
|
|
11401
|
-
const
|
|
11402
|
-
|
|
11403
|
-
? this.visualElement.projection.measure(false)
|
|
11404
|
-
: this.visualElement.projection?.layout;
|
|
11348
|
+
const { projection } = this.visualElement;
|
|
11349
|
+
const layout = projection && (projection.layout || projection.measure(false));
|
|
11405
11350
|
const prevConstraints = this.constraints;
|
|
11406
11351
|
if (isElementConstraints(dragConstraints)) {
|
|
11407
|
-
|
|
11352
|
+
/**
|
|
11353
|
+
* Ref constraints are measured with the layout. Scrolling a
|
|
11354
|
+
* `layoutScroll` ancestor since can move the element relative
|
|
11355
|
+
* to them without a layout measurement, so measure them again
|
|
11356
|
+
* (#2024). Otherwise keep them: they and the layout can both
|
|
11357
|
+
* have moved on the page since, e.g. when content above grows.
|
|
11358
|
+
*/
|
|
11359
|
+
if (!this.constraints ||
|
|
11360
|
+
this.scroll !== measureScroll(projection)) {
|
|
11408
11361
|
this.constraints = this.resolveRefConstraints();
|
|
11409
11362
|
}
|
|
11410
11363
|
}
|
|
@@ -11447,21 +11400,20 @@
|
|
|
11447
11400
|
if (!projection || !projection.layout)
|
|
11448
11401
|
return false;
|
|
11449
11402
|
/**
|
|
11450
|
-
* Refresh the
|
|
11451
|
-
*
|
|
11452
|
-
*
|
|
11453
|
-
*
|
|
11454
|
-
*
|
|
11455
|
-
*
|
|
11456
|
-
* Clear the cached scroll first so `updateScroll` bypasses its
|
|
11457
|
-
* per-animationId cache and re-reads the live value.
|
|
11403
|
+
* Refresh the scroll offsets of the root and `layoutScroll`
|
|
11404
|
+
* ancestors so the constraint's viewport box translates to the
|
|
11405
|
+
* layout's coordinates. The scroll captured when the layout was
|
|
11406
|
+
* measured can be stale if they've scrolled since — e.g. via the
|
|
11407
|
+
* browser restoring scroll on refresh, or an ancestor layout effect
|
|
11408
|
+
* running after this element's mount (#2829).
|
|
11458
11409
|
*/
|
|
11459
|
-
|
|
11460
|
-
projection.root.scroll = undefined;
|
|
11461
|
-
projection.root.updateScroll();
|
|
11462
|
-
}
|
|
11410
|
+
this.scroll = measureScroll(projection);
|
|
11463
11411
|
const constraintsBox = measurePageBox(constraintsElement, projection.root, this.visualElement.getTransformPagePoint());
|
|
11464
|
-
|
|
11412
|
+
/**
|
|
11413
|
+
* layoutBox removes the scroll of `layoutScroll` ancestors, so
|
|
11414
|
+
* remove it from the constraints box too.
|
|
11415
|
+
*/
|
|
11416
|
+
let measuredConstraints = calcViewportConstraints(projection.layout.layoutBox, projection.removeElementScroll(constraintsBox));
|
|
11465
11417
|
/**
|
|
11466
11418
|
* If there's an onMeasureDragConstraints listener we call it and
|
|
11467
11419
|
* if different constraints are returned, set constraints to that
|
|
@@ -11482,7 +11434,7 @@
|
|
|
11482
11434
|
if (!shouldDrag(axis, dragAxes || drag, this.currentDirection)) {
|
|
11483
11435
|
return;
|
|
11484
11436
|
}
|
|
11485
|
-
let transition =
|
|
11437
|
+
let transition = constraints[axis] || {};
|
|
11486
11438
|
if (dragSnapToOrigin === true ||
|
|
11487
11439
|
dragSnapToOrigin === axis)
|
|
11488
11440
|
transition = { min: 0, max: 0 };
|
|
@@ -11588,7 +11540,7 @@
|
|
|
11588
11540
|
this.visualElement.current.style.transform = transformTemplate
|
|
11589
11541
|
? transformTemplate({}, "")
|
|
11590
11542
|
: "none";
|
|
11591
|
-
projection.root
|
|
11543
|
+
projection.root?.updateScroll();
|
|
11592
11544
|
projection.updateLayout();
|
|
11593
11545
|
/**
|
|
11594
11546
|
* Reset constraints so resolveConstraints() will recalculate them
|
|
@@ -11665,8 +11617,8 @@
|
|
|
11665
11617
|
};
|
|
11666
11618
|
const { projection } = this.visualElement;
|
|
11667
11619
|
const stopMeasureLayoutListener = projection.addEventListener("measure", measureDragConstraints);
|
|
11668
|
-
if (
|
|
11669
|
-
projection.root
|
|
11620
|
+
if (!projection.layout) {
|
|
11621
|
+
projection.root?.updateScroll();
|
|
11670
11622
|
projection.updateLayout();
|
|
11671
11623
|
}
|
|
11672
11624
|
frame.read(measureDragConstraints);
|
|
@@ -11701,22 +11653,14 @@
|
|
|
11701
11653
|
stopResizeListener();
|
|
11702
11654
|
stopPointerListener();
|
|
11703
11655
|
stopMeasureLayoutListener();
|
|
11704
|
-
stopLayoutUpdateListener
|
|
11705
|
-
stopResizeObservers
|
|
11656
|
+
stopLayoutUpdateListener();
|
|
11657
|
+
stopResizeObservers?.();
|
|
11706
11658
|
};
|
|
11707
11659
|
}
|
|
11708
11660
|
getProps() {
|
|
11709
11661
|
const props = this.readProps();
|
|
11710
|
-
const {
|
|
11711
|
-
return {
|
|
11712
|
-
...props,
|
|
11713
|
-
drag,
|
|
11714
|
-
dragDirectionLock,
|
|
11715
|
-
dragPropagation,
|
|
11716
|
-
dragConstraints,
|
|
11717
|
-
dragElastic,
|
|
11718
|
-
dragMomentum,
|
|
11719
|
-
};
|
|
11662
|
+
const { dragElastic = defaultElastic, dragMomentum = true } = props;
|
|
11663
|
+
return { ...props, dragElastic, dragMomentum };
|
|
11720
11664
|
}
|
|
11721
11665
|
}
|
|
11722
11666
|
/**
|
|
@@ -14051,6 +13995,12 @@
|
|
|
14051
13995
|
}
|
|
14052
13996
|
|
|
14053
13997
|
const reversePriorityOrder = [...variantPriorityOrder].reverse();
|
|
13998
|
+
/**
|
|
13999
|
+
* The transition a value uses when it leaves a state (#1725): the state's
|
|
14000
|
+
* own transition when `out: true`, or the `out` transition itself.
|
|
14001
|
+
* Value-specific keys are resolved by animateTarget.
|
|
14002
|
+
*/
|
|
14003
|
+
const getOutTransition = (transition) => transition?.out === true ? transition : transition?.out;
|
|
14054
14004
|
/**
|
|
14055
14005
|
* Diffs the element's animation props (one layer per AnimationType, highest
|
|
14056
14006
|
* priority wins each value) against the previous call and starts animations
|
|
@@ -14072,13 +14022,18 @@
|
|
|
14072
14022
|
let wasReset = false;
|
|
14073
14023
|
function animateChanges(changedActiveType) {
|
|
14074
14024
|
const { props, parent, manuallyAnimateOnMount } = visualElement;
|
|
14025
|
+
const { initial } = props;
|
|
14075
14026
|
const isMounting = isInitialRender || wasReset;
|
|
14076
14027
|
const animations = [];
|
|
14028
|
+
const resolveInitial = (definition = initial) => (typeof definition !== "boolean" &&
|
|
14029
|
+
resolveVariant(visualElement, Array.isArray(definition) ? definition[0] : definition, visualElement.presenceContext?.custom)) ||
|
|
14030
|
+
undefined;
|
|
14077
14031
|
/**
|
|
14078
|
-
* Values removed from a type
|
|
14079
|
-
*
|
|
14032
|
+
* Values removed from a type, mapped to the out transition of the
|
|
14033
|
+
* state they're leaving. A lower-priority type that defines them
|
|
14034
|
+
* animates them back, otherwise they fall back to a base target.
|
|
14080
14035
|
*/
|
|
14081
|
-
const removedKeys = new
|
|
14036
|
+
const removedKeys = new Map();
|
|
14082
14037
|
/**
|
|
14083
14038
|
* Values claimed by active, higher-priority types.
|
|
14084
14039
|
*/
|
|
@@ -14096,6 +14051,13 @@
|
|
|
14096
14051
|
do
|
|
14097
14052
|
source = source.props.inherit !== false && source.parent;
|
|
14098
14053
|
while (source && !source.isControllingVariants);
|
|
14054
|
+
/**
|
|
14055
|
+
* Mounting leaves initial, so values use its out transition. Like the
|
|
14056
|
+
* other types, variant children inherit initial's label.
|
|
14057
|
+
*/
|
|
14058
|
+
const initialOut = isMounting &&
|
|
14059
|
+
getOutTransition(resolveInitial(initial ?? (source && source.props.initial))
|
|
14060
|
+
?.transition);
|
|
14099
14061
|
reversePriorityOrder.forEach((type, i) => {
|
|
14100
14062
|
const typeState = state[type];
|
|
14101
14063
|
const ownProp = props[type];
|
|
@@ -14107,8 +14069,6 @@
|
|
|
14107
14069
|
: undefined;
|
|
14108
14070
|
const propIsVariant = isVariantLabel(prop);
|
|
14109
14071
|
const activeDelta = type === changedActiveType ? typeState.isActive : null;
|
|
14110
|
-
if (activeDelta === false)
|
|
14111
|
-
removedVariantIndex = i;
|
|
14112
14072
|
/**
|
|
14113
14073
|
* Inherited labels are animated by the parent through
|
|
14114
14074
|
* variantChildren, unless this element mounted after its parent.
|
|
@@ -14123,6 +14083,13 @@
|
|
|
14123
14083
|
typeof prop === "boolean") {
|
|
14124
14084
|
return;
|
|
14125
14085
|
}
|
|
14086
|
+
/**
|
|
14087
|
+
* Set after the skip above, so deactivating a type that has nothing
|
|
14088
|
+
* to animate (e.g. whileTap with no prop) doesn't re-run the variants
|
|
14089
|
+
* below it.
|
|
14090
|
+
*/
|
|
14091
|
+
if (activeDelta === false)
|
|
14092
|
+
removedVariantIndex = i;
|
|
14126
14093
|
/**
|
|
14127
14094
|
* Don't re-resolve a running exit: a changed custom would start
|
|
14128
14095
|
* new value animations that stop the originals, leaving the exit
|
|
@@ -14142,6 +14109,7 @@
|
|
|
14142
14109
|
let handledRemovedValues = false;
|
|
14143
14110
|
const definitionList = Array.isArray(prop) ? prop : [prop];
|
|
14144
14111
|
let resolvedValues = {};
|
|
14112
|
+
let out;
|
|
14145
14113
|
if (activeDelta !== false) {
|
|
14146
14114
|
for (const definition of definitionList) {
|
|
14147
14115
|
const { transition, transitionEnd, ...target } = resolveVariant(visualElement, definition, getTypeCustom(visualElement, type)) || {};
|
|
@@ -14150,6 +14118,11 @@
|
|
|
14150
14118
|
...target,
|
|
14151
14119
|
...transitionEnd,
|
|
14152
14120
|
};
|
|
14121
|
+
/**
|
|
14122
|
+
* One out transition per type. With a list of labels,
|
|
14123
|
+
* the last one that sets out applies to all its values.
|
|
14124
|
+
*/
|
|
14125
|
+
out = getOutTransition(transition) || out;
|
|
14153
14126
|
}
|
|
14154
14127
|
}
|
|
14155
14128
|
const { prevResolvedValues } = typeState;
|
|
@@ -14165,12 +14138,18 @@
|
|
|
14165
14138
|
const valueHasChanged = Array.isArray(next) && Array.isArray(prev)
|
|
14166
14139
|
? !shallowCompare(next, prev) || variantDidChange
|
|
14167
14140
|
: next !== prev;
|
|
14141
|
+
let outTransition;
|
|
14168
14142
|
if (valueHasChanged && (next === undefined || next === null)) {
|
|
14169
|
-
|
|
14143
|
+
/**
|
|
14144
|
+
* The highest-priority state a value leaves wins.
|
|
14145
|
+
*/
|
|
14146
|
+
removedKeys.has(key) ||
|
|
14147
|
+
removedKeys.set(key, typeState.prevOut);
|
|
14170
14148
|
}
|
|
14171
14149
|
else if (valueHasChanged ||
|
|
14172
14150
|
(next !== undefined && removedKeys.has(key))) {
|
|
14173
14151
|
shouldAnimateType = true;
|
|
14152
|
+
outTransition = removedKeys.get(key) || initialOut;
|
|
14174
14153
|
if (removedKeys.delete(key))
|
|
14175
14154
|
handledRemovedValues = true;
|
|
14176
14155
|
typeState.needsAnimating[key] = true;
|
|
@@ -14181,9 +14160,11 @@
|
|
|
14181
14160
|
else {
|
|
14182
14161
|
typeState.protectedKeys[key] = true;
|
|
14183
14162
|
}
|
|
14163
|
+
typeState.out[key] = outTransition;
|
|
14184
14164
|
}
|
|
14185
14165
|
typeState.prevProp = prop;
|
|
14186
14166
|
typeState.prevResolvedValues = resolvedValues;
|
|
14167
|
+
typeState.prevOut = out;
|
|
14187
14168
|
if (typeState.isActive) {
|
|
14188
14169
|
encounteredKeys = { ...encounteredKeys, ...resolvedValues };
|
|
14189
14170
|
}
|
|
@@ -14214,14 +14195,17 @@
|
|
|
14214
14195
|
* longer defines a value it once did, it stays where it is.
|
|
14215
14196
|
*/
|
|
14216
14197
|
if (removedKeys.size) {
|
|
14217
|
-
const { initial } = props;
|
|
14218
14198
|
const fallbackAnimation = {};
|
|
14219
|
-
const resolvedInitial =
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
|
|
14224
|
-
|
|
14199
|
+
const resolvedInitial = resolveInitial();
|
|
14200
|
+
let fallbackTransition = resolvedInitial?.transition;
|
|
14201
|
+
removedKeys.forEach((outTransition, key) => {
|
|
14202
|
+
if (outTransition) {
|
|
14203
|
+
fallbackTransition = {
|
|
14204
|
+
...(fallbackTransition ||
|
|
14205
|
+
visualElement.getDefaultTransition()),
|
|
14206
|
+
[key]: outTransition,
|
|
14207
|
+
};
|
|
14208
|
+
}
|
|
14225
14209
|
const motionValue = visualElement.getValue(key);
|
|
14226
14210
|
if (motionValue)
|
|
14227
14211
|
motionValue.liveStyle = true;
|
|
@@ -14252,6 +14236,9 @@
|
|
|
14252
14236
|
visualElement.baseTarget[key] = motionValue?.get();
|
|
14253
14237
|
}
|
|
14254
14238
|
});
|
|
14239
|
+
if (fallbackTransition) {
|
|
14240
|
+
fallbackAnimation.transition = fallbackTransition;
|
|
14241
|
+
}
|
|
14255
14242
|
animations.push({ animation: fallbackAnimation });
|
|
14256
14243
|
}
|
|
14257
14244
|
const blockAnimation = (isMounting && visualElement.blockInitialAnimation) ||
|
|
@@ -14311,6 +14298,7 @@
|
|
|
14311
14298
|
protectedKeys: {},
|
|
14312
14299
|
needsAnimating: {},
|
|
14313
14300
|
prevResolvedValues: {},
|
|
14301
|
+
out: {},
|
|
14314
14302
|
};
|
|
14315
14303
|
}
|
|
14316
14304
|
return state;
|
|
@@ -14429,6 +14417,18 @@
|
|
|
14429
14417
|
return duration * (repeat + 1) + repeatDelay * repeat;
|
|
14430
14418
|
}
|
|
14431
14419
|
|
|
14420
|
+
/**
|
|
14421
|
+
* The time of a label, or a label with an offset like "label+1" or
|
|
14422
|
+
* "label-0.5". An exact label name wins over an offset.
|
|
14423
|
+
*/
|
|
14424
|
+
function calcLabelTime(position, labels) {
|
|
14425
|
+
if (labels.has(position))
|
|
14426
|
+
return labels.get(position);
|
|
14427
|
+
const [, name, offset] = position.match(/^(.+?)([+-][\d.]+)$/) || [];
|
|
14428
|
+
return labels.has(name)
|
|
14429
|
+
? Math.max(0, labels.get(name) + parseFloat(offset))
|
|
14430
|
+
: undefined;
|
|
14431
|
+
}
|
|
14432
14432
|
/**
|
|
14433
14433
|
* Given a absolute or relative time definition and current/prev time state of the sequence,
|
|
14434
14434
|
* calculate an absolute time for the next keyframes.
|
|
@@ -14447,7 +14447,7 @@
|
|
|
14447
14447
|
return Math.max(0, prev + parseFloat(next.slice(1)));
|
|
14448
14448
|
}
|
|
14449
14449
|
else {
|
|
14450
|
-
return
|
|
14450
|
+
return calcLabelTime(next, labels) ?? current;
|
|
14451
14451
|
}
|
|
14452
14452
|
}
|
|
14453
14453
|
|
|
@@ -14508,7 +14508,7 @@
|
|
|
14508
14508
|
|
|
14509
14509
|
const defaultSegmentEasing = "easeInOut";
|
|
14510
14510
|
const MAX_REPEAT = 20;
|
|
14511
|
-
function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) {
|
|
14511
|
+
function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators, info) {
|
|
14512
14512
|
const defaultDuration = defaultTransition.duration || 0.3;
|
|
14513
14513
|
const animationDefinitions = new Map();
|
|
14514
14514
|
const sequences = new Map();
|
|
@@ -14521,196 +14521,233 @@
|
|
|
14521
14521
|
* Build the timeline by mapping over the sequence array and converting
|
|
14522
14522
|
* the definitions into keyframes and offsets with absolute time values.
|
|
14523
14523
|
* These will later get converted into relative offsets in a second pass.
|
|
14524
|
+
*
|
|
14525
|
+
* Nested sequences are resolved recursively. `base` is the absolute
|
|
14526
|
+
* start of the sequence being resolved, so numeric `at` values inside
|
|
14527
|
+
* a nested sequence are relative to where it starts. Returns the time
|
|
14528
|
+
* the sequence ends.
|
|
14524
14529
|
*/
|
|
14525
|
-
|
|
14526
|
-
|
|
14527
|
-
|
|
14528
|
-
|
|
14529
|
-
|
|
14530
|
-
|
|
14531
|
-
|
|
14532
|
-
continue;
|
|
14533
|
-
}
|
|
14534
|
-
else if (!Array.isArray(segment)) {
|
|
14535
|
-
timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels));
|
|
14536
|
-
continue;
|
|
14537
|
-
}
|
|
14538
|
-
let [subject, keyframes, transition = {}] = segment;
|
|
14539
|
-
/**
|
|
14540
|
-
* If a relative or absolute time value has been specified we need to resolve
|
|
14541
|
-
* it in relation to the currentTime.
|
|
14542
|
-
*/
|
|
14543
|
-
if (transition.at !== undefined) {
|
|
14544
|
-
currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels);
|
|
14545
|
-
}
|
|
14546
|
-
/**
|
|
14547
|
-
* Keep track of the maximum duration in this definition. This will be
|
|
14548
|
-
* applied to currentTime once the definition has been parsed.
|
|
14549
|
-
*/
|
|
14550
|
-
let maxDuration = 0;
|
|
14551
|
-
const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
|
|
14552
|
-
const valueKeyframesAsList = keyframesAsList(valueKeyframes);
|
|
14553
|
-
const { delay = 0, times = defaultOffset$1(valueKeyframesAsList), type = defaultTransition.type || "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
|
|
14554
|
-
let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
|
|
14530
|
+
const resolveSequence = (sequence, base) => {
|
|
14531
|
+
let sequenceEnd = base;
|
|
14532
|
+
const resolveTime = (at) => typeof at === "number"
|
|
14533
|
+
? base + at
|
|
14534
|
+
: calcNextTime(currentTime, at, prevTime, timeLabels);
|
|
14535
|
+
for (let i = 0; i < sequence.length; i++) {
|
|
14536
|
+
const segment = sequence[i];
|
|
14555
14537
|
/**
|
|
14556
|
-
*
|
|
14538
|
+
* If this is a timeline label, mark it and skip the rest of this iteration.
|
|
14557
14539
|
*/
|
|
14558
|
-
|
|
14559
|
-
|
|
14560
|
-
|
|
14561
|
-
|
|
14562
|
-
|
|
14563
|
-
|
|
14564
|
-
|
|
14565
|
-
|
|
14566
|
-
|
|
14567
|
-
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
if (numKeyframes === 2 &&
|
|
14577
|
-
isNumberKeyframesArray(valueKeyframesAsList)) {
|
|
14578
|
-
const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
|
|
14579
|
-
absoluteDelta = Math.abs(delta);
|
|
14540
|
+
if (typeof segment === "string") {
|
|
14541
|
+
timeLabels.set(segment, currentTime);
|
|
14542
|
+
continue;
|
|
14543
|
+
}
|
|
14544
|
+
else if (!Array.isArray(segment)) {
|
|
14545
|
+
const start = segment.at === undefined
|
|
14546
|
+
? currentTime
|
|
14547
|
+
: resolveTime(segment.at);
|
|
14548
|
+
if ("sequence" in segment) {
|
|
14549
|
+
/**
|
|
14550
|
+
* Resolve the nested sequence from its start. Afterwards
|
|
14551
|
+
* "<" points at its start and the next segment follows
|
|
14552
|
+
* its end, as if it were a single segment.
|
|
14553
|
+
*/
|
|
14554
|
+
prevTime = currentTime = start;
|
|
14555
|
+
currentTime = resolveSequence(segment.sequence, start);
|
|
14556
|
+
prevTime = start;
|
|
14557
|
+
sequenceEnd = Math.max(sequenceEnd, currentTime);
|
|
14580
14558
|
}
|
|
14581
|
-
|
|
14582
|
-
|
|
14583
|
-
...remainingTransition,
|
|
14584
|
-
};
|
|
14585
|
-
if (duration !== undefined) {
|
|
14586
|
-
springTransition.duration = secondsToMilliseconds(duration);
|
|
14559
|
+
else {
|
|
14560
|
+
timeLabels.set(segment.name, start);
|
|
14587
14561
|
}
|
|
14588
|
-
|
|
14589
|
-
ease = springEasing.ease;
|
|
14590
|
-
duration = springEasing.duration;
|
|
14562
|
+
continue;
|
|
14591
14563
|
}
|
|
14592
|
-
|
|
14593
|
-
const startTime = currentTime + calculatedDelay;
|
|
14564
|
+
let [subject, keyframes, transition = {}] = segment;
|
|
14594
14565
|
/**
|
|
14595
|
-
* If
|
|
14566
|
+
* If a relative or absolute time value has been specified we need to resolve
|
|
14567
|
+
* it in relation to the currentTime.
|
|
14596
14568
|
*/
|
|
14597
|
-
if (
|
|
14598
|
-
|
|
14569
|
+
if (transition.at !== undefined) {
|
|
14570
|
+
currentTime = resolveTime(transition.at);
|
|
14599
14571
|
}
|
|
14600
14572
|
/**
|
|
14601
|
-
*
|
|
14602
|
-
|
|
14603
|
-
const remainder = times.length - valueKeyframesAsList.length;
|
|
14604
|
-
remainder > 0 && fillOffset(times, remainder);
|
|
14605
|
-
/**
|
|
14606
|
-
* If only one value has been set, ie [1], push a null to the start of
|
|
14607
|
-
* the keyframe array. This will let us mark a keyframe at this point
|
|
14608
|
-
* that will later be hydrated with the previous value.
|
|
14609
|
-
*/
|
|
14610
|
-
valueKeyframesAsList.length === 1 &&
|
|
14611
|
-
valueKeyframesAsList.unshift(null);
|
|
14612
|
-
/**
|
|
14613
|
-
* Segments can't express `repeat: Infinity` or very large
|
|
14614
|
-
* counts — they'd leave dead time after the segment or
|
|
14615
|
-
* explode the keyframe array. Ignore with a warning.
|
|
14573
|
+
* Keep track of the maximum duration in this definition. This will be
|
|
14574
|
+
* applied to currentTime once the definition has been parsed.
|
|
14616
14575
|
*/
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14576
|
+
let maxDuration = 0;
|
|
14577
|
+
const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
|
|
14578
|
+
const valueKeyframesAsList = keyframesAsList(valueKeyframes);
|
|
14579
|
+
const { delay = 0, times = defaultOffset$1(valueKeyframesAsList), type = defaultTransition.type || "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
|
|
14580
|
+
let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
|
|
14621
14581
|
/**
|
|
14622
|
-
*
|
|
14623
|
-
* so it can be added to the per-iteration time offsets below
|
|
14624
|
-
* before they're normalized to 0-1.
|
|
14582
|
+
* Resolve stagger() if defined.
|
|
14625
14583
|
*/
|
|
14626
|
-
const
|
|
14627
|
-
|
|
14628
|
-
|
|
14629
|
-
const originalTimes = [...times];
|
|
14630
|
-
ease = Array.isArray(ease) ? [...ease] : [ease];
|
|
14631
|
-
const originalEase = [...ease];
|
|
14584
|
+
const calculatedDelay = typeof delay === "function"
|
|
14585
|
+
? delay(elementIndex, numSubjects)
|
|
14586
|
+
: delay;
|
|
14632
14587
|
/**
|
|
14633
|
-
*
|
|
14634
|
-
* backwards. mirror matches JSAnimation's mirroredGenerator:
|
|
14635
|
-
* reversed keyframes, easings unchanged. reverse matches
|
|
14636
|
-
* JSAnimation's iterationProgress = 1 - p: reversed
|
|
14637
|
-
* keyframes, easing array reversed AND each function easing
|
|
14638
|
-
* mapped through reverseEasing (string easings unchanged —
|
|
14639
|
-
* they're resolved later by the keyframes engine).
|
|
14588
|
+
* If this animation should and can use a spring, generate a spring easing function.
|
|
14640
14589
|
*/
|
|
14641
|
-
const
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
|
|
14651
|
-
|
|
14590
|
+
const numKeyframes = valueKeyframesAsList.length;
|
|
14591
|
+
const createGenerator = isGenerator(type)
|
|
14592
|
+
? type
|
|
14593
|
+
: generators?.[type || "keyframes"];
|
|
14594
|
+
if (numKeyframes <= 2 && createGenerator) {
|
|
14595
|
+
/**
|
|
14596
|
+
* As we're creating an easing function from a spring,
|
|
14597
|
+
* ideally we want to generate it using the real distance
|
|
14598
|
+
* between the two keyframes. However this isn't always
|
|
14599
|
+
* possible - in these situations we use 0-100.
|
|
14600
|
+
*/
|
|
14601
|
+
let absoluteDelta = 100;
|
|
14602
|
+
if (numKeyframes === 2 &&
|
|
14603
|
+
isNumberKeyframesArray(valueKeyframesAsList)) {
|
|
14604
|
+
const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
|
|
14605
|
+
absoluteDelta = Math.abs(delta);
|
|
14606
|
+
}
|
|
14607
|
+
const springTransition = {
|
|
14608
|
+
...defaultTransition,
|
|
14609
|
+
...remainingTransition,
|
|
14610
|
+
};
|
|
14611
|
+
if (duration !== undefined) {
|
|
14612
|
+
springTransition.duration =
|
|
14613
|
+
secondsToMilliseconds(duration);
|
|
14652
14614
|
}
|
|
14615
|
+
const springEasing = createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
|
|
14616
|
+
ease = springEasing.ease;
|
|
14617
|
+
duration = springEasing.duration;
|
|
14618
|
+
}
|
|
14619
|
+
duration ?? (duration = defaultDuration);
|
|
14620
|
+
const startTime = currentTime + calculatedDelay;
|
|
14621
|
+
/**
|
|
14622
|
+
* If there's only one time offset of 0, fill in a second with length 1
|
|
14623
|
+
*/
|
|
14624
|
+
if (times.length === 1 && times[0] === 0) {
|
|
14625
|
+
times[1] = 1;
|
|
14653
14626
|
}
|
|
14654
|
-
|
|
14655
|
-
|
|
14656
|
-
|
|
14657
|
-
|
|
14658
|
-
|
|
14659
|
-
|
|
14660
|
-
|
|
14627
|
+
/**
|
|
14628
|
+
* Fill out if offset if fewer offsets than keyframes
|
|
14629
|
+
*/
|
|
14630
|
+
const remainder = times.length - valueKeyframesAsList.length;
|
|
14631
|
+
remainder > 0 && fillOffset(times, remainder);
|
|
14632
|
+
/**
|
|
14633
|
+
* If only one value has been set, ie [1], push a null to the start of
|
|
14634
|
+
* the keyframe array. This will let us mark a keyframe at this point
|
|
14635
|
+
* that will later be hydrated with the previous value.
|
|
14636
|
+
*/
|
|
14637
|
+
valueKeyframesAsList.length === 1 &&
|
|
14638
|
+
valueKeyframesAsList.unshift(null);
|
|
14639
|
+
/**
|
|
14640
|
+
* Segments can't express `repeat: Infinity` or very large
|
|
14641
|
+
* counts — they'd leave dead time after the segment or
|
|
14642
|
+
* explode the keyframe array. Ignore with a warning.
|
|
14643
|
+
*/
|
|
14644
|
+
if (repeat) {
|
|
14645
|
+
typeof process !== "undefined" &&
|
|
14646
|
+
"development" !== "production" &&
|
|
14647
|
+
exports.warning(repeat < MAX_REPEAT, `Sequence segments can't repeat ${repeat} times — ignoring repeat option. Use a value below ${MAX_REPEAT} or apply repeat at the sequence level instead.`);
|
|
14648
|
+
}
|
|
14649
|
+
if (repeat && repeat < MAX_REPEAT) {
|
|
14661
14650
|
/**
|
|
14662
|
-
*
|
|
14663
|
-
*
|
|
14664
|
-
*
|
|
14651
|
+
* Express repeatDelay in units of a single iteration's duration
|
|
14652
|
+
* so it can be added to the per-iteration time offsets below
|
|
14653
|
+
* before they're normalized to 0-1.
|
|
14665
14654
|
*/
|
|
14666
|
-
|
|
14667
|
-
|
|
14668
|
-
|
|
14669
|
-
|
|
14655
|
+
const repeatDelayUnits = duration > 0 ? repeatDelay / duration : 0;
|
|
14656
|
+
duration = calculateRepeatDuration(duration, repeat, repeatDelay);
|
|
14657
|
+
const originalKeyframes = [...valueKeyframesAsList];
|
|
14658
|
+
const originalTimes = [...times];
|
|
14659
|
+
ease = Array.isArray(ease) ? [...ease] : [ease];
|
|
14660
|
+
const originalEase = [...ease];
|
|
14661
|
+
/**
|
|
14662
|
+
* For reverse/mirror, alternate iterations play the segment
|
|
14663
|
+
* backwards. mirror matches JSAnimation's mirroredGenerator:
|
|
14664
|
+
* reversed keyframes, easings unchanged. reverse matches
|
|
14665
|
+
* JSAnimation's iterationProgress = 1 - p: reversed
|
|
14666
|
+
* keyframes, easing array reversed AND each function easing
|
|
14667
|
+
* mapped through reverseEasing (string easings unchanged —
|
|
14668
|
+
* they're resolved later by the keyframes engine).
|
|
14669
|
+
*/
|
|
14670
|
+
const isFlipping = repeatType === "reverse" || repeatType === "mirror";
|
|
14671
|
+
let flippedKeyframes = originalKeyframes;
|
|
14672
|
+
let flippedEases = originalEase;
|
|
14673
|
+
if (isFlipping) {
|
|
14674
|
+
flippedKeyframes = [...originalKeyframes].reverse();
|
|
14675
|
+
if (repeatType === "reverse") {
|
|
14676
|
+
flippedEases = [...originalEase]
|
|
14677
|
+
.reverse()
|
|
14678
|
+
.map((e) => typeof e === "function"
|
|
14679
|
+
? reverseEasing(e)
|
|
14680
|
+
: e);
|
|
14681
|
+
}
|
|
14670
14682
|
}
|
|
14671
|
-
|
|
14672
|
-
|
|
14673
|
-
|
|
14674
|
-
|
|
14675
|
-
|
|
14676
|
-
|
|
14683
|
+
for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
|
|
14684
|
+
const isFlipped = isFlipping && repeatIndex % 2 === 0;
|
|
14685
|
+
const iterKeyframes = isFlipped
|
|
14686
|
+
? flippedKeyframes
|
|
14687
|
+
: originalKeyframes;
|
|
14688
|
+
const iterEase = isFlipped ? flippedEases : originalEase;
|
|
14689
|
+
const iterStartOffset = (repeatIndex + 1) * (1 + repeatDelayUnits);
|
|
14690
|
+
/**
|
|
14691
|
+
* If repeatDelay is set, hold the previous iteration's
|
|
14692
|
+
* final value through the delay by inserting a keyframe
|
|
14693
|
+
* at the moment the next iteration begins.
|
|
14694
|
+
*/
|
|
14695
|
+
if (repeatDelayUnits > 0) {
|
|
14696
|
+
valueKeyframesAsList.push(valueKeyframesAsList[valueKeyframesAsList.length - 1]);
|
|
14697
|
+
times.push(iterStartOffset);
|
|
14698
|
+
ease.push("linear");
|
|
14699
|
+
}
|
|
14700
|
+
valueKeyframesAsList.push(...iterKeyframes);
|
|
14701
|
+
for (let keyframeIndex = 0; keyframeIndex < iterKeyframes.length; keyframeIndex++) {
|
|
14702
|
+
times.push(originalTimes[keyframeIndex] + iterStartOffset);
|
|
14703
|
+
ease.push(keyframeIndex === 0
|
|
14704
|
+
? "linear"
|
|
14705
|
+
: getEasingForSegment(iterEase, keyframeIndex - 1));
|
|
14706
|
+
}
|
|
14677
14707
|
}
|
|
14708
|
+
normalizeTimes(times, repeat, repeatDelayUnits);
|
|
14678
14709
|
}
|
|
14679
|
-
|
|
14710
|
+
const targetTime = startTime + duration;
|
|
14711
|
+
/**
|
|
14712
|
+
* Add keyframes, mapping offsets to absolute time.
|
|
14713
|
+
*/
|
|
14714
|
+
addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
|
|
14715
|
+
maxDuration = Math.max(calculatedDelay + duration, maxDuration);
|
|
14716
|
+
totalDuration = Math.max(targetTime, totalDuration);
|
|
14717
|
+
};
|
|
14718
|
+
if (isMotionValue(subject)) {
|
|
14719
|
+
const subjectSequence = getSubjectSequence(subject, sequences);
|
|
14720
|
+
resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
|
|
14680
14721
|
}
|
|
14681
|
-
|
|
14682
|
-
|
|
14683
|
-
|
|
14684
|
-
*/
|
|
14685
|
-
addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
|
|
14686
|
-
maxDuration = Math.max(calculatedDelay + duration, maxDuration);
|
|
14687
|
-
totalDuration = Math.max(targetTime, totalDuration);
|
|
14688
|
-
};
|
|
14689
|
-
if (isMotionValue(subject)) {
|
|
14690
|
-
const subjectSequence = getSubjectSequence(subject, sequences);
|
|
14691
|
-
resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
|
|
14692
|
-
}
|
|
14693
|
-
else {
|
|
14694
|
-
const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
|
|
14695
|
-
const numSubjects = subjects.length;
|
|
14696
|
-
/**
|
|
14697
|
-
* For every element in this segment, process the defined values.
|
|
14698
|
-
*/
|
|
14699
|
-
for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
|
|
14722
|
+
else {
|
|
14723
|
+
const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
|
|
14724
|
+
const numSubjects = subjects.length;
|
|
14700
14725
|
/**
|
|
14701
|
-
*
|
|
14726
|
+
* For every element in this segment, process the defined values.
|
|
14702
14727
|
*/
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
|
|
14706
|
-
|
|
14707
|
-
|
|
14708
|
-
|
|
14728
|
+
for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
|
|
14729
|
+
/**
|
|
14730
|
+
* Cast necessary, but we know these are of this type
|
|
14731
|
+
*/
|
|
14732
|
+
keyframes = keyframes;
|
|
14733
|
+
transition = transition;
|
|
14734
|
+
const thisSubject = subjects[subjectIndex];
|
|
14735
|
+
const subjectSequence = getSubjectSequence(thisSubject, sequences);
|
|
14736
|
+
for (const key in keyframes) {
|
|
14737
|
+
resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
|
|
14738
|
+
}
|
|
14709
14739
|
}
|
|
14710
14740
|
}
|
|
14741
|
+
prevTime = currentTime;
|
|
14742
|
+
currentTime += maxDuration;
|
|
14743
|
+
sequenceEnd = Math.max(sequenceEnd, currentTime);
|
|
14711
14744
|
}
|
|
14712
|
-
|
|
14713
|
-
|
|
14745
|
+
return sequenceEnd;
|
|
14746
|
+
};
|
|
14747
|
+
resolveSequence(sequence, 0);
|
|
14748
|
+
if (info) {
|
|
14749
|
+
info.duration = totalDuration;
|
|
14750
|
+
info.labels = timeLabels;
|
|
14714
14751
|
}
|
|
14715
14752
|
/**
|
|
14716
14753
|
* For every element and value combination create a new animation.
|
|
@@ -14858,7 +14895,7 @@
|
|
|
14858
14895
|
return animations;
|
|
14859
14896
|
}
|
|
14860
14897
|
|
|
14861
|
-
function animateSequence(sequence, options, scope) {
|
|
14898
|
+
function animateSequence(sequence, options, scope, info) {
|
|
14862
14899
|
const animations = [];
|
|
14863
14900
|
/**
|
|
14864
14901
|
* Pre-process: replace function segments with MotionValue segments,
|
|
@@ -14881,13 +14918,515 @@
|
|
|
14881
14918
|
}
|
|
14882
14919
|
return segment;
|
|
14883
14920
|
});
|
|
14884
|
-
const animationDefinitions = createAnimationsFromSequence(processedSequence, options, scope, { spring });
|
|
14921
|
+
const animationDefinitions = createAnimationsFromSequence(processedSequence, options, scope, { spring }, info);
|
|
14885
14922
|
animationDefinitions.forEach(({ keyframes, transition }, subject) => {
|
|
14886
14923
|
animations.push(...animateSubject(subject, keyframes, transition));
|
|
14887
14924
|
});
|
|
14888
14925
|
return animations;
|
|
14889
14926
|
}
|
|
14890
14927
|
|
|
14928
|
+
let markerId = 0;
|
|
14929
|
+
/**
|
|
14930
|
+
* Copy a segment with a new `at`, adding a transition if it has none.
|
|
14931
|
+
*/
|
|
14932
|
+
function withAt(segment, at) {
|
|
14933
|
+
const copy = [...segment];
|
|
14934
|
+
const i = typeof copy[0] === "function" ? Math.max(1, copy.length - 1) : 2;
|
|
14935
|
+
copy[i] = { ...copy[i], at };
|
|
14936
|
+
return copy;
|
|
14937
|
+
}
|
|
14938
|
+
const label = (name, at) => at === undefined ? name : { name, at };
|
|
14939
|
+
/**
|
|
14940
|
+
* Rewind to the start and stop, so every value is back at the value it
|
|
14941
|
+
* started from and the next build reads those values as its origin.
|
|
14942
|
+
*/
|
|
14943
|
+
function rewind(animation) {
|
|
14944
|
+
animation.pause();
|
|
14945
|
+
animation.time = 0;
|
|
14946
|
+
animation.stop();
|
|
14947
|
+
}
|
|
14948
|
+
/**
|
|
14949
|
+
* A sequence that can be changed after it's created. Segments, labels,
|
|
14950
|
+
* callbacks, pauses and other timelines can be added, moved and removed
|
|
14951
|
+
* at any point, including while it plays.
|
|
14952
|
+
*
|
|
14953
|
+
* Changes are batched and applied once per microtask. Each change
|
|
14954
|
+
* rebuilds the underlying animations from the timeline's starting
|
|
14955
|
+
* values, then restores the playhead, speed and play state, so changes
|
|
14956
|
+
* made while playing take effect without a jump.
|
|
14957
|
+
*/
|
|
14958
|
+
class Timeline {
|
|
14959
|
+
/**
|
|
14960
|
+
* `animations` are already running animations of `items`, as
|
|
14961
|
+
* animate() makes them. The timeline plays these until it's first
|
|
14962
|
+
* changed, so it costs nothing over the animations themselves until
|
|
14963
|
+
* then.
|
|
14964
|
+
*/
|
|
14965
|
+
constructor(items = [], options = {}, animations) {
|
|
14966
|
+
this.options = options;
|
|
14967
|
+
this.entries = [];
|
|
14968
|
+
/**
|
|
14969
|
+
* The repeat the playhead is in, to detect when it starts another.
|
|
14970
|
+
*/
|
|
14971
|
+
this.repeatIndex = 0;
|
|
14972
|
+
this.prevTime = 0;
|
|
14973
|
+
this.markers = [];
|
|
14974
|
+
this.labels = new Map();
|
|
14975
|
+
this.totalDuration = 0;
|
|
14976
|
+
this.isDirty = false;
|
|
14977
|
+
this.isDone = false;
|
|
14978
|
+
this.isRebuilding = false;
|
|
14979
|
+
this.playbackSpeed = 1;
|
|
14980
|
+
/**
|
|
14981
|
+
* True while this plays the animations it was created with, before
|
|
14982
|
+
* any change compiles it as a sequence.
|
|
14983
|
+
*/
|
|
14984
|
+
this.isWrapped = false;
|
|
14985
|
+
this.stop = () => {
|
|
14986
|
+
this.flush();
|
|
14987
|
+
this.group?.stop();
|
|
14988
|
+
};
|
|
14989
|
+
this.isPaused = options.autoplay === false;
|
|
14990
|
+
this.newFinished();
|
|
14991
|
+
items.forEach((item) => this.add(item));
|
|
14992
|
+
if (animations) {
|
|
14993
|
+
this.isDirty = false;
|
|
14994
|
+
this.isWrapped = true;
|
|
14995
|
+
this.group = new GroupAnimationWithThen(animations);
|
|
14996
|
+
this.watch();
|
|
14997
|
+
}
|
|
14998
|
+
else {
|
|
14999
|
+
/**
|
|
15000
|
+
* Build now, like animate(), so selectors resolve while a
|
|
15001
|
+
* scope is attached. Later changes are batched.
|
|
15002
|
+
*/
|
|
15003
|
+
this.invalidate();
|
|
15004
|
+
this.flush();
|
|
15005
|
+
}
|
|
15006
|
+
/**
|
|
15007
|
+
* Register with the scope (for instance from useAnimate()) so it
|
|
15008
|
+
* stops when the scope is torn down.
|
|
15009
|
+
*/
|
|
15010
|
+
const { scope } = options;
|
|
15011
|
+
if (scope) {
|
|
15012
|
+
scope.animations.push(this);
|
|
15013
|
+
this.finished.then(() => removeItem(scope.animations, this));
|
|
15014
|
+
}
|
|
15015
|
+
}
|
|
15016
|
+
/**
|
|
15017
|
+
* Add a segment, label or timeline. `at` overrides any `at` already
|
|
15018
|
+
* on the segment. The item itself is the handle for `move()` and
|
|
15019
|
+
* `remove()`.
|
|
15020
|
+
*/
|
|
15021
|
+
add(item, at) {
|
|
15022
|
+
if (item instanceof Timeline)
|
|
15023
|
+
item.adopt(this);
|
|
15024
|
+
return this.push({ item, at });
|
|
15025
|
+
}
|
|
15026
|
+
/**
|
|
15027
|
+
* Fire a callback when the playhead crosses `at`, in either direction.
|
|
15028
|
+
* It receives the direction: 1 forwards, -1 backwards.
|
|
15029
|
+
*/
|
|
15030
|
+
call(callback, at) {
|
|
15031
|
+
return this.push({
|
|
15032
|
+
item: callback,
|
|
15033
|
+
at,
|
|
15034
|
+
marker: { name: "_m" + markerId++, time: 0, callback },
|
|
15035
|
+
});
|
|
15036
|
+
}
|
|
15037
|
+
/**
|
|
15038
|
+
* Pause when the playhead reaches `at`.
|
|
15039
|
+
*/
|
|
15040
|
+
addPause(at, callback) {
|
|
15041
|
+
return this.push({
|
|
15042
|
+
item: callback || {},
|
|
15043
|
+
at,
|
|
15044
|
+
marker: { name: "_m" + markerId++, time: 0, callback, pause: true },
|
|
15045
|
+
});
|
|
15046
|
+
}
|
|
15047
|
+
/**
|
|
15048
|
+
* Change where an item starts.
|
|
15049
|
+
*/
|
|
15050
|
+
move(item, at) {
|
|
15051
|
+
this.entries.forEach((entry) => {
|
|
15052
|
+
if (entry.item === item)
|
|
15053
|
+
entry.at = at;
|
|
15054
|
+
});
|
|
15055
|
+
return this.invalidate();
|
|
15056
|
+
}
|
|
15057
|
+
/**
|
|
15058
|
+
* Remove a segment, label, callback or timeline.
|
|
15059
|
+
*/
|
|
15060
|
+
remove(item) {
|
|
15061
|
+
if (item instanceof Timeline)
|
|
15062
|
+
item.parent = undefined;
|
|
15063
|
+
this.entries = this.entries.filter((entry) => entry.item !== item);
|
|
15064
|
+
return this.invalidate();
|
|
15065
|
+
}
|
|
15066
|
+
clear() {
|
|
15067
|
+
this.entries = [];
|
|
15068
|
+
return this.invalidate();
|
|
15069
|
+
}
|
|
15070
|
+
/**
|
|
15071
|
+
* The time of a label in seconds, or `undefined` if there's no label
|
|
15072
|
+
* with this name.
|
|
15073
|
+
*/
|
|
15074
|
+
getLabelTime(name) {
|
|
15075
|
+
this.flush();
|
|
15076
|
+
return this.labels.get(name);
|
|
15077
|
+
}
|
|
15078
|
+
/**
|
|
15079
|
+
* Jump to a time in seconds or a label.
|
|
15080
|
+
*/
|
|
15081
|
+
seek(position) {
|
|
15082
|
+
this.time = this.resolve(position);
|
|
15083
|
+
return this;
|
|
15084
|
+
}
|
|
15085
|
+
/**
|
|
15086
|
+
* Play forwards or backwards to a time or label, then pause there.
|
|
15087
|
+
*/
|
|
15088
|
+
playTo(position) {
|
|
15089
|
+
const target = this.resolve(position);
|
|
15090
|
+
const speed = Math.abs(this.speed);
|
|
15091
|
+
this.speed = target < this.time ? -speed : speed;
|
|
15092
|
+
this.stopAt = target;
|
|
15093
|
+
this.clock || this.invalidate();
|
|
15094
|
+
this.play();
|
|
15095
|
+
return this;
|
|
15096
|
+
}
|
|
15097
|
+
reverse() {
|
|
15098
|
+
this.speed = -this.speed;
|
|
15099
|
+
this.play();
|
|
15100
|
+
return this;
|
|
15101
|
+
}
|
|
15102
|
+
get progress() {
|
|
15103
|
+
return this.duration ? this.time / this.duration : 0;
|
|
15104
|
+
}
|
|
15105
|
+
set progress(progress) {
|
|
15106
|
+
this.time = progress * this.duration;
|
|
15107
|
+
}
|
|
15108
|
+
get time() {
|
|
15109
|
+
return this.active()?.time ?? 0;
|
|
15110
|
+
}
|
|
15111
|
+
set time(time) {
|
|
15112
|
+
this.flush();
|
|
15113
|
+
this.prevTime = time;
|
|
15114
|
+
this.group && (this.group.time = time);
|
|
15115
|
+
this.repeatIndex = this.currentRepeat();
|
|
15116
|
+
}
|
|
15117
|
+
get speed() {
|
|
15118
|
+
return this.playbackSpeed;
|
|
15119
|
+
}
|
|
15120
|
+
set speed(speed) {
|
|
15121
|
+
this.playbackSpeed = speed;
|
|
15122
|
+
this.group && (this.group.speed = speed);
|
|
15123
|
+
}
|
|
15124
|
+
get duration() {
|
|
15125
|
+
this.flush();
|
|
15126
|
+
return this.isWrapped ? this.group.duration : this.totalDuration;
|
|
15127
|
+
}
|
|
15128
|
+
get iterationDuration() {
|
|
15129
|
+
this.flush();
|
|
15130
|
+
return this.isWrapped
|
|
15131
|
+
? this.group.iterationDuration
|
|
15132
|
+
: this.totalDuration;
|
|
15133
|
+
}
|
|
15134
|
+
get state() {
|
|
15135
|
+
return this.active()?.state ?? "idle";
|
|
15136
|
+
}
|
|
15137
|
+
get startTime() {
|
|
15138
|
+
return this.active()?.startTime ?? null;
|
|
15139
|
+
}
|
|
15140
|
+
play() {
|
|
15141
|
+
this.flush();
|
|
15142
|
+
this.isPaused = false;
|
|
15143
|
+
this.group?.play();
|
|
15144
|
+
/**
|
|
15145
|
+
* Watch after playing, so it waits for the restarted animations
|
|
15146
|
+
* rather than the ones that already finished.
|
|
15147
|
+
*/
|
|
15148
|
+
if (this.isDone) {
|
|
15149
|
+
this.newFinished();
|
|
15150
|
+
this.watch();
|
|
15151
|
+
}
|
|
15152
|
+
}
|
|
15153
|
+
pause() {
|
|
15154
|
+
this.flush();
|
|
15155
|
+
this.isPaused = true;
|
|
15156
|
+
this.group?.pause();
|
|
15157
|
+
}
|
|
15158
|
+
cancel() {
|
|
15159
|
+
this.flush();
|
|
15160
|
+
this.group?.cancel();
|
|
15161
|
+
}
|
|
15162
|
+
complete() {
|
|
15163
|
+
this.flush();
|
|
15164
|
+
this.group?.complete();
|
|
15165
|
+
}
|
|
15166
|
+
attachTimeline(timeline) {
|
|
15167
|
+
this.flush();
|
|
15168
|
+
this.scrollTimeline = timeline;
|
|
15169
|
+
this.detachScroll = this.group?.attachTimeline(timeline);
|
|
15170
|
+
return () => {
|
|
15171
|
+
this.scrollTimeline = undefined;
|
|
15172
|
+
this.detachScroll?.();
|
|
15173
|
+
};
|
|
15174
|
+
}
|
|
15175
|
+
then(onResolve, onReject) {
|
|
15176
|
+
return this.finished.then(onResolve, onReject);
|
|
15177
|
+
}
|
|
15178
|
+
/**
|
|
15179
|
+
* The current animations, if any are running. Skipped animations
|
|
15180
|
+
* (skipAnimations, reduced motion) can leave the group empty.
|
|
15181
|
+
*/
|
|
15182
|
+
active() {
|
|
15183
|
+
this.flush();
|
|
15184
|
+
return this.group?.animations.length ? this.group : undefined;
|
|
15185
|
+
}
|
|
15186
|
+
push(entry) {
|
|
15187
|
+
this.entries.push(entry);
|
|
15188
|
+
return this.invalidate();
|
|
15189
|
+
}
|
|
15190
|
+
/**
|
|
15191
|
+
* Schedule a rebuild. A nested timeline passes changes up, as it's
|
|
15192
|
+
* built as part of its parent.
|
|
15193
|
+
*/
|
|
15194
|
+
invalidate() {
|
|
15195
|
+
if (this.parent) {
|
|
15196
|
+
this.parent.invalidate();
|
|
15197
|
+
}
|
|
15198
|
+
else if (!this.isDirty) {
|
|
15199
|
+
this.isDirty = true;
|
|
15200
|
+
queueMicrotask(() => this.flush());
|
|
15201
|
+
}
|
|
15202
|
+
return this;
|
|
15203
|
+
}
|
|
15204
|
+
/**
|
|
15205
|
+
* Apply pending changes now, so reads straight after a change
|
|
15206
|
+
* reflect it.
|
|
15207
|
+
*/
|
|
15208
|
+
flush() {
|
|
15209
|
+
this.isDirty && !this.parent && this.build();
|
|
15210
|
+
}
|
|
15211
|
+
adopt(parent) {
|
|
15212
|
+
if (this.group) {
|
|
15213
|
+
rewind(this.group);
|
|
15214
|
+
this.group = undefined;
|
|
15215
|
+
}
|
|
15216
|
+
this.isDirty = false;
|
|
15217
|
+
this.parent = parent;
|
|
15218
|
+
}
|
|
15219
|
+
build() {
|
|
15220
|
+
this.isDirty = false;
|
|
15221
|
+
this.isWrapped = false;
|
|
15222
|
+
const prev = this.group;
|
|
15223
|
+
let time = 0;
|
|
15224
|
+
if (prev) {
|
|
15225
|
+
this.isRebuilding = true;
|
|
15226
|
+
if (this.scrollTimeline) {
|
|
15227
|
+
/**
|
|
15228
|
+
* Scroll-linked animations can't be seeked in seconds.
|
|
15229
|
+
* Cancelling also returns them to their starting values,
|
|
15230
|
+
* and the new build is attached to scroll instead.
|
|
15231
|
+
*/
|
|
15232
|
+
this.detachScroll?.();
|
|
15233
|
+
prev.cancel();
|
|
15234
|
+
}
|
|
15235
|
+
else {
|
|
15236
|
+
time = prev.animations.length ? prev.time : 0;
|
|
15237
|
+
rewind(prev);
|
|
15238
|
+
}
|
|
15239
|
+
this.isRebuilding = false;
|
|
15240
|
+
}
|
|
15241
|
+
const { defaultTransition, onComplete, onUpdate, onRepeat, onReverseComplete, autoplay, scope, ...options } = this.options;
|
|
15242
|
+
/**
|
|
15243
|
+
* Inherit MotionConfig settings from a useAnimate() scope.
|
|
15244
|
+
*/
|
|
15245
|
+
const playback = {};
|
|
15246
|
+
if (scope?.reduceMotion !== undefined) {
|
|
15247
|
+
playback.reduceMotion = scope.reduceMotion;
|
|
15248
|
+
}
|
|
15249
|
+
if (scope?.skipAnimations !== undefined) {
|
|
15250
|
+
playback.skipAnimations = scope.skipAnimations;
|
|
15251
|
+
}
|
|
15252
|
+
Object.assign(playback, options);
|
|
15253
|
+
const markers = new Map();
|
|
15254
|
+
const info = {};
|
|
15255
|
+
const animations = animateSequence(this.toSequence(markers), { defaultTransition, ...playback }, scope, info);
|
|
15256
|
+
const labels = info.labels;
|
|
15257
|
+
let duration = playback.duration ?? info.duration;
|
|
15258
|
+
/**
|
|
15259
|
+
* A `duration` option stretches every animation to fit, so scale
|
|
15260
|
+
* label and marker times to match.
|
|
15261
|
+
*/
|
|
15262
|
+
const scale = info.duration ? duration / info.duration : 1;
|
|
15263
|
+
labels.forEach((time, name) => labels.set(name, time * scale));
|
|
15264
|
+
/**
|
|
15265
|
+
* Markers are resolved as labels. Move them out of the label map
|
|
15266
|
+
* and extend the duration to reach any marker past the end.
|
|
15267
|
+
*/
|
|
15268
|
+
this.markers = [];
|
|
15269
|
+
markers.forEach((marker, name) => {
|
|
15270
|
+
marker.time = labels.get(name);
|
|
15271
|
+
labels.delete(name);
|
|
15272
|
+
duration = Math.max(duration, marker.time);
|
|
15273
|
+
this.markers.push(marker);
|
|
15274
|
+
});
|
|
15275
|
+
this.markers.sort((a, b) => a.time - b.time);
|
|
15276
|
+
this.labels = labels;
|
|
15277
|
+
this.totalDuration = duration;
|
|
15278
|
+
/**
|
|
15279
|
+
* Callbacks, pauses and playTo() watch a linear clock. Without
|
|
15280
|
+
* them, the timeline is only its animations.
|
|
15281
|
+
*/
|
|
15282
|
+
this.clock = this.clockAnimation = undefined;
|
|
15283
|
+
if (this.markers.length ||
|
|
15284
|
+
this.stopAt !== undefined ||
|
|
15285
|
+
onUpdate ||
|
|
15286
|
+
onRepeat) {
|
|
15287
|
+
const clock = (this.clock = motionValue(0));
|
|
15288
|
+
clock.on("change", (t) => clock === this.clock && this.onClock(t));
|
|
15289
|
+
animations.unshift((this.clockAnimation = animateSingleValue(clock, [0, duration], {
|
|
15290
|
+
...playback,
|
|
15291
|
+
duration,
|
|
15292
|
+
ease: "linear",
|
|
15293
|
+
})));
|
|
15294
|
+
}
|
|
15295
|
+
const group = (this.group = new GroupAnimationWithThen(animations));
|
|
15296
|
+
time = Math.min(time, duration);
|
|
15297
|
+
if (this.isDone && time < duration)
|
|
15298
|
+
this.newFinished();
|
|
15299
|
+
if (this.scrollTimeline) {
|
|
15300
|
+
this.detachScroll = group.attachTimeline(this.scrollTimeline);
|
|
15301
|
+
}
|
|
15302
|
+
else {
|
|
15303
|
+
group.speed = this.playbackSpeed;
|
|
15304
|
+
if (this.isPaused)
|
|
15305
|
+
group.pause();
|
|
15306
|
+
if (time)
|
|
15307
|
+
group.time = time;
|
|
15308
|
+
}
|
|
15309
|
+
this.repeatIndex = this.currentRepeat();
|
|
15310
|
+
/**
|
|
15311
|
+
* Treat a start from 0 as coming from just before it, so markers
|
|
15312
|
+
* at 0 fire.
|
|
15313
|
+
*/
|
|
15314
|
+
this.prevTime = time || -1;
|
|
15315
|
+
this.watch();
|
|
15316
|
+
}
|
|
15317
|
+
toSequence(markers) {
|
|
15318
|
+
return this.entries.map(({ item, at, marker }) => {
|
|
15319
|
+
if (marker) {
|
|
15320
|
+
markers.set(marker.name, marker);
|
|
15321
|
+
return label(marker.name, at);
|
|
15322
|
+
}
|
|
15323
|
+
if (item instanceof Timeline) {
|
|
15324
|
+
return { sequence: item.toSequence(markers), at };
|
|
15325
|
+
}
|
|
15326
|
+
if (typeof item === "string")
|
|
15327
|
+
return label(item, at);
|
|
15328
|
+
if (at === undefined)
|
|
15329
|
+
return item;
|
|
15330
|
+
return Array.isArray(item)
|
|
15331
|
+
? withAt(item, at)
|
|
15332
|
+
: { ...item, at };
|
|
15333
|
+
});
|
|
15334
|
+
}
|
|
15335
|
+
resolve(position) {
|
|
15336
|
+
this.flush();
|
|
15337
|
+
return typeof position === "number"
|
|
15338
|
+
? position
|
|
15339
|
+
: calcLabelTime(position, this.labels) ?? this.time;
|
|
15340
|
+
}
|
|
15341
|
+
/**
|
|
15342
|
+
* The repeat the clock is in, from its time across all repeats.
|
|
15343
|
+
*/
|
|
15344
|
+
currentRepeat() {
|
|
15345
|
+
const { repeat = 0, repeatDelay = 0, delay = 0 } = this.options;
|
|
15346
|
+
const cycle = this.totalDuration + repeatDelay;
|
|
15347
|
+
const time = (this.clockAnimation?.time ?? 0) - delay;
|
|
15348
|
+
return cycle && time > 0
|
|
15349
|
+
? Math.min(repeat, Math.floor(time / cycle))
|
|
15350
|
+
: 0;
|
|
15351
|
+
}
|
|
15352
|
+
onClock(time) {
|
|
15353
|
+
let prevTime = this.prevTime;
|
|
15354
|
+
this.prevTime = time;
|
|
15355
|
+
if (this.isRebuilding)
|
|
15356
|
+
return;
|
|
15357
|
+
const { onUpdate, onRepeat, repeatType = "loop" } = this.options;
|
|
15358
|
+
onUpdate?.(time);
|
|
15359
|
+
const repeatIndex = this.currentRepeat();
|
|
15360
|
+
if (repeatIndex !== this.repeatIndex) {
|
|
15361
|
+
this.repeatIndex = repeatIndex;
|
|
15362
|
+
onRepeat?.();
|
|
15363
|
+
/**
|
|
15364
|
+
* A loop jumps the playhead back to the start, or to the
|
|
15365
|
+
* end when playing backwards. Finish crossing the repeat
|
|
15366
|
+
* that ended, then cross the new one from its edge.
|
|
15367
|
+
*/
|
|
15368
|
+
if (repeatType === "loop") {
|
|
15369
|
+
const isForward = this.speed > 0;
|
|
15370
|
+
if (this.cross(prevTime, isForward ? this.totalDuration : 0)) {
|
|
15371
|
+
return;
|
|
15372
|
+
}
|
|
15373
|
+
prevTime = isForward ? -1 : Infinity;
|
|
15374
|
+
}
|
|
15375
|
+
}
|
|
15376
|
+
this.cross(prevTime, time);
|
|
15377
|
+
}
|
|
15378
|
+
/**
|
|
15379
|
+
* Fire markers between two playhead times. Returns `true` if it
|
|
15380
|
+
* paused.
|
|
15381
|
+
*/
|
|
15382
|
+
cross(from, to) {
|
|
15383
|
+
if (from === to)
|
|
15384
|
+
return;
|
|
15385
|
+
const isForward = to > from;
|
|
15386
|
+
const crossed = (t) => isForward ? t > from && t <= to : t < from && t >= to;
|
|
15387
|
+
const markers = isForward ? this.markers : [...this.markers].reverse();
|
|
15388
|
+
for (const marker of markers) {
|
|
15389
|
+
if (!crossed(marker.time))
|
|
15390
|
+
continue;
|
|
15391
|
+
marker.callback?.(isForward ? 1 : -1);
|
|
15392
|
+
if (marker.pause)
|
|
15393
|
+
return this.pauseAt(marker.time);
|
|
15394
|
+
}
|
|
15395
|
+
if (this.stopAt !== undefined && crossed(this.stopAt)) {
|
|
15396
|
+
this.pauseAt(this.stopAt);
|
|
15397
|
+
this.stopAt = undefined;
|
|
15398
|
+
return true;
|
|
15399
|
+
}
|
|
15400
|
+
}
|
|
15401
|
+
pauseAt(time) {
|
|
15402
|
+
this.pause();
|
|
15403
|
+
this.time = time;
|
|
15404
|
+
return true;
|
|
15405
|
+
}
|
|
15406
|
+
watch() {
|
|
15407
|
+
const group = this.group;
|
|
15408
|
+
group.finished.then(() => {
|
|
15409
|
+
if (group !== this.group || this.isDone)
|
|
15410
|
+
return;
|
|
15411
|
+
this.isDone = true;
|
|
15412
|
+
const { onComplete, onReverseComplete } = this.options;
|
|
15413
|
+
/**
|
|
15414
|
+
* Without onReverseComplete, onComplete fires in either
|
|
15415
|
+
* direction, as it does for a single animation.
|
|
15416
|
+
*/
|
|
15417
|
+
const callback = (this.speed < 0 && onReverseComplete) || onComplete;
|
|
15418
|
+
callback?.();
|
|
15419
|
+
this.resolveFinished();
|
|
15420
|
+
});
|
|
15421
|
+
}
|
|
15422
|
+
newFinished() {
|
|
15423
|
+
this.isDone = false;
|
|
15424
|
+
this.finished = new Promise((resolve) => {
|
|
15425
|
+
this.resolveFinished = resolve;
|
|
15426
|
+
});
|
|
15427
|
+
}
|
|
15428
|
+
}
|
|
15429
|
+
|
|
14891
15430
|
function isSequence(value) {
|
|
14892
15431
|
return Array.isArray(value) && value.some(Array.isArray);
|
|
14893
15432
|
}
|
|
@@ -14901,8 +15440,6 @@
|
|
|
14901
15440
|
* Implementation
|
|
14902
15441
|
*/
|
|
14903
15442
|
function scopedAnimate(subjectOrSequence, optionsOrKeyframes, options) {
|
|
14904
|
-
let animations = [];
|
|
14905
|
-
let animationOnComplete;
|
|
14906
15443
|
const motionScope = activeScope.current;
|
|
14907
15444
|
const scopeTransition = motionScope?.transition;
|
|
14908
15445
|
const inherited = {};
|
|
@@ -14910,45 +15447,53 @@
|
|
|
14910
15447
|
inherited.reduceMotion = reduceMotion;
|
|
14911
15448
|
if (skipAnimations !== undefined)
|
|
14912
15449
|
inherited.skipAnimations = skipAnimations;
|
|
15450
|
+
let timeline;
|
|
14913
15451
|
if (isSequence(subjectOrSequence)) {
|
|
14914
|
-
const
|
|
14915
|
-
|
|
14916
|
-
|
|
14917
|
-
|
|
15452
|
+
const sequenceOptions = {
|
|
15453
|
+
...inherited,
|
|
15454
|
+
...optionsOrKeyframes,
|
|
15455
|
+
scope,
|
|
15456
|
+
};
|
|
14918
15457
|
if (scopeTransition) {
|
|
14919
15458
|
sequenceOptions.defaultTransition = {
|
|
14920
15459
|
...scopeTransition,
|
|
14921
15460
|
...sequenceOptions.defaultTransition,
|
|
14922
15461
|
};
|
|
14923
15462
|
}
|
|
14924
|
-
|
|
15463
|
+
timeline = new Timeline(subjectOrSequence, sequenceOptions);
|
|
14925
15464
|
}
|
|
14926
15465
|
else {
|
|
14927
15466
|
// Extract top-level onComplete so it doesn't get applied per-value
|
|
14928
15467
|
const { onComplete, ...rest } = options || {};
|
|
14929
|
-
|
|
14930
|
-
animationOnComplete = onComplete;
|
|
14931
|
-
}
|
|
14932
|
-
animations = animateSubject(subjectOrSequence, optionsOrKeyframes, {
|
|
15468
|
+
const transition = {
|
|
14933
15469
|
...inherited,
|
|
14934
15470
|
...scopeTransition,
|
|
14935
15471
|
...rest,
|
|
14936
|
-
}
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
15472
|
+
};
|
|
15473
|
+
/**
|
|
15474
|
+
* Play the animations directly, and only compile them as a
|
|
15475
|
+
* sequence if the timeline is changed. Values animated from a
|
|
15476
|
+
* number are animated via their onUpdate callback instead.
|
|
15477
|
+
*/
|
|
15478
|
+
const { onUpdate } = rest;
|
|
15479
|
+
const segment = (typeof subjectOrSequence === "number" ||
|
|
15480
|
+
(typeof subjectOrSequence === "string" &&
|
|
15481
|
+
(typeof optionsOrKeyframes === "string" ||
|
|
15482
|
+
Array.isArray(optionsOrKeyframes)))
|
|
15483
|
+
? [
|
|
15484
|
+
onUpdate || (() => { }),
|
|
15485
|
+
Array.isArray(optionsOrKeyframes)
|
|
15486
|
+
? optionsOrKeyframes
|
|
15487
|
+
: [subjectOrSequence, optionsOrKeyframes],
|
|
15488
|
+
{ ...transition, onUpdate: undefined },
|
|
15489
|
+
]
|
|
15490
|
+
: [subjectOrSequence, optionsOrKeyframes, transition]);
|
|
15491
|
+
timeline = new Timeline([segment], { ...inherited, onComplete, scope }, animateSubject(subjectOrSequence, optionsOrKeyframes, transition, scope));
|
|
14941
15492
|
}
|
|
14942
15493
|
if (motionScope) {
|
|
14943
|
-
|
|
15494
|
+
timeline.finished.then(motionScope.add(() => timeline.stop()));
|
|
14944
15495
|
}
|
|
14945
|
-
|
|
14946
|
-
scope.animations.push(animation);
|
|
14947
|
-
animation.finished.then(() => {
|
|
14948
|
-
removeItem(scope.animations, animation);
|
|
14949
|
-
});
|
|
14950
|
-
}
|
|
14951
|
-
return animation;
|
|
15496
|
+
return timeline;
|
|
14952
15497
|
}
|
|
14953
15498
|
return scopedAnimate;
|
|
14954
15499
|
}
|
|
@@ -14971,7 +15516,9 @@
|
|
|
14971
15516
|
}
|
|
14972
15517
|
const elements = resolveElements(elementOrSelector, scope);
|
|
14973
15518
|
const numElements = elements.length;
|
|
14974
|
-
|
|
15519
|
+
typeof process !== "undefined" &&
|
|
15520
|
+
"development" !== "production" &&
|
|
15521
|
+
exports.invariant(Boolean(numElements), "No valid elements provided.", "no-valid-elements");
|
|
14975
15522
|
/**
|
|
14976
15523
|
* WAAPI doesn't support interrupting animations.
|
|
14977
15524
|
*
|
|
@@ -15280,22 +15827,26 @@
|
|
|
15280
15827
|
const p = (i + progress(at(i), at(i + 1), v)) / n;
|
|
15281
15828
|
return reverse ? 1 - p : p;
|
|
15282
15829
|
}
|
|
15283
|
-
|
|
15284
|
-
|
|
15285
|
-
|
|
15286
|
-
|
|
15287
|
-
|
|
15288
|
-
|
|
15289
|
-
|
|
15290
|
-
|
|
15291
|
-
|
|
15292
|
-
|
|
15293
|
-
|
|
15294
|
-
|
|
15295
|
-
|
|
15296
|
-
|
|
15830
|
+
/**
|
|
15831
|
+
* Resolves offsets on both axes, as useScroll reports the target's progress
|
|
15832
|
+
* on each (#2546). `axis` only picks which progress scroll() reports.
|
|
15833
|
+
*/
|
|
15834
|
+
function resolveOffsets(info, { offset: offsetDefinition = ScrollOffset.All }) {
|
|
15835
|
+
for (const axis in axisKeys) {
|
|
15836
|
+
const axisInfo = info[axis];
|
|
15837
|
+
/**
|
|
15838
|
+
* Reset the length of the resolved offset array rather than creating a new one.
|
|
15839
|
+
*/
|
|
15840
|
+
axisInfo.offset.length = 0;
|
|
15841
|
+
/**
|
|
15842
|
+
* Populate the offset array by resolving the user's offset definition into
|
|
15843
|
+
* a list of pixel scroll offsets.
|
|
15844
|
+
*/
|
|
15845
|
+
for (let i = 0; i < offsetDefinition.length; i++) {
|
|
15846
|
+
axisInfo.offset[i] = resolveOffset(offsetDefinition[i], axisInfo.containerLength, axisInfo.targetLength, axisInfo.targetOffset);
|
|
15847
|
+
}
|
|
15848
|
+
axisInfo.progress = clamp(0, 1, offsetsToProgress(axisInfo.offset, axisInfo.current));
|
|
15297
15849
|
}
|
|
15298
|
-
axisInfo.progress = clamp(0, 1, offsetsToProgress(axisInfo.offset, axisInfo.current));
|
|
15299
15850
|
}
|
|
15300
15851
|
|
|
15301
15852
|
function calcInset(element, container) {
|
|
@@ -15801,6 +16352,7 @@
|
|
|
15801
16352
|
exports.buildSVGAttrs = buildSVGAttrs;
|
|
15802
16353
|
exports.buildSVGPath = buildSVGPath;
|
|
15803
16354
|
exports.buildTransform = buildTransform$1;
|
|
16355
|
+
exports.buildTransformer = buildTransformer;
|
|
15804
16356
|
exports.calcAxisDelta = calcAxisDelta;
|
|
15805
16357
|
exports.calcBoxDelta = calcBoxDelta;
|
|
15806
16358
|
exports.calcChildStagger = calcChildStagger;
|