motion 14.5.0 → 14.7.0

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.
@@ -247,8 +247,8 @@
247
247
  : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
248
248
 
249
249
  const circIn = (p) => 1 - Math.sin(Math.acos(p));
250
- const circOut = reverseEasing(circIn);
251
- const circInOut = mirrorEasing(circIn);
250
+ const circOut = /* @__PURE__ */ reverseEasing(circIn);
251
+ const circInOut = /* @__PURE__ */ mirrorEasing(circIn);
252
252
 
253
253
  const easeIn = /*@__PURE__*/ cubicBezier(0.42, 0, 1, 1);
254
254
  const easeOut = /*@__PURE__*/ cubicBezier(0, 0, 0.58, 1);
@@ -299,13 +299,17 @@
299
299
  const easingDefinitionToFunction = (definition) => {
300
300
  if (isBezierDefinition(definition)) {
301
301
  // If cubic bezier definition, create bezier curve
302
- exports.invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`, "cubic-bezier-length");
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
- exports.invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`, "invalid-easing-type");
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
- let r = "";
447
- let g = "";
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(r, 16),
470
- green: parseInt(g, 16),
471
- blue: parseInt(b, 16),
472
- alpha: a ? parseInt(a, 16) / 255 : 1,
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), template);
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");
@@ -928,6 +918,11 @@
928
918
  */
929
919
  let isProcessing = false;
930
920
  let flushNextFrame = false;
921
+ /**
922
+ * The callback currently running. If it schedules itself with `immediate`
923
+ * it's left in place, so as before it doesn't run again this step.
924
+ */
925
+ let running;
931
926
  /**
932
927
  * A set of processes which were marked keepAlive when scheduled.
933
928
  * A keepAlive process is always also held by a frame queue until
@@ -945,7 +940,7 @@
945
940
  nextFrame.add(callback);
946
941
  runNextFrame();
947
942
  }
948
- callback(latestFrameData);
943
+ (running = callback)(latestFrameData);
949
944
  }
950
945
  const step = {
951
946
  /**
@@ -956,6 +951,14 @@
956
951
  const queue = addToCurrentFrame ? thisFrame : nextFrame;
957
952
  if (keepAlive)
958
953
  toKeepAlive.add(callback);
954
+ /**
955
+ * A callback scheduled with `immediate` during processing may
956
+ * already have run this step (for instance, a value it reads was
957
+ * changed by a later callback). Re-adding moves it to the end of
958
+ * the queue, so it runs again. The running callback is excluded,
959
+ * so one that re-schedules itself can't loop forever.
960
+ */
961
+ addToCurrentFrame && callback !== running && queue.delete(callback);
959
962
  queue.add(callback);
960
963
  return callback;
961
964
  },
@@ -990,6 +993,7 @@
990
993
  // Clear the frame so no callbacks remain. This is to avoid
991
994
  // memory leaks should this render step not run for a while.
992
995
  thisFrame.clear();
996
+ running = undefined;
993
997
  isProcessing = false;
994
998
  if (flushNextFrame) {
995
999
  flushNextFrame = false;
@@ -1137,37 +1141,31 @@
1137
1141
  };
1138
1142
  }
1139
1143
 
1140
- const springDefaults = {
1141
- // Default spring physics
1142
- stiffness: 100,
1143
- damping: 10,
1144
- mass: 1.0,
1145
- // Default duration/bounce-based options
1146
- duration: 800, // in ms
1147
- bounce: 0.3,
1148
- visualDuration: 0.3, // in seconds
1149
- // Rest thresholds
1150
- restSpeed: {
1151
- granular: 0.01,
1152
- default: 2,
1153
- },
1154
- restDelta: {
1155
- granular: 0.005,
1156
- default: 0.5,
1157
- },
1158
- // Limits
1159
- minDuration: 0.01, // in seconds
1160
- maxDuration: 10.0, // in seconds
1161
- minDamping: 0.05,
1162
- };
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;
1163
1161
  /**
1164
1162
  * Maps bounce to a damping ratio as SwiftUI does: 0 is critically damped, and
1165
1163
  * it approaches undamped towards 1 and infinitely overdamped towards -1. Both
1166
1164
  * ends are limited so the spring still moves and settles.
1167
1165
  */
1168
1166
  const bounceToDampingRatio = (bounce) => bounce < 0
1169
- ? 1 / Math.max(1 + bounce, springDefaults.minDamping)
1170
- : Math.max(1 - bounce, springDefaults.minDamping);
1167
+ ? 1 / Math.max(1 + bounce, minDamping)
1168
+ : Math.max(1 - bounce, minDamping);
1171
1169
  /**
1172
1170
  * Scales an overdamped spring's undamped frequency so that, at its defining
1173
1171
  * time, it has as far left to go as the critically damped spring that's
@@ -1210,12 +1208,12 @@
1210
1208
  * ignore inherited velocity and only resolve without valid physics. Returns
1211
1209
  * NaN physics when the root search doesn't converge.
1212
1210
  */
1213
- function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, }) {
1211
+ function findSpring({ duration = defaultDuration, bounce = defaultBounce, }) {
1214
1212
  let envelope;
1215
1213
  let derivative;
1216
- exports.warning(duration <= secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less", "spring-duration-limit");
1214
+ exports.warning(duration <= secondsToMilliseconds(maxDuration), "Spring duration must be 10 seconds or less", "spring-duration-limit");
1217
1215
  const dampingRatio = bounceToDampingRatio(bounce);
1218
- duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, millisecondsToSeconds(duration));
1216
+ duration = clamp(minDuration, maxDuration, millisecondsToSeconds(duration));
1219
1217
  if (dampingRatio < 1) {
1220
1218
  /**
1221
1219
  * Underdamped spring
@@ -1300,9 +1298,9 @@
1300
1298
  const validMass = resolvePhysics(options.mass);
1301
1299
  const springOptions = {
1302
1300
  ...options,
1303
- stiffness: validStiffness ?? springDefaults.stiffness,
1304
- damping: validDamping ?? springDefaults.damping,
1305
- mass: validMass ?? springDefaults.mass,
1301
+ stiffness: validStiffness ?? defaultStiffness,
1302
+ damping: validDamping ?? defaultDamping,
1303
+ mass: validMass ?? defaultMass,
1306
1304
  isResolvedFromDuration: false,
1307
1305
  // stiffness/damping/mass overrides duration/bounce
1308
1306
  isTimeDefined: (validStiffness ?? validDamping ?? validMass) === undefined &&
@@ -1330,13 +1328,13 @@
1330
1328
  */
1331
1329
  if (!isValidPhysics(springOptions.stiffness) ||
1332
1330
  !isValidPhysics(springOptions.damping, true)) {
1333
- springOptions.stiffness = springDefaults.stiffness;
1334
- springOptions.damping = springDefaults.damping;
1331
+ springOptions.stiffness = defaultStiffness;
1332
+ springOptions.damping = defaultDamping;
1335
1333
  }
1336
1334
  }
1337
1335
  return springOptions;
1338
1336
  }
1339
- function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce = springDefaults.bounce) {
1337
+ function springGenerator(optionsOrVisualDuration = defaultVisualDuration, bounce = defaultBounce) {
1340
1338
  const options = typeof optionsOrVisualDuration !== "object"
1341
1339
  ? {
1342
1340
  visualDuration: optionsOrVisualDuration,
@@ -1386,14 +1384,10 @@
1386
1384
  const isGranularScale = Math.abs(s.delta) < 5;
1387
1385
  s.restSpeed =
1388
1386
  options.restSpeed ||
1389
- (isGranularScale
1390
- ? springDefaults.restSpeed.granular
1391
- : springDefaults.restSpeed.default);
1387
+ (isGranularScale ? restSpeedGranular : restSpeedDefault);
1392
1388
  s.restDelta =
1393
1389
  options.restDelta ||
1394
- (isGranularScale
1395
- ? springDefaults.restDelta.granular
1396
- : springDefaults.restDelta.default);
1390
+ (isGranularScale ? restDeltaGranular : restDeltaDefault);
1397
1391
  };
1398
1392
  setRestThresholds();
1399
1393
  let resolveSpring;
@@ -1462,11 +1456,16 @@
1462
1456
  * dampedAngularFreq * t passes 300, both terms decay at the damping
1463
1457
  * rate. Heavily overdamped drag springs (dragElastic: 0,
1464
1458
  * dragTransition) rely on this to settle almost at once rather than
1465
- * creep at stiffness / damping. Negative bounce needs the exact
1466
- * curve, so time-defined springs are exempt.
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.
1467
1464
  */
1468
- const limit = isTimeDefined ? Infinity : 300 / dampedAngularFreq;
1469
- const decayAt = (rate, t) => Math.exp(t > limit ? -rate * limit - decay * (t - limit) : -rate * t);
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);
1470
1469
  const c = { S: 0, F: 0 };
1471
1470
  update = () => {
1472
1471
  const P = (s.velocity + decay * s.delta) / dampedAngularFreq;
@@ -1521,13 +1520,15 @@
1521
1520
  };
1522
1521
  return generator;
1523
1522
  }
1524
- spring.applyToOptions = (options) => {
1525
- const generatorOptions = createGeneratorEasing(options, 100, spring);
1526
- options.ease = generatorOptions.ease;
1527
- options.duration = secondsToMilliseconds(generatorOptions.duration);
1528
- options.type = "keyframes";
1529
- return options;
1530
- };
1523
+ const spring = /* @__PURE__ */ Object.assign(springGenerator, {
1524
+ applyToOptions: (options) => {
1525
+ const generatorOptions = createGeneratorEasing(options, 100, springGenerator);
1526
+ options.ease = generatorOptions.ease;
1527
+ options.duration = secondsToMilliseconds(generatorOptions.duration);
1528
+ options.type = "keyframes";
1529
+ return options;
1530
+ },
1531
+ });
1531
1532
 
1532
1533
  function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
1533
1534
  const origin = keyframes[0];
@@ -1545,7 +1546,7 @@
1545
1546
  };
1546
1547
  let amplitude = power * velocity;
1547
1548
  const ideal = origin + amplitude;
1548
- const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
1549
+ const target = modifyTarget ? modifyTarget(ideal) : ideal;
1549
1550
  /**
1550
1551
  * If the target has changed we need to re-calculate the amplitude, otherwise
1551
1552
  * the animation will start from the wrong position.
@@ -1556,7 +1557,8 @@
1556
1557
  const applyFriction = (t) => {
1557
1558
  const delta = calcDelta(t);
1558
1559
  state.done = Math.abs(delta) <= restDelta;
1559
- state.value = state.done ? target : target + delta;
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;
1560
1562
  };
1561
1563
  /**
1562
1564
  * Ideally this would resolve for t in a stateless way, we could
@@ -2002,17 +2004,12 @@
2002
2004
  this.generator = generator;
2003
2005
  }
2004
2006
  updateTime(timestamp) {
2005
- const animationTime = Math.round(timestamp - this.startTime) * this.playbackSpeed;
2006
- // Update currentTime
2007
- if (this.holdTime !== null) {
2008
- this.currentTime = this.holdTime;
2009
- }
2010
- else {
2011
- // Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 =
2012
- // 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for
2013
- // example.
2014
- this.currentTime = animationTime;
2015
- }
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;
2016
2013
  }
2017
2014
  tick(timestamp, sample = false) {
2018
2015
  const { generator, totalDuration, calculatedDuration } = this;
@@ -2053,10 +2050,17 @@
2053
2050
  }
2054
2051
  const state = this.generate(this.currentTime, isInDelayPhase);
2055
2052
  let { done } = state;
2056
- if (!isInDelayPhase && calculatedDuration !== null) {
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) {
2057
2059
  done =
2058
2060
  this.playbackSpeed >= 0
2059
- ? this.currentTime >= totalDuration
2061
+ ? calculatedDuration < Infinity
2062
+ ? this.currentTime >= totalDuration
2063
+ : done
2060
2064
  : this.currentTime <= 0;
2061
2065
  }
2062
2066
  const isAnimationFinished = this.holdTime === null &&
@@ -2162,20 +2166,11 @@
2162
2166
  }
2163
2167
  return state;
2164
2168
  }
2165
- /**
2166
- * Allows the returned animation to be awaited or promise-chained. Currently
2167
- * resolves when the animation finishes at all but in a future update could/should
2168
- * reject if its cancels.
2169
- */
2170
- then(resolve, reject) {
2171
- return this.finished.then(resolve, reject);
2172
- }
2173
2169
  get duration() {
2174
2170
  return millisecondsToSeconds(this.calculatedDuration);
2175
2171
  }
2176
2172
  get iterationDuration() {
2177
- const { delay = 0 } = this.options || {};
2178
- return this.duration + millisecondsToSeconds(delay);
2173
+ return this.duration + millisecondsToSeconds(this.options.delay ?? 0);
2179
2174
  }
2180
2175
  get time() {
2181
2176
  return millisecondsToSeconds(this.currentTime);
@@ -2279,14 +2274,9 @@
2279
2274
  }
2280
2275
  teardown() {
2281
2276
  this.state = "idle";
2282
- this.stopDriver();
2283
- this.startTime = this.holdTime = null;
2284
- }
2285
- stopDriver() {
2286
- if (!this.driver)
2287
- return;
2288
- this.driver.stop();
2277
+ this.driver?.stop();
2289
2278
  this.driver = undefined;
2279
+ this.startTime = this.holdTime = null;
2290
2280
  }
2291
2281
  sample(sampleTime) {
2292
2282
  this.startTime = 0;
@@ -2543,23 +2533,15 @@
2543
2533
  if (!transform || transform === "none") {
2544
2534
  return defaultTransformValue(name);
2545
2535
  }
2546
- const matrix3dMatch = transform.match(/^matrix3d\(([-\d.e\s,]+)\)$/u);
2547
- let parsers;
2548
- let match;
2549
- if (matrix3dMatch) {
2550
- parsers = matrix3dParsers;
2551
- match = matrix3dMatch;
2552
- }
2553
- else {
2554
- const matrix2dMatch = transform.match(/^matrix\(([-\d.e\s,]+)\)$/u);
2555
- parsers = matrix2dParsers;
2556
- match = matrix2dMatch;
2557
- }
2536
+ const match = transform.match(/^matrix(3d)?\(([-\d.e\s,]+)\)$/u);
2558
2537
  if (!match) {
2559
2538
  return defaultTransformValue(name);
2560
2539
  }
2561
- const valueParser = parsers[name];
2562
- const values = match[1].split(",").map(convertTransformToNumber);
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));
2563
2545
  return typeof valueParser === "function"
2564
2546
  ? valueParser(values)
2565
2547
  : values[valueParser];
@@ -2568,9 +2550,6 @@
2568
2550
  const { transform = "none" } = getComputedStyle(instance);
2569
2551
  return parseValueFromTransform(transform, name);
2570
2552
  };
2571
- function convertTransformToNumber(value) {
2572
- return parseFloat(value.trim());
2573
- }
2574
2553
 
2575
2554
  /**
2576
2555
  * Generate a list of every possible transform key.
@@ -2609,7 +2588,7 @@
2609
2588
 
2610
2589
  const isNumOrPxType = (v) => v === number || v === px;
2611
2590
  const transformKeys = new Set(["x", "y", "z"]);
2612
- const nonTranslationalTransformKeys = transformPropOrder.filter((key) => !transformKeys.has(key));
2591
+ const nonTranslationalTransformKeys = /* @__PURE__ */ transformPropOrder.filter((key) => !transformKeys.has(key));
2613
2592
  /**
2614
2593
  * Reset any bounding box-changing transforms so the element can be
2615
2594
  * measured. Returns the values to restore. Values already at their
@@ -2651,6 +2630,8 @@
2651
2630
  ? size
2652
2631
  : size - parseFloat(paddingStart) - parseFloat(paddingEnd);
2653
2632
  }
2633
+ const transformX = ({ transform }) => parseValueFromTransform(transform, "x");
2634
+ const transformY = ({ transform }) => parseValueFromTransform(transform, "y");
2654
2635
  const positionalValues = {
2655
2636
  // Dimensions
2656
2637
  width: ({ width, paddingLeft = "0", paddingRight = "0", boxSizing }, measureBox) => measureDimension(width, measureBox, "x", paddingLeft, paddingRight, boxSizing),
@@ -2665,13 +2646,12 @@
2665
2646
  const { x } = measureBox();
2666
2647
  return parseFloat(left) + (x.max - x.min);
2667
2648
  },
2668
- // Transform
2669
- x: ({ transform }) => parseValueFromTransform(transform, "x"),
2670
- y: ({ transform }) => parseValueFromTransform(transform, "y"),
2649
+ // Transform, including translate longform names
2650
+ x: transformX,
2651
+ y: transformY,
2652
+ translateX: transformX,
2653
+ translateY: transformY,
2671
2654
  };
2672
- // Alias translate longform names
2673
- positionalValues.translateX = positionalValues.x;
2674
- positionalValues.translateY = positionalValues.y;
2675
2655
 
2676
2656
  const toResolve = new Set();
2677
2657
  let isScheduled = false;
@@ -2972,7 +2952,9 @@
2972
2952
  this.isPseudoElement = Boolean(pseudoElement);
2973
2953
  this.allowFlatten = allowFlatten;
2974
2954
  this.options = options;
2975
- exports.invariant(typeof options.type !== "string", `Mini animate() doesn't support "type" as a string.`, "mini-spring");
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");
2976
2958
  const transition = applyGeneratorOptions(options);
2977
2959
  this.animation = startWaapiAnimation(element, name, keyframes, transition, pseudoElement);
2978
2960
  if (transition.autoplay === false) {
@@ -2982,9 +2964,7 @@
2982
2964
  this.finishedTime = this.time;
2983
2965
  if (!pseudoElement) {
2984
2966
  const keyframe = getFinalKeyframe(keyframes, this.options, finalKeyframe, this.speed);
2985
- if (this.updateMotionValue) {
2986
- this.updateMotionValue(keyframe);
2987
- }
2967
+ this.updateMotionValue?.(keyframe);
2988
2968
  /**
2989
2969
  * If we can, we want to commit the final style as set by the user,
2990
2970
  * rather than the computed keyframe value supplied by the animation.
@@ -3025,18 +3005,24 @@
3025
3005
  if (this.isStopped)
3026
3006
  return;
3027
3007
  this.isStopped = true;
3028
- const { state } = this;
3029
- if (state === "idle" || state === "finished") {
3008
+ /**
3009
+ * Before its finish event, a finished animation (an instant one is
3010
+ * as soon as it starts) is still showing its end, so it's stopped
3011
+ * like a running one. Otherwise that event would apply its end
3012
+ * over whatever animates the value next.
3013
+ */
3014
+ if (this.state === "idle" || this.finishedTime !== null)
3030
3015
  return;
3031
- }
3032
3016
  if (this.updateMotionValue) {
3033
3017
  this.updateMotionValue();
3034
3018
  }
3035
3019
  else {
3036
3020
  this.commitStyles();
3037
3021
  }
3038
- if (!this.isPseudoElement)
3022
+ if (!this.isPseudoElement) {
3023
+ this.animation.onfinish = null;
3039
3024
  this.cancel();
3025
+ }
3040
3026
  }
3041
3027
  /**
3042
3028
  * WAAPI doesn't natively have any interruption capabilities.
@@ -3299,6 +3285,7 @@
3299
3285
  if (!isOriginAnimatable || !isTargetAnimatable) {
3300
3286
  // Only build the message when we're going to warn
3301
3287
  isOriginAnimatable !== isTargetAnimatable &&
3288
+ "development" !== "production" &&
3302
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");
3303
3290
  return false;
3304
3291
  }
@@ -3313,13 +3300,16 @@
3313
3300
 
3314
3301
  /**
3315
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.
3316
3307
  */
3317
3308
  const acceleratedValues = new Set([
3318
3309
  "opacity",
3319
3310
  "clipPath",
3320
3311
  "filter",
3321
3312
  "transform",
3322
- "backgroundColor",
3323
3313
  ]);
3324
3314
 
3325
3315
  const browserColorFunctions = /^(?:oklch|oklab|lab|lch|color|color-mix|light-dark)\(/;
@@ -3345,7 +3335,13 @@
3345
3335
  "borderBottomColor",
3346
3336
  "borderLeftColor",
3347
3337
  ]);
3348
- const supportsWaapi = /*@__PURE__*/ memo(() => Object.hasOwnProperty.call(Element.prototype, "animate"));
3338
+ /**
3339
+ * WAAPI runs on the browser's clock, so on a manual clock every animation
3340
+ * runs in JS where processFrame can drive it. Set the flag before starting
3341
+ * animations: running WAAPI animations stay on the browser's clock.
3342
+ */
3343
+ const supportsWaapi = () => !MotionGlobalConfig.useManualTiming &&
3344
+ Object.hasOwnProperty.call(Element.prototype, "animate");
3349
3345
  function supportsBrowserAnimation(options) {
3350
3346
  const { motionValue, name, repeatDelay, repeatType, damping, type, keyframes, } = options;
3351
3347
  /**
@@ -3405,6 +3401,20 @@
3405
3401
  transformPerspective: "perspective",
3406
3402
  };
3407
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);
3408
3418
  /**
3409
3419
  * Build a CSS transform style from individual x/y/scale etc properties.
3410
3420
  *
@@ -3414,7 +3424,6 @@
3414
3424
  function buildTransform$1(latestValues, transform, transformTemplate) {
3415
3425
  // The transform string we're going to build into.
3416
3426
  let transformString = "";
3417
- let transformIsDefault = true;
3418
3427
  /**
3419
3428
  * Loop over all possible transforms in order, adding the ones that
3420
3429
  * are present to the transform string.
@@ -3424,24 +3433,11 @@
3424
3433
  const value = latestValues[key];
3425
3434
  if (value === undefined)
3426
3435
  continue;
3427
- let valueIsDefault = true;
3428
- if (typeof value === "number") {
3429
- valueIsDefault = value === (key.startsWith("scale") ? 1 : 0);
3430
- }
3431
- else {
3432
- const parsed = parseFloat(value);
3433
- valueIsDefault = key.startsWith("scale") ? parsed === 1 : parsed === 0;
3434
- }
3435
- if (!valueIsDefault || transformTemplate) {
3436
- const valueAsType = getValueAsType(value, numberValueTypes[key]);
3437
- if (!valueIsDefault) {
3438
- transformIsDefault = false;
3439
- const transformName = translateAlias[key] || key;
3440
- transformString += `${transformName}(${valueAsType}) `;
3441
- }
3442
- if (transformTemplate) {
3443
- transform[key] = valueAsType;
3444
- }
3436
+ const valueAsType = transformTemplate &&
3437
+ (transform[key] = getValueAsType(value, numberValueTypes[key]));
3438
+ if (!isDefaultTransform(key, value)) {
3439
+ transformString +=
3440
+ transformFunction(key, value, valueAsType || undefined) + " ";
3445
3441
  }
3446
3442
  }
3447
3443
  // `pathRotation` composes onto `rotate` as a separate additive term so
@@ -3449,19 +3445,14 @@
3449
3445
  // `transformPropOrder`.
3450
3446
  const pathRotation = latestValues.pathRotation;
3451
3447
  if (pathRotation) {
3452
- transformIsDefault = false;
3453
- transformString += `rotate(${getValueAsType(pathRotation, numberValueTypes.pathRotation)}) `;
3448
+ transformString += transformFunction("rotate", pathRotation);
3454
3449
  }
3455
3450
  transformString = transformString.trim();
3456
3451
  // If we have a custom `transform` template, pass our transform values and
3457
3452
  // generated transformString to that before returning
3458
- if (transformTemplate) {
3459
- transformString = transformTemplate(transform, transformIsDefault ? "" : transformString);
3460
- }
3461
- else if (transformIsDefault) {
3462
- transformString = "none";
3463
- }
3464
- return transformString;
3453
+ return transformTemplate
3454
+ ? transformTemplate(transform, transformString)
3455
+ : transformString || "none";
3465
3456
  }
3466
3457
 
3467
3458
  const transformGroups = new WeakMap();
@@ -3803,27 +3794,30 @@
3803
3794
  return values;
3804
3795
  };
3805
3796
  /**
3806
- * Build the transform at a timestamp. Unlike buildTransform, every key
3807
- * is written, even at its default, so each keyframe has the same list
3808
- * 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.
3809
3801
  */
3810
- const compose = (timestamp, settled) => {
3802
+ const toTransform = (values, isStyle) => {
3811
3803
  let transform = "";
3812
- const values = valuesAt(timestamp, settled);
3813
3804
  for (const key in values) {
3814
- transform +=
3815
- (translateAlias[key] || key) +
3816
- "(" +
3817
- getValueAsType(values[key], numberValueTypes[key]) +
3818
- ") ";
3805
+ const value = values[key];
3806
+ if (!isStyle || !isDefaultTransform(key, value)) {
3807
+ transform += transformFunction(key, value) + " ";
3808
+ }
3819
3809
  }
3820
- return transform || "none";
3810
+ return (isStyle ? transform.trim() : transform) || "none";
3821
3811
  };
3812
+ /**
3813
+ * Build the transform at a timestamp.
3814
+ */
3815
+ const compose = (timestamp, settled) => toTransform(valuesAt(timestamp, settled));
3822
3816
  /**
3823
3817
  * Write the transform as an inline style, as the renderer would.
3824
3818
  */
3825
3819
  const commit = (values = valuesAt()) => {
3826
- element.style.transform = buildTransform$1(values, {});
3820
+ element.style.transform = toTransform(values, true);
3827
3821
  };
3828
3822
  /**
3829
3823
  * Cancel the WAAPI animations, by default first committing the
@@ -3834,6 +3828,13 @@
3834
3828
  segments.forEach(({ animation }) => animation.cancel());
3835
3829
  segments = [];
3836
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));
3837
3838
  const add = (animation, offset, end = Infinity) => {
3838
3839
  const segment = { animation, offset, end };
3839
3840
  animation.startTime = time.now() + offset;
@@ -4026,7 +4027,7 @@
4026
4027
  commit(valuesAt(from, settled));
4027
4028
  keyframes = null;
4028
4029
  }
4029
- return element.animate(keyframes, {
4030
+ return create(keyframes, {
4030
4031
  ...options,
4031
4032
  duration: to - from,
4032
4033
  easing: easing || "linear",
@@ -4116,9 +4117,7 @@
4116
4117
  * empty animations that end with them.
4117
4118
  */
4118
4119
  ends.forEach((trackEnd) => trackEnd < end - 1 &&
4119
- add(element.animate(null, {
4120
- duration: trackEnd - now,
4121
- }), 0, trackEnd - now));
4120
+ add(create(null, { duration: trackEnd - now }), 0, trackEnd - now));
4122
4121
  }
4123
4122
  if (cycle) {
4124
4123
  /**
@@ -4192,12 +4191,29 @@
4192
4191
  if (isDone)
4193
4192
  continue;
4194
4193
  const local = elapsed - offset;
4195
- 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) {
4196
4207
  animation.playbackRate = rate;
4197
4208
  animation.startTime = now - local / rate;
4198
4209
  }
4199
4210
  else {
4200
- animation.pause();
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;
4201
4217
  animation.currentTime = local;
4202
4218
  }
4203
4219
  }
@@ -4388,9 +4404,8 @@
4388
4404
  * If we can't animate this value with the resolved keyframes
4389
4405
  * then we should complete it immediately.
4390
4406
  */
4391
- let canAnimateValue = true;
4392
- if (!canAnimate(keyframes, name, type, velocity)) {
4393
- canAnimateValue = false;
4407
+ const canAnimateValue = canAnimate(keyframes, name, type, velocity);
4408
+ if (!canAnimateValue) {
4394
4409
  if (MotionGlobalConfig.instantAnimations || !delay) {
4395
4410
  onUpdate?.(getFinalKeyframe(keyframes, options, finalKeyframe));
4396
4411
  }
@@ -4534,9 +4549,7 @@
4534
4549
  this.animation.complete();
4535
4550
  }
4536
4551
  cancel() {
4537
- if (this._animation) {
4538
- this.animation.cancel();
4539
- }
4552
+ this._animation?.cancel();
4540
4553
  this.keyframeResolver?.cancel();
4541
4554
  }
4542
4555
  }
@@ -4719,7 +4732,7 @@
4719
4732
  this.setCurrent(v);
4720
4733
  // Update update subscribers
4721
4734
  if (this.current !== this.prev) {
4722
- this.notifyChange();
4735
+ this.dirty();
4723
4736
  if (this.dependents) {
4724
4737
  for (const dependent of this.dependents) {
4725
4738
  dependent.dirty();
@@ -4857,12 +4870,9 @@
4857
4870
  * @public
4858
4871
  */
4859
4872
  set(v) {
4860
- if (!this.passiveEffect) {
4861
- this.updateAndNotify(v);
4862
- }
4863
- else {
4864
- this.passiveEffect(v, this.updateAndNotify);
4865
- }
4873
+ this.passiveEffect
4874
+ ? this.passiveEffect(v, this.updateAndNotify)
4875
+ : this.updateAndNotify(v);
4866
4876
  }
4867
4877
  setWithVelocity(prev, current, delta) {
4868
4878
  this.set(current);
@@ -4879,13 +4889,9 @@
4879
4889
  this.prev = v;
4880
4890
  this.prevUpdatedAt = this.prevFrameValue = undefined;
4881
4891
  endAnimation && this.stop();
4882
- if (this.stopPassiveEffect)
4883
- this.stopPassiveEffect();
4892
+ this.stopPassiveEffect?.();
4884
4893
  }
4885
4894
  dirty() {
4886
- this.notifyChange();
4887
- }
4888
- notifyChange() {
4889
4895
  const { current, changeSubscriber } = this;
4890
4896
  /**
4891
4897
  * One or the other exists. Subscribing during the direct
@@ -4900,15 +4906,10 @@
4900
4906
  }
4901
4907
  }
4902
4908
  addDependent(dependent) {
4903
- if (!this.dependents) {
4904
- this.dependents = new Set();
4905
- }
4906
- this.dependents.add(dependent);
4909
+ (this.dependents || (this.dependents = new Set())).add(dependent);
4907
4910
  }
4908
4911
  removeDependent(dependent) {
4909
- if (this.dependents) {
4910
- this.dependents.delete(dependent);
4911
- }
4912
+ this.dependents?.delete(dependent);
4912
4913
  }
4913
4914
  /**
4914
4915
  * Returns the latest state of `MotionValue`
@@ -4918,9 +4919,7 @@
4918
4919
  * @public
4919
4920
  */
4920
4921
  get() {
4921
- if (collectMotionValues.current) {
4922
- collectMotionValues.current.push(this);
4923
- }
4922
+ collectMotionValues.current?.push(this);
4924
4923
  /**
4925
4924
  * A value animating on the compositor isn't updated every frame,
4926
4925
  * so read it from its animation.
@@ -4986,7 +4985,7 @@
4986
4985
  isComplete = true;
4987
4986
  this.events.animationComplete?.notify();
4988
4987
  if (this.animation === animation)
4989
- this.clearAnimation();
4988
+ this.animation = undefined;
4990
4989
  resolve();
4991
4990
  });
4992
4991
  if (!isComplete)
@@ -5002,11 +5001,10 @@
5002
5001
  stop() {
5003
5002
  if (this.animation) {
5004
5003
  this.animation.stop();
5005
- if (this.events.animationCancel) {
5006
- this.events.animationCancel.notify();
5007
- }
5004
+ this.events.animationCancel?.notify();
5008
5005
  }
5009
- this.clearAnimation();
5006
+ // Assign rather than delete to keep the object's shape stable
5007
+ this.animation = undefined;
5010
5008
  }
5011
5009
  /**
5012
5010
  * Returns `true` if this value is currently animating.
@@ -5016,10 +5014,6 @@
5016
5014
  isAnimating() {
5017
5015
  return !!this.animation;
5018
5016
  }
5019
- clearAnimation() {
5020
- // Assign rather than delete to keep the object's shape stable
5021
- this.animation = undefined;
5022
- }
5023
5017
  /**
5024
5018
  * Destroy and clean up subscribers to this `MotionValue`.
5025
5019
  *
@@ -5034,9 +5028,7 @@
5034
5028
  this.events.destroy?.notify();
5035
5029
  this.clearListeners();
5036
5030
  this.stop();
5037
- if (this.stopPassiveEffect) {
5038
- this.stopPassiveEffect();
5039
- }
5031
+ this.stopPassiveEffect?.();
5040
5032
  }
5041
5033
  }
5042
5034
  function motionValue(init, options) {
@@ -5114,6 +5106,7 @@
5114
5106
  "repeatDelay",
5115
5107
  "from",
5116
5108
  "elapsed",
5109
+ "out",
5117
5110
  ]);
5118
5111
  /**
5119
5112
  * Decide whether a transition is defined on a given Transition.
@@ -5536,12 +5529,8 @@
5536
5529
  }
5537
5530
  }
5538
5531
 
5539
- function camelToDash(str) {
5540
- return str.replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`);
5541
- }
5542
-
5543
5532
  const optimizedAppearDataId = "framerAppearId";
5544
- const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
5533
+ const optimizedAppearDataAttribute = "data-framer-appear-id";
5545
5534
 
5546
5535
  function getOptimisedAppearId(visualElement) {
5547
5536
  return visualElement.props[optimizedAppearDataAttribute];
@@ -5588,7 +5577,7 @@
5588
5577
  }
5589
5578
  const valueTransition = {
5590
5579
  delay,
5591
- ...getValueTransition$1(transition || {}, key),
5580
+ ...getValueTransition$1(animationTypeState?.out[key] || transition || {}, key),
5592
5581
  };
5593
5582
  if (skipAnimations)
5594
5583
  valueTransition.skipAnimations = true;
@@ -5758,8 +5747,9 @@
5758
5747
  }
5759
5748
 
5760
5749
  class DOMKeyframesResolver extends KeyframeResolver {
5761
- constructor(unresolvedKeyframes, onComplete, name, motionValue, element) {
5762
- super(unresolvedKeyframes, onComplete, name, motionValue, element, true);
5750
+ constructor() {
5751
+ super(...arguments);
5752
+ this.isAsync = true;
5763
5753
  }
5764
5754
  /**
5765
5755
  * Keyframes that don't need reading from the DOM, or measuring,
@@ -5787,7 +5777,7 @@
5787
5777
  }
5788
5778
  readKeyframes() {
5789
5779
  const { unresolvedKeyframes, element, name } = this;
5790
- if (!element || !element.current)
5780
+ if (!element?.current)
5791
5781
  return;
5792
5782
  super.readKeyframes();
5793
5783
  /**
@@ -5882,11 +5872,10 @@
5882
5872
  }
5883
5873
  measureInitialState() {
5884
5874
  const { element, unresolvedKeyframes, name } = this;
5885
- if (!element || !element.current)
5875
+ if (!element?.current)
5886
5876
  return;
5887
- if (name === "height") {
5877
+ if (name === "height")
5888
5878
  this.suspendedScrollY = window.pageYOffset;
5889
- }
5890
5879
  this.measuredOrigin = this.measure();
5891
5880
  unresolvedKeyframes[0] = this.measuredOrigin;
5892
5881
  // Set final key frame to measure after next render
@@ -5897,7 +5886,7 @@
5897
5886
  }
5898
5887
  measureEndState() {
5899
5888
  const { element, unresolvedKeyframes } = this;
5900
- if (!element || !element.current)
5889
+ if (!element?.current)
5901
5890
  return;
5902
5891
  this.motionValue?.jump(this.measuredOrigin, false);
5903
5892
  const finalKeyframeIndex = unresolvedKeyframes.length - 1;
@@ -5906,14 +5895,6 @@
5906
5895
  if (finalKeyframe !== null && this.finalKeyframe === undefined) {
5907
5896
  this.finalKeyframe = finalKeyframe;
5908
5897
  }
5909
- // If we removed transform values, reapply them before the next render
5910
- if (this.removedTransforms?.length) {
5911
- this.removedTransforms.forEach(([unsetTransformName, unsetTransformValue]) => {
5912
- element
5913
- .getValue(unsetTransformName)
5914
- .set(unsetTransformValue);
5915
- });
5916
- }
5917
5898
  this.resolveNoneKeyframes();
5918
5899
  }
5919
5900
  }
@@ -6018,7 +5999,8 @@
6018
5999
  */
6019
6000
  const effects = [];
6020
6001
  function addEffect(effect) {
6021
- exports.invariant(typeof effect.test === "function" && typeof effect.read === "function", "Effects passed to animate.addEffect() need test() and read().", "effect-missing-test");
6002
+ exports.invariant(typeof effect.test === "function" &&
6003
+ typeof effect.read === "function", "Effects passed to animate.addEffect() need test() and read().", "effect-missing-test");
6022
6004
  removeEffect(effect);
6023
6005
  effects.unshift(effect);
6024
6006
  }
@@ -6345,11 +6327,6 @@
6345
6327
  });
6346
6328
  }
6347
6329
 
6348
- /**
6349
- * Cache of `translateX(`-style openers so each render is a few string
6350
- * concatenations rather than a template per transform.
6351
- */
6352
- const openers = {};
6353
6330
  function buildTransform(state) {
6354
6331
  let transform = "";
6355
6332
  const { transformKeys: keys = [], transformValues: values = {} } = state;
@@ -6362,14 +6339,8 @@
6362
6339
  const value = values[key].getCurrent();
6363
6340
  if (value === undefined)
6364
6341
  continue;
6365
- const parsed = typeof value === "number" ? value : parseFloat(value);
6366
- if (parsed !== (key.startsWith("scale") ? 1 : 0)) {
6367
- transform +=
6368
- (transform && " ") +
6369
- (openers[key] ||
6370
- (openers[key] = (translateAlias[key] || key) + "(")) +
6371
- getValueAsType(value, transformValueTypes[key]) +
6372
- ")";
6342
+ if (!isDefaultTransform(key, value)) {
6343
+ transform += (transform && " ") + transformFunction(key, value);
6373
6344
  }
6374
6345
  }
6375
6346
  // See build-transform.ts: additive `rotate()` so user `rotate` isn't
@@ -6377,10 +6348,7 @@
6377
6348
  const pathRotation = state.get("pathRotation")?.get();
6378
6349
  if (pathRotation) {
6379
6350
  transform +=
6380
- (transform && " ") +
6381
- "rotate(" +
6382
- getValueAsType(pathRotation, transformValueTypes.pathRotation) +
6383
- ")";
6351
+ (transform && " ") + transformFunction("rotate", pathRotation);
6384
6352
  }
6385
6353
  return transform || "none";
6386
6354
  }
@@ -6469,6 +6437,10 @@
6469
6437
  const styleEffect =
6470
6438
  /*@__PURE__*/ createSelectorEffect(styleSubjectEffect);
6471
6439
 
6440
+ function camelToDash(str) {
6441
+ return str.replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`);
6442
+ }
6443
+
6472
6444
  function buildHTMLStyles(state, latestValues, transformTemplate) {
6473
6445
  const { style, vars, transformOrigin } = state;
6474
6446
  // Track whether we encounter any transform or transformOrigin values.
@@ -6779,10 +6751,24 @@
6779
6751
  !isIdentityScale(scaleX) ||
6780
6752
  !isIdentityScale(scaleY));
6781
6753
  }
6754
+ /**
6755
+ * Rotations and skews, which projection can't correct for.
6756
+ */
6757
+ const angleTransforms = [
6758
+ "rotate",
6759
+ "rotateX",
6760
+ "rotateY",
6761
+ "rotateZ",
6762
+ "skewX",
6763
+ "skewY",
6764
+ ];
6782
6765
  function hasTransform(values) {
6783
6766
  return (hasScale(values) ||
6784
6767
  has2DTranslate(values) ||
6785
- values.z ||
6768
+ hasDistortingTransform(values));
6769
+ }
6770
+ function hasDistortingTransform(values) {
6771
+ return (values.z ||
6786
6772
  values.rotate ||
6787
6773
  values.rotateX ||
6788
6774
  values.rotateY ||
@@ -6835,6 +6821,11 @@
6835
6821
  * in a tree upon our box before then calculating how to project it into our desired viewport-relative box
6836
6822
  *
6837
6823
  * This is the final nested loop within updateLayoutDelta for future refactoring
6824
+ *
6825
+ * isSharedTransition is true for a shared layout animation in page space, which
6826
+ * also applies ancestor scroll and transforms. Or it's the layoutRoot a shared
6827
+ * animation is relative to, so only transforms of ancestors not shared with it
6828
+ * (or above it) are applied.
6838
6829
  */
6839
6830
  function applyTreeDeltas(box, treeScale, treePath, isSharedTransition = false) {
6840
6831
  const treeLength = treePath.length;
@@ -6851,13 +6842,10 @@
6851
6842
  * TODO: Prefer to remove this, but currently we have motion components with
6852
6843
  * display: contents in Framer.
6853
6844
  */
6854
- const { visualElement } = node.options;
6855
- if (visualElement &&
6856
- visualElement.props.style &&
6857
- visualElement.props.style.display === "contents") {
6845
+ if (node.options.visualElement?.props.style?.display === "contents") {
6858
6846
  continue;
6859
6847
  }
6860
- if (isSharedTransition &&
6848
+ if (isSharedTransition === true &&
6861
6849
  node.options.layoutScroll &&
6862
6850
  node.scroll &&
6863
6851
  node !== node.root) {
@@ -6871,7 +6859,9 @@
6871
6859
  // Apply each ancestor's calculated delta into this component's recorded layout box
6872
6860
  applyBoxDelta(box, delta);
6873
6861
  }
6874
- if (isSharedTransition && hasTransform(node.latestValues)) {
6862
+ if (isSharedTransition &&
6863
+ (isSharedTransition.path?.[i] || isSharedTransition) !== node &&
6864
+ hasTransform(node.latestValues)) {
6875
6865
  transformBox(box, node.latestValues, node.layout?.layoutBox);
6876
6866
  }
6877
6867
  }
@@ -7044,6 +7034,41 @@
7044
7034
  const { schedule: microtask, cancel: cancelMicrotask } =
7045
7035
  /* @__PURE__ */ createRenderBatcher(queueMicrotask, false);
7046
7036
 
7037
+ /**
7038
+ * Process a frame now, for driving Motion from a loop other than
7039
+ * window.requestAnimationFrame, like an XRSession's during an immersive
7040
+ * WebXR session. Wherever window.requestAnimationFrame does fire, Motion
7041
+ * keeps processing frames from it too.
7042
+ *
7043
+ * `timestamp` defaults to now. For a timeline you control, set
7044
+ * `MotionGlobalConfig.useManualTiming = true` and pass each frame's time in
7045
+ * milliseconds.
7046
+ *
7047
+ * Animations Motion hands to the browser (WAAPI), like `opacity` or
7048
+ * `transform` on an HTML or SVG element, follow the browser's clock. With
7049
+ * `useManualTiming` on, Motion runs those in JS instead, except for
7050
+ * `motion/mini`, view transitions and optimized appear animations, and
7051
+ * colours only the browser can mix, like `oklch()`, which jump to their end.
7052
+ *
7053
+ * Ignored when called inside a frame.
7054
+ */
7055
+ const processFrame = (timestamp = time.now()) => {
7056
+ if (frameData.isProcessing)
7057
+ return;
7058
+ const delta = timestamp - frameData.timestamp;
7059
+ // Clamp like the requestAnimationFrame loop, unless the timeline is ours
7060
+ frameData.delta = MotionGlobalConfig.useManualTiming
7061
+ ? delta
7062
+ : Math.max(Math.min(delta, 40), 1);
7063
+ time.set((frameData.timestamp = timestamp));
7064
+ frameData.isProcessing = true;
7065
+ // frameSteps' keys are in stepsOrder
7066
+ for (const step in frameSteps) {
7067
+ frameSteps[step].process(frameData);
7068
+ }
7069
+ frameData.isProcessing = false;
7070
+ };
7071
+
7047
7072
  function animateSingleValue(value, keyframes, options) {
7048
7073
  const motionValue$1 = isMotionValue(value) ? value : motionValue(value);
7049
7074
  motionValue$1.start(animateMotionValue("", motionValue$1, keyframes, options));
@@ -7103,15 +7128,6 @@
7103
7128
  const asNumber = (v) => typeof v === "number" ? v : parseFloat(v);
7104
7129
 
7105
7130
  const numBorders = cornerRadiusProps.length;
7106
- const mixedTransforms = [
7107
- "rotate",
7108
- "rotateX",
7109
- "rotateY",
7110
- "rotateZ",
7111
- "skewX",
7112
- "skewY",
7113
- ];
7114
- const numMixedTransforms = mixedTransforms.length;
7115
7131
  const isPx = (value) => typeof value === "number" || px.test(value);
7116
7132
  function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
7117
7133
  if (shouldCrossfadeOpacity) {
@@ -7149,8 +7165,8 @@
7149
7165
  * Mix rotation and skew. These can't be projected, so mix them every
7150
7166
  * frame rather than leaving them frozen at the animation-start snapshot.
7151
7167
  */
7152
- for (let i = 0; i < numMixedTransforms; i++) {
7153
- const key = mixedTransforms[i];
7168
+ for (let i = 0; i < angleTransforms.length; i++) {
7169
+ const key = angleTransforms[i];
7154
7170
  if (follow[key] || lead[key]) {
7155
7171
  target[key] = mixNumber$1(follow[key] || 0, lead[key] || 0, progress);
7156
7172
  }
@@ -7233,8 +7249,8 @@
7233
7249
  }
7234
7250
  }
7235
7251
  function calcBoxDelta(delta, source, target, origin) {
7236
- calcAxisDelta(delta.x, source.x, target.x, origin ? origin.originX : undefined);
7237
- calcAxisDelta(delta.y, source.y, target.y, origin ? origin.originY : undefined);
7252
+ calcAxisDelta(delta.x, source.x, target.x, origin?.originX);
7253
+ calcAxisDelta(delta.y, source.y, target.y, origin?.originY);
7238
7254
  }
7239
7255
  function calcRelativeAxis(target, relative, parent, anchor = 0) {
7240
7256
  const anchorPoint = anchor
@@ -7304,8 +7320,8 @@
7304
7320
  * and acts as a bridge between motion values and removeAxisDelta
7305
7321
  */
7306
7322
  function removeBoxTransforms(box, transforms, originBox, sourceBox) {
7307
- removeAxisTransforms(box.x, transforms, xKeys, originBox ? originBox.x : undefined, sourceBox ? sourceBox.x : undefined);
7308
- removeAxisTransforms(box.y, transforms, yKeys, originBox ? originBox.y : undefined, sourceBox ? sourceBox.y : undefined);
7323
+ removeAxisTransforms(box.x, transforms, xKeys, originBox?.x, sourceBox?.x);
7324
+ removeAxisTransforms(box.y, transforms, yKeys, originBox?.y, sourceBox?.y);
7309
7325
  }
7310
7326
 
7311
7327
  const createAxisDelta = () => ({
@@ -7503,10 +7519,9 @@
7503
7519
  };
7504
7520
 
7505
7521
  const defaultScaleCorrectors = {
7506
- borderRadius: {
7507
- ...correctBorderRadius,
7508
- applyTo: [...cornerRadiusProps],
7509
- },
7522
+ borderRadius: /* @__PURE__ */ Object.assign({}, correctBorderRadius, {
7523
+ applyTo: /* @__PURE__ */ cornerRadiusProps.slice(),
7524
+ }),
7510
7525
  borderTopLeftRadius: correctBorderRadius,
7511
7526
  borderTopRightRadius: correctBorderRadius,
7512
7527
  borderBottomLeftRadius: correctBorderRadius,
@@ -7536,24 +7551,18 @@
7536
7551
  transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `;
7537
7552
  }
7538
7553
  if (latestTransform) {
7539
- const { transformPerspective, rotate, pathRotation, rotateX, rotateY, rotateZ, skewX, skewY, } = latestTransform;
7554
+ const { transformPerspective } = latestTransform;
7540
7555
  if (transformPerspective)
7541
7556
  transform = `perspective(${transformPerspective}px) ${transform}`;
7542
- if (rotate)
7543
- transform += `rotate(${rotate}deg) `;
7544
- // Additive `rotate()` so user `rotate` isn't clobbered.
7545
- if (pathRotation)
7546
- transform += `rotate(${pathRotation}deg) `;
7547
- if (rotateX)
7548
- transform += `rotateX(${rotateX}deg) `;
7549
- if (rotateY)
7550
- transform += `rotateY(${rotateY}deg) `;
7551
- if (rotateZ)
7552
- transform += `rotateZ(${rotateZ}deg) `;
7553
- if (skewX)
7554
- transform += `skewX(${skewX}deg) `;
7555
- if (skewY)
7556
- transform += `skewY(${skewY}deg) `;
7557
+ for (let i = 0; i < angleTransforms.length; i++) {
7558
+ const key = angleTransforms[i];
7559
+ const value = latestTransform[key];
7560
+ if (value)
7561
+ transform += `${key}(${value}deg) `;
7562
+ // Additive `rotate()` so user `rotate` isn't clobbered.
7563
+ if (!i && latestTransform.pathRotation)
7564
+ transform += `rotate(${latestTransform.pathRotation}deg) `;
7565
+ }
7557
7566
  }
7558
7567
  /**
7559
7568
  * Apply scale to match the size of the element to the size we want it.
@@ -7611,20 +7620,15 @@
7611
7620
  hasEverUpdated: false,
7612
7621
  };
7613
7622
 
7614
- /**
7615
- * Correctors registered via addScaleCorrector before projection loaded
7616
- * (e.g. with an async LazyMotion bundle) take precedence over the defaults.
7617
- */
7618
- Object.assign(scaleCorrectors, {
7619
- ...defaultScaleCorrectors,
7620
- ...scaleCorrectors,
7621
- });
7622
7623
  const metrics = {
7623
7624
  nodes: 0,
7624
7625
  calculatedTargetDeltas: 0,
7625
7626
  calculatedProjections: 0,
7626
7627
  };
7627
- const transformAxes = ["", "X", "Y", "Z"];
7628
+ /**
7629
+ * Transforms that distort an element's layout, so must be reset before measuring.
7630
+ */
7631
+ const distortingTransforms = ["z", "skew", "skewZ"];
7628
7632
  /**
7629
7633
  * We use 1000 as the animation target as 0-1000 maps better to pixels than 0-1
7630
7634
  * which has a noticeable difference in spring animations
@@ -7661,6 +7665,16 @@
7661
7665
  }
7662
7666
  }
7663
7667
  function createProjectionNode({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
7668
+ /**
7669
+ * Correctors registered via addScaleCorrector before projection loaded
7670
+ * (e.g. with an async LazyMotion bundle) take precedence over the defaults.
7671
+ * Applied here rather than at module scope so bundlers can drop projection
7672
+ * when it's unused.
7673
+ */
7674
+ Object.assign(scaleCorrectors, {
7675
+ ...defaultScaleCorrectors,
7676
+ ...scaleCorrectors,
7677
+ });
7664
7678
  return class ProjectionNode {
7665
7679
  constructor(latestValues = {}, parent = defaultParent?.()) {
7666
7680
  /**
@@ -7822,8 +7836,7 @@
7822
7836
  return this.eventHandlers.get(name).add(handler);
7823
7837
  }
7824
7838
  notifyListeners(name, ...args) {
7825
- const subscriptionManager = this.eventHandlers.get(name);
7826
- subscriptionManager && subscriptionManager.notify(...args);
7839
+ this.eventHandlers.get(name)?.notify(...args);
7827
7840
  }
7828
7841
  hasListeners(name) {
7829
7842
  return this.eventHandlers.has(name);
@@ -7841,7 +7854,7 @@
7841
7854
  visualElement.mount(instance);
7842
7855
  }
7843
7856
  this.root.nodes.add(this);
7844
- this.parent && this.parent.children.add(this);
7857
+ this.parent?.children.add(this);
7845
7858
  if (this.root.hasTreeAnimated && (layout || layoutId)) {
7846
7859
  this.isLayoutDirty = true;
7847
7860
  }
@@ -7859,7 +7872,7 @@
7859
7872
  return;
7860
7873
  innerWidth = newInnerWidth;
7861
7874
  this.root.updateBlockedByResize = true;
7862
- cancelDelay && cancelDelay();
7875
+ cancelDelay?.();
7863
7876
  cancelDelay = delay(resizeUnblockUpdate, 250);
7864
7877
  if (globalProjectionState.hasAnimatedSinceResize) {
7865
7878
  globalProjectionState.hasAnimatedSinceResize = false;
@@ -7950,9 +7963,7 @@
7950
7963
  if (!hasLayoutChanged) {
7951
7964
  finishAnimation(this);
7952
7965
  }
7953
- if (this.isLead() && this.options.onExitComplete) {
7954
- this.options.onExitComplete();
7955
- }
7966
+ this.isLead() && this.options.onExitComplete?.();
7956
7967
  }
7957
7968
  this.targetLayout = newLayout;
7958
7969
  });
@@ -7960,9 +7971,8 @@
7960
7971
  unmount() {
7961
7972
  this.options.layoutId && this.willUpdate();
7962
7973
  this.root.nodes.remove(this);
7963
- const stack = this.getStack();
7964
- stack && stack.remove(this);
7965
- this.parent && this.parent.children.delete(this);
7974
+ this.getStack()?.remove(this);
7975
+ this.parent?.children.delete(this);
7966
7976
  this.instance = undefined;
7967
7977
  this.eventHandlers.clear();
7968
7978
  this.hasLayoutListener = false;
@@ -7983,7 +7993,7 @@
7983
7993
  if (this.isUpdateBlocked())
7984
7994
  return;
7985
7995
  this.isUpdating = true;
7986
- this.nodes && this.nodes.forEach(resetSkewAndRotation);
7996
+ this.nodes?.forEach(resetSkewAndRotation);
7987
7997
  this.animationId++;
7988
7998
  }
7989
7999
  getTransformTemplate() {
@@ -7993,7 +8003,7 @@
7993
8003
  willUpdate(shouldNotifyListeners = true) {
7994
8004
  this.root.hasTreeAnimated = true;
7995
8005
  if (this.root.isUpdateBlocked()) {
7996
- this.options.onExitComplete && this.options.onExitComplete();
8006
+ this.options.onExitComplete?.();
7997
8007
  return;
7998
8008
  }
7999
8009
  /**
@@ -8210,24 +8220,23 @@
8210
8220
  this.projectionDelta = undefined;
8211
8221
  this.notifyListeners("measure", this.layout.layoutBox);
8212
8222
  const { visualElement } = this.options;
8213
- visualElement &&
8214
- visualElement.notify("LayoutMeasure", this.layout.layoutBox, prevLayout ? prevLayout.layoutBox : undefined);
8223
+ visualElement?.notify("LayoutMeasure", this.layout.layoutBox, prevLayout?.layoutBox);
8215
8224
  }
8216
8225
  updateScroll(phase = "measure") {
8217
- let needsMeasurement = Boolean(this.options.layoutScroll && this.instance);
8218
- if (this.scroll &&
8219
- this.scroll.animationId === this.root.animationId &&
8220
- this.scroll.phase === phase) {
8221
- needsMeasurement = false;
8222
- }
8223
- if (needsMeasurement && this.instance) {
8224
- const isRoot = checkIsScrollRoot(this.instance);
8226
+ const { scroll, instance } = this;
8227
+ const { animationId } = this.root;
8228
+ if (this.options.layoutScroll &&
8229
+ instance &&
8230
+ !(scroll &&
8231
+ scroll.animationId === animationId &&
8232
+ scroll.phase === phase)) {
8233
+ const isRoot = checkIsScrollRoot(instance);
8225
8234
  this.scroll = {
8226
- animationId: this.root.animationId,
8235
+ animationId,
8227
8236
  phase,
8228
8237
  isRoot,
8229
- offset: measureScroll(this.instance),
8230
- wasRoot: this.scroll ? this.scroll.isRoot : isRoot,
8238
+ offset: measureScroll(instance),
8239
+ wasRoot: scroll ? scroll.isRoot : isRoot,
8231
8240
  };
8232
8241
  }
8233
8242
  }
@@ -8317,10 +8326,10 @@
8317
8326
  }
8318
8327
  return boxWithoutScroll;
8319
8328
  }
8320
- applyTransform(box, transformOnly = false, output) {
8329
+ applyTransform(box, transformOnly = false, output, skipOwnTransform, start = 0) {
8321
8330
  const withTransforms = output || createBox();
8322
8331
  copyBoxInto(withTransforms, box);
8323
- for (let i = 0; i < this.path.length; i++) {
8332
+ for (let i = start; i < this.path.length; i++) {
8324
8333
  const node = this.path[i];
8325
8334
  if (!transformOnly &&
8326
8335
  node.options.layoutScroll &&
@@ -8333,7 +8342,7 @@
8333
8342
  continue;
8334
8343
  transformBox(withTransforms, node.latestValues, node.layout?.layoutBox);
8335
8344
  }
8336
- if (hasTransform(this.latestValues)) {
8345
+ if (!skipOwnTransform && hasTransform(this.latestValues)) {
8337
8346
  transformBox(withTransforms, this.latestValues, this.layout?.layoutBox);
8338
8347
  }
8339
8348
  return withTransforms;
@@ -8468,7 +8477,11 @@
8468
8477
  }
8469
8478
  else if (this.targetDelta) {
8470
8479
  if (Boolean(this.resumingFrom)) {
8471
- this.applyTransform(this.layout.layoutBox, false, this.target);
8480
+ /**
8481
+ * Own transforms are applied later, in applyTransformsToTarget,
8482
+ * so only apply ancestor transforms here or they'd be doubled.
8483
+ */
8484
+ this.applyTransform(this.layout.layoutBox, false, this.target, true);
8472
8485
  }
8473
8486
  else {
8474
8487
  copyBoxInto(this.target, this.layout.layoutBox);
@@ -8488,8 +8501,13 @@
8488
8501
  this.attemptToResolveRelativeTarget = false;
8489
8502
  if (this.options.layoutAnchor !== false &&
8490
8503
  relativeParent &&
8491
- Boolean(relativeParent.resumingFrom) ===
8492
- Boolean(this.resumingFrom) &&
8504
+ (!relativeParent.resumingFrom === !this.resumingFrom ||
8505
+ /**
8506
+ * Shared layout animations within a layoutRoot
8507
+ * animate relative to it.
8508
+ */
8509
+ (relativeParent.options.layoutRoot &&
8510
+ !relativeParent.resumingFrom)) &&
8493
8511
  !relativeParent.options.layoutScroll &&
8494
8512
  relativeParent.target &&
8495
8513
  this.animationProgress !== 1) {
@@ -8569,7 +8587,7 @@
8569
8587
  * If this section of the tree isn't animating we can
8570
8588
  * delete our target sources for the following frame.
8571
8589
  */
8572
- this.isTreeAnimating = Boolean((this.parent && this.parent.isTreeAnimating) ||
8590
+ this.isTreeAnimating = Boolean(this.parent?.isTreeAnimating ||
8573
8591
  this.currentAnimation ||
8574
8592
  this.pendingAnimation);
8575
8593
  if (!this.isTreeAnimating) {
@@ -8590,8 +8608,17 @@
8590
8608
  /**
8591
8609
  * Apply all the parent deltas to this box to produce the corrected box. This
8592
8610
  * is the layout box, as it will appear on screen as a result of the transforms of its parents.
8611
+ *
8612
+ * A shared target is usually in page space, so ancestor transforms are applied
8613
+ * too. But a target relative to a parent that isn't itself shared (like a
8614
+ * layoutRoot) is in that parent's layout space. So the transforms of the
8615
+ * parent and its ancestors, which move both elements alike, are skipped,
8616
+ * but those of other ancestors (between it and either element) apply.
8593
8617
  */
8594
- applyTreeDeltas(this.layoutCorrected, this.treeScale, this.path, isShared);
8618
+ applyTreeDeltas(this.layoutCorrected, this.treeScale, this.path, isShared &&
8619
+ (lead.relativeTarget && !lead.relativeParent.resumingFrom
8620
+ ? lead.relativeParent
8621
+ : true));
8595
8622
  /**
8596
8623
  * If this layer needs to perform scale correction but doesn't have a target,
8597
8624
  * use the layout as the target.
@@ -8658,8 +8685,7 @@
8658
8685
  scheduleRender(notifyAll = true) {
8659
8686
  this.options.visualElement?.scheduleRender();
8660
8687
  if (notifyAll) {
8661
- const stack = this.getStack();
8662
- stack && stack.scheduleRender();
8688
+ this.getStack()?.scheduleRender();
8663
8689
  }
8664
8690
  if (this.resumingFrom && !this.resumingFrom.instance) {
8665
8691
  this.resumingFrom = undefined;
@@ -8681,9 +8707,7 @@
8681
8707
  }
8682
8708
  this.attemptToResolveRelativeTarget = !hasOnlyRelativeTargetChanged;
8683
8709
  const relativeLayout = createBox();
8684
- const snapshotSource = snapshot ? snapshot.source : undefined;
8685
- const layoutSource = this.layout ? this.layout.source : undefined;
8686
- const isSharedLayoutAnimation = snapshotSource !== layoutSource;
8710
+ const isSharedLayoutAnimation = snapshot?.source !== this.layout?.source;
8687
8711
  const stack = this.getStack();
8688
8712
  const isOnlyMember = !stack || stack.members.length <= 1;
8689
8713
  const shouldCrossfadeOpacity = Boolean(isSharedLayoutAnimation &&
@@ -8698,19 +8722,11 @@
8698
8722
  this.mixTargetDelta = (latest) => {
8699
8723
  const progress = latest / 1000;
8700
8724
  const point = interpolate?.(progress);
8725
+ mixAxisDeltaLinear(targetDelta.x, delta.x, progress);
8726
+ mixAxisDeltaLinear(targetDelta.y, delta.y, progress);
8701
8727
  if (point) {
8702
8728
  targetDelta.x.translate = point.x;
8703
- targetDelta.x.scale = mixNumber$1(delta.x.scale, 1, progress);
8704
- targetDelta.x.origin = delta.x.origin;
8705
- targetDelta.x.originPoint = delta.x.originPoint;
8706
8729
  targetDelta.y.translate = point.y;
8707
- targetDelta.y.scale = mixNumber$1(delta.y.scale, 1, progress);
8708
- targetDelta.y.origin = delta.y.origin;
8709
- targetDelta.y.originPoint = delta.y.originPoint;
8710
- }
8711
- else {
8712
- mixAxisDeltaLinear(targetDelta.x, delta.x, progress);
8713
- mixAxisDeltaLinear(targetDelta.y, delta.y, progress);
8714
8730
  }
8715
8731
  this.setTargetDelta(targetDelta);
8716
8732
  if (this.relativeTarget &&
@@ -8736,7 +8752,7 @@
8736
8752
  this.animationValues = mixedValues;
8737
8753
  mixValues(mixedValues, snapshotLatestValues, this.latestValues, progress, shouldCrossfadeOpacity, isOnlyMember);
8738
8754
  }
8739
- if (point && point.rotate !== undefined) {
8755
+ if (point?.rotate !== undefined) {
8740
8756
  // Dedicated `pathRotation` channel, not `rotate`, so an
8741
8757
  // animating `rotate` is composed with, never clobbered.
8742
8758
  if (!this.animationValues)
@@ -8801,7 +8817,7 @@
8801
8817
  }
8802
8818
  finishAnimation() {
8803
8819
  if (this.currentAnimation) {
8804
- this.mixTargetDelta && this.mixTargetDelta(animationTarget);
8820
+ this.mixTargetDelta?.(animationTarget);
8805
8821
  this.currentAnimation.stop();
8806
8822
  }
8807
8823
  this.completeAnimation();
@@ -8821,13 +8837,11 @@
8821
8837
  this.layout &&
8822
8838
  layout &&
8823
8839
  shouldAnimatePositionOnly(this.options.animationType, this.layout.layoutBox, layout.layoutBox)) {
8840
+ const { layoutBox } = this.layout;
8841
+ const leadTarget = target;
8824
8842
  target = this.target || createBox();
8825
- const xLength = calcLength(this.layout.layoutBox.x);
8826
- target.x.min = lead.target.x.min;
8827
- target.x.max = target.x.min + xLength;
8828
- const yLength = calcLength(this.layout.layoutBox.y);
8829
- target.y.min = lead.target.y.min;
8830
- target.y.max = target.y.min + yLength;
8843
+ alignAxis(target.x, leadTarget.x, layoutBox.x);
8844
+ alignAxis(target.y, leadTarget.y, layoutBox.y);
8831
8845
  }
8832
8846
  copyBoxInto(targetWithTransforms, target);
8833
8847
  /**
@@ -8852,8 +8866,8 @@
8852
8866
  stack.add(node);
8853
8867
  const config = node.options.initialPromotionConfig;
8854
8868
  node.promote({
8855
- transition: config ? config.transition : undefined,
8856
- preserveFollowOpacity: config && config.shouldPreserveFollowOpacity
8869
+ transition: config?.transition,
8870
+ preserveFollowOpacity: config?.shouldPreserveFollowOpacity
8857
8871
  ? config.shouldPreserveFollowOpacity(node)
8858
8872
  : undefined,
8859
8873
  });
@@ -8876,9 +8890,7 @@
8876
8890
  return this.root.sharedNodes.get(layoutId);
8877
8891
  }
8878
8892
  promote({ needsReset, transition, preserveFollowOpacity, } = {}) {
8879
- const stack = this.getStack();
8880
- if (stack)
8881
- stack.promote(this, preserveFollowOpacity);
8893
+ this.getStack()?.promote(this, preserveFollowOpacity);
8882
8894
  if (needsReset) {
8883
8895
  this.projectionDelta = undefined;
8884
8896
  this.needsReset = true;
@@ -8887,45 +8899,27 @@
8887
8899
  this.setOptions({ transition });
8888
8900
  }
8889
8901
  relegate() {
8890
- const stack = this.getStack();
8891
- if (stack) {
8892
- return stack.relegate(this);
8893
- }
8894
- else {
8895
- return false;
8896
- }
8902
+ return this.getStack()?.relegate(this) || false;
8897
8903
  }
8898
8904
  resetSkewAndRotation() {
8899
8905
  const { visualElement } = this.options;
8900
8906
  if (!visualElement)
8901
8907
  return;
8902
- // If there's no detected skew or rotation values, we can early return without a forced render.
8903
- let hasDistortingTransform = false;
8904
8908
  /**
8905
8909
  * An unrolled check for rotation values. Most elements don't have any rotation and
8906
- * skipping the nested loop and new object creation is 50% faster.
8910
+ * skipping the loop and new object creation is 50% faster.
8911
+ * If there's no distorting values, we don't need to do any more.
8907
8912
  */
8908
8913
  const { latestValues } = visualElement;
8909
- if (latestValues.z ||
8910
- latestValues.rotate ||
8911
- latestValues.rotateX ||
8912
- latestValues.rotateY ||
8913
- latestValues.rotateZ ||
8914
- latestValues.skewX ||
8915
- latestValues.skewY) {
8916
- hasDistortingTransform = true;
8917
- }
8918
- // If there's no distorting values, we don't need to do any more.
8919
- if (!hasDistortingTransform)
8914
+ if (!hasDistortingTransform(latestValues))
8920
8915
  return;
8921
8916
  const resetValues = {};
8922
- if (latestValues.z) {
8923
- resetDistortingTransform("z", visualElement, resetValues, this.animationValues);
8924
- }
8925
8917
  // Check the skew and rotate value of all axes and reset to 0
8926
- for (let i = 0; i < transformAxes.length; i++) {
8927
- resetDistortingTransform(`rotate${transformAxes[i]}`, visualElement, resetValues, this.animationValues);
8928
- resetDistortingTransform(`skew${transformAxes[i]}`, visualElement, resetValues, this.animationValues);
8918
+ for (let i = 0; i < distortingTransforms.length; i++) {
8919
+ resetDistortingTransform(distortingTransforms[i], visualElement, resetValues, this.animationValues);
8920
+ }
8921
+ for (let i = 0; i < angleTransforms.length; i++) {
8922
+ resetDistortingTransform(angleTransforms[i], visualElement, resetValues, this.animationValues);
8929
8923
  }
8930
8924
  // Force a render of this element to apply the transform with all skews and rotations
8931
8925
  // set to 0.
@@ -8945,7 +8939,13 @@
8945
8939
  styleProp) {
8946
8940
  if (!this.instance || this.isSVG)
8947
8941
  return;
8948
- if (!this.isVisible) {
8942
+ const lead = this.getLead();
8943
+ /**
8944
+ * Without crossfade, a new lead hides the previous one, as it's
8945
+ * usually unmounting. One that gave its layoutId away keeps
8946
+ * rendering, so only hide nodes that follow another.
8947
+ */
8948
+ if (!this.isVisible && lead !== this) {
8949
8949
  targetStyle.visibility = "hidden";
8950
8950
  return;
8951
8951
  }
@@ -8961,9 +8961,10 @@
8961
8961
  : "none";
8962
8962
  return;
8963
8963
  }
8964
- const lead = this.getLead();
8965
8964
  if (!this.projectionDelta || !this.layout || !lead.target) {
8966
- if (this.options.layoutId) {
8965
+ if (this.options.layoutId || this.needsShow) {
8966
+ this.needsShow = false;
8967
+ targetStyle.visibility = "";
8967
8968
  targetStyle.opacity =
8968
8969
  this.latestValues.opacity !== undefined
8969
8970
  ? this.latestValues.opacity
@@ -9097,52 +9098,49 @@
9097
9098
  const isShared = snapshot.source !== node.layout.source;
9098
9099
  // TODO Maybe we want to also resize the layout snapshot so we don't trigger
9099
9100
  // animations for instance if layout="size" and an element has only changed position
9100
- if (animationType === "size") {
9101
- eachAxis((axis) => {
9102
- const axisSnapshot = isShared
9103
- ? snapshot.measuredBox[axis]
9104
- : snapshot.layoutBox[axis];
9105
- const length = calcLength(axisSnapshot);
9106
- axisSnapshot.min = layout[axis].min;
9107
- axisSnapshot.max = axisSnapshot.min + length;
9108
- });
9109
- }
9110
- else if (animationType === "x" || animationType === "y") {
9111
- const snapAxis = animationType === "x" ? "y" : "x";
9112
- copyAxisInto(isShared
9113
- ? snapshot.measuredBox[snapAxis]
9114
- : snapshot.layoutBox[snapAxis], layout[snapAxis]);
9115
- }
9116
- else if (shouldAnimatePositionOnly(animationType, snapshot.layoutBox, layout)) {
9117
- eachAxis((axis) => {
9118
- const axisSnapshot = isShared
9119
- ? snapshot.measuredBox[axis]
9120
- : snapshot.layoutBox[axis];
9121
- const length = calcLength(layout[axis]);
9122
- axisSnapshot.max = axisSnapshot.min + length;
9123
- /**
9124
- * Ensure relative target gets resized and rerendererd
9125
- */
9126
- if (node.relativeTarget && !node.currentAnimation) {
9127
- node.isProjectionDirty = true;
9128
- node.relativeTarget[axis].max =
9129
- node.relativeTarget[axis].min + length;
9130
- }
9131
- });
9132
- }
9101
+ const resizeSnapshot = (box, to) => {
9102
+ if (animationType === "size") {
9103
+ eachAxis((axis) => {
9104
+ const length = calcLength(box[axis]);
9105
+ box[axis].min = to[axis].min;
9106
+ box[axis].max = box[axis].min + length;
9107
+ });
9108
+ }
9109
+ else if (animationType === "x" || animationType === "y") {
9110
+ const snapAxis = animationType === "x" ? "y" : "x";
9111
+ copyAxisInto(box[snapAxis], to[snapAxis]);
9112
+ }
9113
+ else if (shouldAnimatePositionOnly(animationType, snapshot.layoutBox, layout)) {
9114
+ eachAxis((axis) => {
9115
+ const length = calcLength(to[axis]);
9116
+ box[axis].max = box[axis].min + length;
9117
+ /**
9118
+ * Ensure relative target gets resized and rerendererd
9119
+ */
9120
+ if (node.relativeTarget && !node.currentAnimation) {
9121
+ node.isProjectionDirty = true;
9122
+ node.relativeTarget[axis].max =
9123
+ node.relativeTarget[axis].min + length;
9124
+ }
9125
+ });
9126
+ }
9127
+ };
9128
+ const snapshotBox = isShared ? snapshot.measuredBox : snapshot.layoutBox;
9129
+ resizeSnapshot(snapshotBox, layout);
9133
9130
  const layoutDelta = createDelta();
9134
9131
  calcBoxDelta(layoutDelta, layout, snapshot.layoutBox);
9135
9132
  const visualDelta = createDelta();
9136
- if (isShared) {
9137
- calcBoxDelta(visualDelta, node.applyTransform(measuredLayout, true), snapshot.measuredBox);
9138
- }
9139
- else {
9140
- calcBoxDelta(visualDelta, layout, snapshot.layoutBox);
9141
- }
9133
+ calcBoxDelta(visualDelta, isShared ? node.applyTransform(measuredLayout, true) : layout, snapshotBox);
9142
9134
  const hasLayoutChanged = !isDeltaZero(layoutDelta);
9143
9135
  let hasRelativeLayoutChanged = false;
9144
- if (!node.resumeFrom) {
9145
- const relativeParent = node.getClosestProjectingParent();
9136
+ const relativeParent = node.getClosestProjectingParent();
9137
+ /**
9138
+ * A snapshot resumed from another element is only relative to this
9139
+ * node's relativeParent if it's a layoutRoot that contains both.
9140
+ */
9141
+ if (!node.resumeFrom ||
9142
+ (relativeParent?.options.layoutRoot &&
9143
+ node.resumeFrom.path.includes(relativeParent))) {
9146
9144
  /**
9147
9145
  * If the relativeParent is itself resuming from a different element then
9148
9146
  * the relative snapshot is not relavent
@@ -9151,10 +9149,22 @@
9151
9149
  const { snapshot: parentSnapshot, layout: parentLayout } = relativeParent;
9152
9150
  if (parentSnapshot && parentLayout) {
9153
9151
  const anchor = node.options.layoutAnchor || undefined;
9152
+ /**
9153
+ * A shared snapshot includes the transforms of the old
9154
+ * element's ancestors below the layoutRoot, as they
9155
+ * aren't shared with the new element.
9156
+ */
9154
9157
  const relativeSnapshot = createBox();
9155
- calcRelativePosition(relativeSnapshot, snapshot.layoutBox, parentSnapshot.layoutBox, anchor);
9158
+ calcRelativePosition(relativeSnapshot, isShared
9159
+ ? node.resumeFrom.applyTransform(snapshot.layoutBox, true, undefined, true, relativeParent.depth + 1)
9160
+ : snapshot.layoutBox, parentSnapshot.layoutBox, anchor);
9156
9161
  const relativeLayout = createBox();
9157
9162
  calcRelativePosition(relativeLayout, layout, parentLayout.layoutBox, anchor);
9163
+ /**
9164
+ * A shared snapshot is resized in page space, above, so
9165
+ * resize it relative to the layoutRoot too.
9166
+ */
9167
+ isShared && resizeSnapshot(relativeSnapshot, relativeLayout);
9158
9168
  if (!boxEqualsRounded(relativeSnapshot, relativeLayout)) {
9159
9169
  hasRelativeLayoutChanged = true;
9160
9170
  }
@@ -9176,8 +9186,7 @@
9176
9186
  });
9177
9187
  }
9178
9188
  else if (node.isLead()) {
9179
- const { onExitComplete } = node.options;
9180
- onExitComplete && onExitComplete();
9189
+ node.options.onExitComplete?.();
9181
9190
  }
9182
9191
  /**
9183
9192
  * Clearing transition
@@ -9237,10 +9246,13 @@
9237
9246
  * A dragged node doesn't layout animate, so it jumps to its new layout. Shift
9238
9247
  * its descendants' snapshots by the same amount, so they only animate layout
9239
9248
  * changes relative to it, rather than lagging behind.
9249
+ *
9250
+ * If its drag ended since the snapshot (e.g. drag disabled from onDrag), it
9251
+ * layout animates again, so its descendants mustn't follow its jump.
9240
9252
  */
9241
9253
  function followDraggedAncestor(node) {
9242
9254
  const { snapshot, dragAncestor } = node;
9243
- const to = dragAncestor?.layout;
9255
+ const to = dragAncestor?.isAnimationBlocked && dragAncestor.layout;
9244
9256
  if (!snapshot || !to || node.resumeFrom || node.isAnimationBlocked)
9245
9257
  return;
9246
9258
  const from = dragAncestor.dragSnapshot;
@@ -9280,7 +9292,7 @@
9280
9292
  }
9281
9293
  function resetTransformStyle(node) {
9282
9294
  const { visualElement } = node.options;
9283
- if (visualElement && visualElement.getProps().onBeforeLayoutMeasure) {
9295
+ if (visualElement?.getProps().onBeforeLayoutMeasure) {
9284
9296
  visualElement.notify("BeforeLayoutMeasure");
9285
9297
  }
9286
9298
  node.resetTransform();
@@ -9314,7 +9326,7 @@
9314
9326
  mixAxis(output.y, from.y, to.y, p);
9315
9327
  }
9316
9328
  function hasOpacityCrossfade(node) {
9317
- return (node.animationValues && node.animationValues.opacityExit !== undefined);
9329
+ return node.animationValues?.opacityExit !== undefined;
9318
9330
  }
9319
9331
  const defaultLayoutTransition = {
9320
9332
  duration: 0.45,
@@ -9340,6 +9352,13 @@
9340
9352
  roundAxis(box.x);
9341
9353
  roundAxis(box.y);
9342
9354
  }
9355
+ /**
9356
+ * Align an axis to the start of another, keeping the length of a layout axis.
9357
+ * The length is read first, as output can be the layout axis itself.
9358
+ */
9359
+ function alignAxis(output, from, layout) {
9360
+ output.max = calcLength(layout) + (output.min = from.min);
9361
+ }
9343
9362
  function shouldAnimatePositionOnly(animationType, snapshot, layout) {
9344
9363
  return (animationType === "position" ||
9345
9364
  (animationType === "preserve-aspect" &&
@@ -9354,11 +9373,16 @@
9354
9373
  return () => target.removeEventListener(eventName, handler, options);
9355
9374
  }
9356
9375
 
9357
- const DocumentProjectionNode = createProjectionNode({
9376
+ const DocumentProjectionNode =
9377
+ /* @__PURE__ */ createProjectionNode({
9358
9378
  attachResizeListener: (ref, notify) => addDomEvent(ref, "resize", notify),
9359
9379
  measureScroll: () => ({
9360
- x: document.documentElement.scrollLeft || document.body?.scrollLeft || 0,
9361
- y: document.documentElement.scrollTop || document.body?.scrollTop || 0,
9380
+ x: document.documentElement.scrollLeft ||
9381
+ document.body?.scrollLeft ||
9382
+ 0,
9383
+ y: document.documentElement.scrollTop ||
9384
+ document.body?.scrollTop ||
9385
+ 0,
9362
9386
  }),
9363
9387
  checkIsScrollRoot: () => true,
9364
9388
  });
@@ -9366,7 +9390,9 @@
9366
9390
  const rootProjectionNode = {
9367
9391
  current: undefined,
9368
9392
  };
9369
- const HTMLProjectionNode = createProjectionNode({
9393
+
9394
+ const HTMLProjectionNode =
9395
+ /* @__PURE__ */ createProjectionNode({
9370
9396
  measureScroll: (instance) => ({
9371
9397
  x: instance.scrollLeft,
9372
9398
  y: instance.scrollTop,
@@ -9383,7 +9409,7 @@
9383
9409
  resetTransform: (instance, value) => {
9384
9410
  instance.style.transform = value !== undefined ? value : "none";
9385
9411
  },
9386
- checkIsScrollRoot: (instance) => Boolean(window.getComputedStyle(instance).position === "fixed"),
9412
+ checkIsScrollRoot: (instance) => window.getComputedStyle(instance).position === "fixed",
9387
9413
  });
9388
9414
 
9389
9415
  const visualElementStore = new WeakMap();
@@ -9410,7 +9436,7 @@
9410
9436
  "whileDrag",
9411
9437
  "exit",
9412
9438
  ];
9413
- const variantProps = ["initial", ...variantPriorityOrder];
9439
+ const variantProps = /* @__PURE__ */ ["initial"].concat(variantPriorityOrder);
9414
9440
 
9415
9441
  function isControllingVariants(props) {
9416
9442
  if (isAnimationControls(props.animate))
@@ -9669,9 +9695,7 @@
9669
9695
  }
9670
9696
  this.current = instance;
9671
9697
  visualElementStore.set(instance, this);
9672
- if (this.projection && !this.projection.instance) {
9673
- this.projection.mount(instance);
9674
- }
9698
+ this.projection?.instance || this.projection?.mount(instance);
9675
9699
  /**
9676
9700
  * Join the closest variant node above, so its variant changes
9677
9701
  * propagate here, unless inherit={false} cuts the chain.
@@ -9717,12 +9741,12 @@
9717
9741
  this.hasBeenMounted = true;
9718
9742
  }
9719
9743
  unmount() {
9720
- this.projection && this.projection.unmount();
9744
+ this.projection?.unmount();
9721
9745
  cancelFrame(this.notifyUpdate);
9722
9746
  cancelFrame(this.render);
9723
9747
  this.valueSubscriptions.forEach((remove) => remove());
9724
9748
  this.valueSubscriptions.clear();
9725
- this.removeFromVariantTree && this.removeFromVariantTree();
9749
+ this.removeFromVariantTree?.();
9726
9750
  this.parent?.removeChild(this);
9727
9751
  for (const key in this.events) {
9728
9752
  this.events[key].clear();
@@ -9738,17 +9762,14 @@
9738
9762
  }
9739
9763
  addChild(child) {
9740
9764
  this.children.add(child);
9741
- this.enteringChildren ?? (this.enteringChildren = new Set());
9742
- this.enteringChildren.add(child);
9765
+ (this.enteringChildren || (this.enteringChildren = new Set())).add(child);
9743
9766
  }
9744
9767
  removeChild(child) {
9745
9768
  this.children.delete(child);
9746
- this.enteringChildren && this.enteringChildren.delete(child);
9769
+ this.enteringChildren?.delete(child);
9747
9770
  }
9748
9771
  bindToMotionValue(key, value) {
9749
- if (this.valueSubscriptions.has(key)) {
9750
- this.valueSubscriptions.get(key)();
9751
- }
9772
+ this.valueSubscriptions.get(key)?.();
9752
9773
  if (value.accelerate &&
9753
9774
  acceleratedValues.has(key) &&
9754
9775
  this.current instanceof HTMLElement) {
@@ -9790,8 +9811,7 @@
9790
9811
  }
9791
9812
  this.valueSubscriptions.set(key, () => {
9792
9813
  removeOnChange();
9793
- if (removeSyncCheck)
9794
- removeSyncCheck();
9814
+ removeSyncCheck?.();
9795
9815
  // Defer to MotionValue.on("change") auto-stop so React 19 remounts
9796
9816
  // can resubscribe before the animation is cancelled (#3315).
9797
9817
  });
@@ -9884,10 +9904,8 @@
9884
9904
  */
9885
9905
  for (let i = 0; i < propEventHandlers.length; i++) {
9886
9906
  const key = propEventHandlers[i];
9887
- if (this.propEventSubscriptions[key]) {
9888
- this.propEventSubscriptions[key]();
9889
- delete this.propEventSubscriptions[key];
9890
- }
9907
+ this.propEventSubscriptions[key]?.();
9908
+ delete this.propEventSubscriptions[key];
9891
9909
  const listenerName = ("on" + key);
9892
9910
  const listener = props[listenerName];
9893
9911
  if (listener) {
@@ -9895,9 +9913,7 @@
9895
9913
  }
9896
9914
  }
9897
9915
  this.prevMotionValues = updateMotionValuesFromProps(this, this.scrapeMotionValuesFromProps(props, this.prevProps || {}, this), this.prevMotionValues);
9898
- if (this.handleChildMotionValue) {
9899
- this.handleChildMotionValue();
9900
- }
9916
+ this.handleChildMotionValue?.();
9901
9917
  }
9902
9918
  getProps() {
9903
9919
  return this.props;
@@ -9906,7 +9922,7 @@
9906
9922
  * Returns the variant definition with a given name.
9907
9923
  */
9908
9924
  getVariant(name) {
9909
- return this.props.variants ? this.props.variants[name] : undefined;
9925
+ return this.props.variants?.[name];
9910
9926
  }
9911
9927
  /**
9912
9928
  * Returns the defined default transition on this component.
@@ -9944,11 +9960,8 @@
9944
9960
  */
9945
9961
  removeValue(key) {
9946
9962
  this.values.delete(key);
9947
- const unsubscribe = this.valueSubscriptions.get(key);
9948
- if (unsubscribe) {
9949
- unsubscribe();
9950
- this.valueSubscriptions.delete(key);
9951
- }
9963
+ this.valueSubscriptions.get(key)?.();
9964
+ this.valueSubscriptions.delete(key);
9952
9965
  delete this.latestValues[key];
9953
9966
  this.removeValueFromRenderState(key, this.renderState);
9954
9967
  }
@@ -9959,10 +9972,10 @@
9959
9972
  return this.values.has(key);
9960
9973
  }
9961
9974
  getValue(key, defaultValue) {
9962
- if (this.props.values && this.props.values[key]) {
9963
- return this.props.values[key];
9964
- }
9965
- let value = this.values.get(key);
9975
+ let value = this.props.values?.[key];
9976
+ if (value)
9977
+ return value;
9978
+ value = this.values.get(key);
9966
9979
  if (value === undefined && defaultValue !== undefined) {
9967
9980
  value = motionValue(defaultValue ?? this.getDefaultValue?.(key), {
9968
9981
  owner: this,
@@ -10012,15 +10025,11 @@
10012
10025
  this.baseTarget[key] = value;
10013
10026
  }
10014
10027
  on(eventName, callback) {
10015
- if (!this.events[eventName]) {
10016
- this.events[eventName] = new SubscriptionManager();
10017
- }
10018
- return this.events[eventName].add(callback);
10028
+ return (this.events[eventName] ||
10029
+ (this.events[eventName] = new SubscriptionManager())).add(callback);
10019
10030
  }
10020
10031
  notify(eventName, ...args) {
10021
- if (this.events[eventName]) {
10022
- this.events[eventName].notify(...args);
10023
- }
10032
+ this.events[eventName]?.notify(...args);
10024
10033
  }
10025
10034
  scheduleRenderMicrotask() {
10026
10035
  microtask.render(this.render);
@@ -10193,7 +10202,7 @@
10193
10202
  let ancestor = element.parentElement;
10194
10203
  while (ancestor) {
10195
10204
  const node = layoutNodes.get(ancestor);
10196
- if (node && node.instance)
10205
+ if (node?.instance)
10197
10206
  return node;
10198
10207
  ancestor = ancestor.parentElement;
10199
10208
  }
@@ -10459,7 +10468,7 @@
10459
10468
  hasSurvivor &&
10460
10469
  !newMemberIds.has(layoutId)) {
10461
10470
  if (restore && restore.parent.isConnected) {
10462
- restore.parent.insertBefore(element, restore.next && restore.next.parentNode === restore.parent
10471
+ restore.parent.insertBefore(element, restore.next?.parentNode === restore.parent
10463
10472
  ? restore.next
10464
10473
  : null);
10465
10474
  node.isPresent = false;
@@ -10664,10 +10673,6 @@
10664
10673
  * Scroll positions of scrollable ancestors and window.
10665
10674
  */
10666
10675
  this.scrollPositions = new Map();
10667
- /**
10668
- * Cleanup function for scroll listeners.
10669
- */
10670
- this.removeScrollListeners = null;
10671
10676
  this.onElementScroll = (event) => {
10672
10677
  this.handleScroll(event.target);
10673
10678
  };
@@ -10695,10 +10700,10 @@
10695
10700
  this.history.push({ ...point, timestamp: time.now() });
10696
10701
  const { onStart, onMove } = this.handlers;
10697
10702
  if (!isPanStarted) {
10698
- onStart && onStart(this.lastMoveEvent, info);
10703
+ onStart?.(this.lastMoveEvent, info);
10699
10704
  this.startEvent = this.lastMoveEvent;
10700
10705
  }
10701
- onMove && onMove(this.lastMoveEvent, info);
10706
+ onMove?.(this.lastMoveEvent, info);
10702
10707
  };
10703
10708
  this.handlePointerMove = (event, info) => {
10704
10709
  /**
@@ -10723,17 +10728,15 @@
10723
10728
  // Resume animation if dragSnapToOrigin is set OR if no drag started (user just clicked)
10724
10729
  // This ensures constraint animations continue when interrupted by a click
10725
10730
  if (this.dragSnapToOrigin || !this.startEvent) {
10726
- resumeAnimation && resumeAnimation();
10731
+ resumeAnimation?.();
10727
10732
  }
10728
10733
  if (!(this.lastMoveEvent && this.lastMoveEventInfo))
10729
10734
  return;
10730
10735
  const panInfo = getPanInfo(event.type === "pointercancel"
10731
10736
  ? this.lastMoveEventInfo
10732
10737
  : transformPoint(info, this.transformPagePoint), this.history);
10733
- if (this.startEvent && onEnd) {
10734
- onEnd(event, panInfo);
10735
- }
10736
- onSessionEnd && onSessionEnd(event, panInfo);
10738
+ this.startEvent && onEnd?.(event, panInfo);
10739
+ onSessionEnd?.(event, panInfo);
10737
10740
  };
10738
10741
  // If we have more than one touch, don't start detecting this gesture
10739
10742
  if (!isPrimaryPointer(event))
@@ -10746,8 +10749,13 @@
10746
10749
  const info = extractEventInfo(event);
10747
10750
  const initialInfo = transformPoint(info, this.transformPagePoint);
10748
10751
  const { point } = initialInfo;
10749
- const { timestamp } = frameData;
10750
- this.history = [{ ...point, timestamp }];
10752
+ /**
10753
+ * Stamp the origin with time.now(), not frameData.timestamp: when
10754
+ * the frameloop is idle, frameData.timestamp is from the last frame
10755
+ * that ran, which can be seconds old, and a fast flick from a
10756
+ * standstill would then report near-zero velocity. See #1747.
10757
+ */
10758
+ this.history = [{ ...point, timestamp: time.now() }];
10751
10759
  const { onSessionStart } = handlers;
10752
10760
  onSessionStart &&
10753
10761
  onSessionStart(event, getPanInfo(initialInfo, this.history));
@@ -10771,30 +10779,19 @@
10771
10779
  const style = getComputedStyle(current);
10772
10780
  if (overflowStyles.has(style.overflowX) ||
10773
10781
  overflowStyles.has(style.overflowY)) {
10774
- this.scrollPositions.set(current, {
10775
- x: current.scrollLeft,
10776
- y: current.scrollTop,
10777
- });
10782
+ this.scrollPositions.set(current, getScroll(current));
10778
10783
  }
10779
10784
  current = current.parentElement;
10780
10785
  }
10781
10786
  // Track window scroll
10782
- this.scrollPositions.set(window, {
10783
- x: window.scrollX,
10784
- y: window.scrollY,
10785
- });
10787
+ this.scrollPositions.set(window, getScroll(window));
10788
+ this.removeScrollListeners = pipe(
10786
10789
  // Capture listener catches element scroll events as they bubble
10787
- window.addEventListener("scroll", this.onElementScroll, {
10790
+ addDomEvent(window, "scroll", this.onElementScroll, {
10788
10791
  capture: true,
10789
- });
10792
+ }),
10790
10793
  // Direct window scroll listener (window scroll doesn't bubble)
10791
- window.addEventListener("scroll", this.onWindowScroll);
10792
- this.removeScrollListeners = () => {
10793
- window.removeEventListener("scroll", this.onElementScroll, {
10794
- capture: true,
10795
- });
10796
- window.removeEventListener("scroll", this.onWindowScroll);
10797
- };
10794
+ addDomEvent(window, "scroll", this.onWindowScroll, {}));
10798
10795
  }
10799
10796
  /**
10800
10797
  * Handle scroll compensation during drag.
@@ -10807,12 +10804,7 @@
10807
10804
  if (!initial)
10808
10805
  return;
10809
10806
  const isWindow = target === window;
10810
- const current = isWindow
10811
- ? { x: window.scrollX, y: window.scrollY }
10812
- : {
10813
- x: target.scrollLeft,
10814
- y: target.scrollTop,
10815
- };
10807
+ const current = getScroll(target);
10816
10808
  const delta = { x: current.x - initial.x, y: current.y - initial.y };
10817
10809
  if (delta.x === 0 && delta.y === 0)
10818
10810
  return;
@@ -10837,12 +10829,18 @@
10837
10829
  this.handlers = handlers;
10838
10830
  }
10839
10831
  end() {
10840
- this.removeListeners && this.removeListeners();
10841
- this.removeScrollListeners && this.removeScrollListeners();
10832
+ this.removeListeners?.();
10833
+ this.removeScrollListeners?.();
10842
10834
  this.scrollPositions.clear();
10843
10835
  cancelFrame(this.updatePoint);
10844
10836
  }
10845
10837
  }
10838
+ const getScroll = (target) => target === window
10839
+ ? { x: window.scrollX, y: window.scrollY }
10840
+ : {
10841
+ x: target.scrollLeft,
10842
+ y: target.scrollTop,
10843
+ };
10846
10844
  function transformPoint(info, transformPagePoint) {
10847
10845
  return transformPagePoint ? { point: transformPagePoint(info.point) } : info;
10848
10846
  }
@@ -11099,6 +11097,25 @@
11099
11097
  : dragElastic[label] || 0;
11100
11098
  }
11101
11099
 
11100
+ /**
11101
+ * Re-read the scroll of the projection's ancestors, and return that of the
11102
+ * `layoutScroll` ones as a key that changes when any of them scroll.
11103
+ *
11104
+ * Clear the cached scroll first so `updateScroll` bypasses its
11105
+ * per-animationId cache and re-reads the live value.
11106
+ */
11107
+ function measureScroll(projection) {
11108
+ let key = "";
11109
+ for (const node of projection.path) {
11110
+ node.scroll = undefined;
11111
+ node.updateScroll();
11112
+ const { scroll } = node;
11113
+ if (node !== projection.root && scroll) {
11114
+ key += scroll.offset.x + "," + scroll.offset.y + ",";
11115
+ }
11116
+ }
11117
+ return key;
11118
+ }
11102
11119
  const elementDragControls = new WeakMap();
11103
11120
  class VisualElementDragControls {
11104
11121
  constructor(visualElement, readProps = () => visualElement.getProps()) {
@@ -11130,8 +11147,7 @@
11130
11147
  /**
11131
11148
  * Don't start dragging if this component is exiting
11132
11149
  */
11133
- const { presenceContext } = this.visualElement;
11134
- if (presenceContext && presenceContext.isPresent === false)
11150
+ if (this.visualElement.presenceContext?.isPresent === false)
11135
11151
  return;
11136
11152
  const onSessionStart = (event) => {
11137
11153
  if (snapToCursor)
@@ -11142,8 +11158,7 @@
11142
11158
  // Attempt to grab the global drag gesture lock - maybe make this part of PanSession
11143
11159
  const { drag, dragPropagation, onDragStart } = this.getProps();
11144
11160
  if (drag && !dragPropagation) {
11145
- if (this.openDragLock)
11146
- this.openDragLock();
11161
+ this.openDragLock?.();
11147
11162
  this.openDragLock = setDragLock(drag);
11148
11163
  // If we don 't have the lock, don't start dragging
11149
11164
  if (!this.openDragLock)
@@ -11154,9 +11169,10 @@
11154
11169
  this.isDragging = true;
11155
11170
  this.currentDirection = null;
11156
11171
  this.resolveConstraints();
11157
- if (this.visualElement.projection) {
11158
- this.visualElement.projection.isAnimationBlocked = true;
11159
- this.visualElement.projection.target = undefined;
11172
+ const { projection } = this.visualElement;
11173
+ if (projection) {
11174
+ projection.isAnimationBlocked = true;
11175
+ projection.target = undefined;
11160
11176
  }
11161
11177
  /**
11162
11178
  * Record gesture origin and pointer offset
@@ -11178,13 +11194,11 @@
11178
11194
  current = parseFloat(current);
11179
11195
  }
11180
11196
  else if (percent.test(current)) {
11181
- const { projection } = this.visualElement;
11182
- if (projection && projection.layout) {
11183
- const measuredAxis = projection.layout.layoutBox[axis];
11184
- if (measuredAxis) {
11185
- const length = calcLength(measuredAxis);
11186
- current = length * (parseFloat(current) / 100);
11187
- }
11197
+ const measuredAxis = projection?.layout?.layoutBox[axis];
11198
+ if (measuredAxis) {
11199
+ current =
11200
+ calcLength(measuredAxis) *
11201
+ (parseFloat(current) / 100);
11188
11202
  }
11189
11203
  }
11190
11204
  this.originPoint[axis] = current;
@@ -11194,8 +11208,7 @@
11194
11208
  frame.update(() => onDragStart(event, info), false, true);
11195
11209
  }
11196
11210
  addValueToWillChange(this.visualElement, "transform");
11197
- const { animationState } = this.visualElement;
11198
- animationState && animationState.setActive("whileDrag", true);
11211
+ this.visualElement.animationState?.setActive("whileDrag", true);
11199
11212
  };
11200
11213
  const onMove = (event, info) => {
11201
11214
  this.latestPointerEvent = event;
@@ -11218,8 +11231,8 @@
11218
11231
  onDirectionLock && onDirectionLock(this.currentDirection);
11219
11232
  }
11220
11233
  // Update each point with the latest position
11221
- this.updateAxis("x", info.point, offset);
11222
- this.updateAxis("y", info.point, offset);
11234
+ this.updateAxis("x", offset);
11235
+ this.updateAxis("y", offset);
11223
11236
  /**
11224
11237
  * Ideally we would leave the renderer to fire naturally at the end of
11225
11238
  * this frame but if the element is about to change layout as the result
@@ -11263,7 +11276,11 @@
11263
11276
  element: this.visualElement.current,
11264
11277
  });
11265
11278
  }
11266
- stop(event, panInfo) {
11279
+ /**
11280
+ * @param dragAxes - The axes to animate on release, if not the `drag`
11281
+ * prop's, e.g. when drag was disabled mid-gesture.
11282
+ */
11283
+ stop(event, panInfo, dragAxes) {
11267
11284
  const finalEvent = event || this.latestPointerEvent;
11268
11285
  const finalPanInfo = panInfo || this.latestPanInfo;
11269
11286
  const isDragging = this.isDragging;
@@ -11271,7 +11288,7 @@
11271
11288
  if (!isDragging || !finalPanInfo || !finalEvent)
11272
11289
  return;
11273
11290
  const { velocity } = finalPanInfo;
11274
- this.startAnimation(velocity);
11291
+ this.startAnimation(velocity, dragAxes);
11275
11292
  const { onDragEnd } = this.getProps();
11276
11293
  if (onDragEnd) {
11277
11294
  frame.postRender(() => onDragEnd(finalEvent, finalPanInfo));
@@ -11284,12 +11301,11 @@
11284
11301
  projection.isAnimationBlocked = false;
11285
11302
  }
11286
11303
  this.endPanSession();
11287
- const { dragPropagation } = this.getProps();
11288
- if (!dragPropagation && this.openDragLock) {
11304
+ if (!this.getProps().dragPropagation && this.openDragLock) {
11289
11305
  this.openDragLock();
11290
11306
  this.openDragLock = null;
11291
11307
  }
11292
- animationState && animationState.setActive("whileDrag", false);
11308
+ animationState?.setActive("whileDrag", false);
11293
11309
  }
11294
11310
  /**
11295
11311
  * Clean up the pan session without modifying other drag state.
@@ -11297,31 +11313,42 @@
11297
11313
  * without affecting projection animations or drag locks.
11298
11314
  */
11299
11315
  endPanSession() {
11300
- this.panSession && this.panSession.end();
11316
+ this.panSession?.end();
11301
11317
  this.panSession = undefined;
11302
11318
  }
11303
- updateAxis(axis, _point, offset) {
11304
- const { drag } = this.getProps();
11319
+ updateAxis(axis, offset) {
11320
+ /**
11321
+ * Read drag per axis: a callback fired while updating the x axis
11322
+ * can synchronously re-render with new props.
11323
+ */
11324
+ const { drag } = this.readProps();
11305
11325
  // If we're not dragging this axis, do an early return.
11306
11326
  if (!offset || !shouldDrag(axis, drag, this.currentDirection))
11307
11327
  return;
11308
11328
  const axisValue = this.getAxisMotionValue(axis);
11309
11329
  let next = this.originPoint[axis] + offset[axis];
11310
11330
  // Apply constraints
11311
- if (this.constraints && this.constraints[axis]) {
11312
- next = applyConstraints(next, this.constraints[axis], this.elastic[axis]);
11331
+ const constraints = this.constraints && this.constraints[axis];
11332
+ if (constraints) {
11333
+ next = applyConstraints(next, constraints, this.elastic[axis]);
11313
11334
  }
11314
11335
  axisValue.set(next);
11315
11336
  }
11316
11337
  resolveConstraints() {
11317
11338
  const { dragConstraints, dragElastic } = this.getProps();
11318
- const layout = this.visualElement.projection &&
11319
- !this.visualElement.projection.layout
11320
- ? this.visualElement.projection.measure(false)
11321
- : this.visualElement.projection?.layout;
11339
+ const { projection } = this.visualElement;
11340
+ const layout = projection && (projection.layout || projection.measure(false));
11322
11341
  const prevConstraints = this.constraints;
11323
11342
  if (isElementConstraints(dragConstraints)) {
11324
- if (!this.constraints) {
11343
+ /**
11344
+ * Ref constraints are measured with the layout. Scrolling a
11345
+ * `layoutScroll` ancestor since can move the element relative
11346
+ * to them without a layout measurement, so measure them again
11347
+ * (#2024). Otherwise keep them: they and the layout can both
11348
+ * have moved on the page since, e.g. when content above grows.
11349
+ */
11350
+ if (!this.constraints ||
11351
+ this.scroll !== measureScroll(projection)) {
11325
11352
  this.constraints = this.resolveRefConstraints();
11326
11353
  }
11327
11354
  }
@@ -11364,21 +11391,20 @@
11364
11391
  if (!projection || !projection.layout)
11365
11392
  return false;
11366
11393
  /**
11367
- * Refresh the root scroll offset so the constraint's viewport box
11368
- * translates to correct page coordinates. The scroll captured at
11369
- * drag mount can be stale if the document was scrolled afterwards —
11370
- * e.g. via the browser restoring scroll on refresh, or an ancestor
11371
- * layout effect running after this element's mount (#2829).
11372
- *
11373
- * Clear the cached scroll first so `updateScroll` bypasses its
11374
- * per-animationId cache and re-reads the live value.
11394
+ * Refresh the scroll offsets of the root and `layoutScroll`
11395
+ * ancestors so the constraint's viewport box translates to the
11396
+ * layout's coordinates. The scroll captured when the layout was
11397
+ * measured can be stale if they've scrolled since — e.g. via the
11398
+ * browser restoring scroll on refresh, or an ancestor layout effect
11399
+ * running after this element's mount (#2829).
11375
11400
  */
11376
- if (projection.root) {
11377
- projection.root.scroll = undefined;
11378
- projection.root.updateScroll();
11379
- }
11401
+ this.scroll = measureScroll(projection);
11380
11402
  const constraintsBox = measurePageBox(constraintsElement, projection.root, this.visualElement.getTransformPagePoint());
11381
- let measuredConstraints = calcViewportConstraints(projection.layout.layoutBox, constraintsBox);
11403
+ /**
11404
+ * layoutBox removes the scroll of `layoutScroll` ancestors, so
11405
+ * remove it from the constraints box too.
11406
+ */
11407
+ let measuredConstraints = calcViewportConstraints(projection.layout.layoutBox, projection.removeElementScroll(constraintsBox));
11382
11408
  /**
11383
11409
  * If there's an onMeasureDragConstraints listener we call it and
11384
11410
  * if different constraints are returned, set constraints to that
@@ -11392,14 +11418,14 @@
11392
11418
  }
11393
11419
  return measuredConstraints;
11394
11420
  }
11395
- startAnimation(velocity) {
11421
+ startAnimation(velocity, dragAxes) {
11396
11422
  const { drag, dragMomentum, dragElastic, dragTransition, dragSnapToOrigin, onDragTransitionEnd, } = this.getProps();
11397
11423
  const constraints = this.constraints || {};
11398
11424
  const momentumAnimations = eachAxis((axis) => {
11399
- if (!shouldDrag(axis, drag, this.currentDirection)) {
11425
+ if (!shouldDrag(axis, dragAxes || drag, this.currentDirection)) {
11400
11426
  return;
11401
11427
  }
11402
- let transition = (constraints && constraints[axis]) || {};
11428
+ let transition = constraints[axis] || {};
11403
11429
  if (dragSnapToOrigin === true ||
11404
11430
  dragSnapToOrigin === axis)
11405
11431
  transition = { min: 0, max: 0 };
@@ -11505,7 +11531,7 @@
11505
11531
  this.visualElement.current.style.transform = transformTemplate
11506
11532
  ? transformTemplate({}, "")
11507
11533
  : "none";
11508
- projection.root && projection.root.updateScroll();
11534
+ projection.root?.updateScroll();
11509
11535
  projection.updateLayout();
11510
11536
  /**
11511
11537
  * Reset constraints so resolveConstraints() will recalculate them
@@ -11582,8 +11608,8 @@
11582
11608
  };
11583
11609
  const { projection } = this.visualElement;
11584
11610
  const stopMeasureLayoutListener = projection.addEventListener("measure", measureDragConstraints);
11585
- if (projection && !projection.layout) {
11586
- projection.root && projection.root.updateScroll();
11611
+ if (!projection.layout) {
11612
+ projection.root?.updateScroll();
11587
11613
  projection.updateLayout();
11588
11614
  }
11589
11615
  frame.read(measureDragConstraints);
@@ -11599,9 +11625,15 @@
11599
11625
  const stopLayoutUpdateListener = projection.addEventListener("didUpdate", (({ delta, hasLayoutChanged }) => {
11600
11626
  if (this.isDragging && hasLayoutChanged) {
11601
11627
  eachAxis((axis) => {
11602
- const motionValue = this.getAxisMotionValue(axis);
11603
- if (!motionValue)
11628
+ /**
11629
+ * Only compensate axes this gesture drags. A
11630
+ * non-drag axis follows its layout instead, as
11631
+ * nothing would ever move it back (e.g. snapping
11632
+ * to origin only animates drag axes).
11633
+ */
11634
+ if (!shouldDrag(axis, this.getProps().drag, this.currentDirection))
11604
11635
  return;
11636
+ const motionValue = this.getAxisMotionValue(axis);
11605
11637
  this.originPoint[axis] += delta[axis].translate;
11606
11638
  motionValue.set(motionValue.get() + delta[axis].translate);
11607
11639
  });
@@ -11612,22 +11644,14 @@
11612
11644
  stopResizeListener();
11613
11645
  stopPointerListener();
11614
11646
  stopMeasureLayoutListener();
11615
- stopLayoutUpdateListener && stopLayoutUpdateListener();
11616
- stopResizeObservers && stopResizeObservers();
11647
+ stopLayoutUpdateListener();
11648
+ stopResizeObservers?.();
11617
11649
  };
11618
11650
  }
11619
11651
  getProps() {
11620
11652
  const props = this.readProps();
11621
- const { drag = false, dragDirectionLock = false, dragPropagation = false, dragConstraints = false, dragElastic = defaultElastic, dragMomentum = true, } = props;
11622
- return {
11623
- ...props,
11624
- drag,
11625
- dragDirectionLock,
11626
- dragPropagation,
11627
- dragConstraints,
11628
- dragElastic,
11629
- dragMomentum,
11630
- };
11653
+ const { dragElastic = defaultElastic, dragMomentum = true } = props;
11654
+ return { ...props, dragElastic, dragMomentum };
11631
11655
  }
11632
11656
  }
11633
11657
  /**
@@ -13962,6 +13986,12 @@
13962
13986
  }
13963
13987
 
13964
13988
  const reversePriorityOrder = [...variantPriorityOrder].reverse();
13989
+ /**
13990
+ * The transition a value uses when it leaves a state (#1725): the state's
13991
+ * own transition when `out: true`, or the `out` transition itself.
13992
+ * Value-specific keys are resolved by animateTarget.
13993
+ */
13994
+ const getOutTransition = (transition) => transition?.out === true ? transition : transition?.out;
13965
13995
  /**
13966
13996
  * Diffs the element's animation props (one layer per AnimationType, highest
13967
13997
  * priority wins each value) against the previous call and starts animations
@@ -13983,13 +14013,18 @@
13983
14013
  let wasReset = false;
13984
14014
  function animateChanges(changedActiveType) {
13985
14015
  const { props, parent, manuallyAnimateOnMount } = visualElement;
14016
+ const { initial } = props;
13986
14017
  const isMounting = isInitialRender || wasReset;
13987
14018
  const animations = [];
14019
+ const resolveInitial = (definition = initial) => (typeof definition !== "boolean" &&
14020
+ resolveVariant(visualElement, Array.isArray(definition) ? definition[0] : definition, visualElement.presenceContext?.custom)) ||
14021
+ undefined;
13988
14022
  /**
13989
- * Values removed from a type. A lower-priority type that defines
13990
- * them animates them back, otherwise they fall back to a base target.
14023
+ * Values removed from a type, mapped to the out transition of the
14024
+ * state they're leaving. A lower-priority type that defines them
14025
+ * animates them back, otherwise they fall back to a base target.
13991
14026
  */
13992
- const removedKeys = new Set();
14027
+ const removedKeys = new Map();
13993
14028
  /**
13994
14029
  * Values claimed by active, higher-priority types.
13995
14030
  */
@@ -14007,6 +14042,13 @@
14007
14042
  do
14008
14043
  source = source.props.inherit !== false && source.parent;
14009
14044
  while (source && !source.isControllingVariants);
14045
+ /**
14046
+ * Mounting leaves initial, so values use its out transition. Like the
14047
+ * other types, variant children inherit initial's label.
14048
+ */
14049
+ const initialOut = isMounting &&
14050
+ getOutTransition(resolveInitial(initial ?? (source && source.props.initial))
14051
+ ?.transition);
14010
14052
  reversePriorityOrder.forEach((type, i) => {
14011
14053
  const typeState = state[type];
14012
14054
  const ownProp = props[type];
@@ -14018,8 +14060,6 @@
14018
14060
  : undefined;
14019
14061
  const propIsVariant = isVariantLabel(prop);
14020
14062
  const activeDelta = type === changedActiveType ? typeState.isActive : null;
14021
- if (activeDelta === false)
14022
- removedVariantIndex = i;
14023
14063
  /**
14024
14064
  * Inherited labels are animated by the parent through
14025
14065
  * variantChildren, unless this element mounted after its parent.
@@ -14034,6 +14074,13 @@
14034
14074
  typeof prop === "boolean") {
14035
14075
  return;
14036
14076
  }
14077
+ /**
14078
+ * Set after the skip above, so deactivating a type that has nothing
14079
+ * to animate (e.g. whileTap with no prop) doesn't re-run the variants
14080
+ * below it.
14081
+ */
14082
+ if (activeDelta === false)
14083
+ removedVariantIndex = i;
14037
14084
  /**
14038
14085
  * Don't re-resolve a running exit: a changed custom would start
14039
14086
  * new value animations that stop the originals, leaving the exit
@@ -14053,6 +14100,7 @@
14053
14100
  let handledRemovedValues = false;
14054
14101
  const definitionList = Array.isArray(prop) ? prop : [prop];
14055
14102
  let resolvedValues = {};
14103
+ let out;
14056
14104
  if (activeDelta !== false) {
14057
14105
  for (const definition of definitionList) {
14058
14106
  const { transition, transitionEnd, ...target } = resolveVariant(visualElement, definition, getTypeCustom(visualElement, type)) || {};
@@ -14061,6 +14109,11 @@
14061
14109
  ...target,
14062
14110
  ...transitionEnd,
14063
14111
  };
14112
+ /**
14113
+ * One out transition per type. With a list of labels,
14114
+ * the last one that sets out applies to all its values.
14115
+ */
14116
+ out = getOutTransition(transition) || out;
14064
14117
  }
14065
14118
  }
14066
14119
  const { prevResolvedValues } = typeState;
@@ -14076,13 +14129,18 @@
14076
14129
  const valueHasChanged = Array.isArray(next) && Array.isArray(prev)
14077
14130
  ? !shallowCompare(next, prev) || variantDidChange
14078
14131
  : next !== prev;
14079
- if (valueHasChanged &&
14080
- (next === undefined || next === null)) {
14081
- removedKeys.add(key);
14132
+ let outTransition;
14133
+ if (valueHasChanged && (next === undefined || next === null)) {
14134
+ /**
14135
+ * The highest-priority state a value leaves wins.
14136
+ */
14137
+ removedKeys.has(key) ||
14138
+ removedKeys.set(key, typeState.prevOut);
14082
14139
  }
14083
14140
  else if (valueHasChanged ||
14084
14141
  (next !== undefined && removedKeys.has(key))) {
14085
14142
  shouldAnimateType = true;
14143
+ outTransition = removedKeys.get(key) || initialOut;
14086
14144
  if (removedKeys.delete(key))
14087
14145
  handledRemovedValues = true;
14088
14146
  typeState.needsAnimating[key] = true;
@@ -14093,9 +14151,11 @@
14093
14151
  else {
14094
14152
  typeState.protectedKeys[key] = true;
14095
14153
  }
14154
+ typeState.out[key] = outTransition;
14096
14155
  }
14097
14156
  typeState.prevProp = prop;
14098
14157
  typeState.prevResolvedValues = resolvedValues;
14158
+ typeState.prevOut = out;
14099
14159
  if (typeState.isActive) {
14100
14160
  encounteredKeys = { ...encounteredKeys, ...resolvedValues };
14101
14161
  }
@@ -14126,14 +14186,17 @@
14126
14186
  * longer defines a value it once did, it stays where it is.
14127
14187
  */
14128
14188
  if (removedKeys.size) {
14129
- const { initial } = props;
14130
14189
  const fallbackAnimation = {};
14131
- const resolvedInitial = typeof initial !== "boolean" &&
14132
- resolveVariant(visualElement, Array.isArray(initial) ? initial[0] : initial, visualElement.presenceContext?.custom);
14133
- if (resolvedInitial && resolvedInitial.transition) {
14134
- fallbackAnimation.transition = resolvedInitial.transition;
14135
- }
14136
- removedKeys.forEach((key) => {
14190
+ const resolvedInitial = resolveInitial();
14191
+ let fallbackTransition = resolvedInitial?.transition;
14192
+ removedKeys.forEach((outTransition, key) => {
14193
+ if (outTransition) {
14194
+ fallbackTransition = {
14195
+ ...(fallbackTransition ||
14196
+ visualElement.getDefaultTransition()),
14197
+ [key]: outTransition,
14198
+ };
14199
+ }
14137
14200
  const motionValue = visualElement.getValue(key);
14138
14201
  if (motionValue)
14139
14202
  motionValue.liveStyle = true;
@@ -14164,6 +14227,9 @@
14164
14227
  visualElement.baseTarget[key] = motionValue?.get();
14165
14228
  }
14166
14229
  });
14230
+ if (fallbackTransition) {
14231
+ fallbackAnimation.transition = fallbackTransition;
14232
+ }
14167
14233
  animations.push({ animation: fallbackAnimation });
14168
14234
  }
14169
14235
  const blockAnimation = (isMounting && visualElement.blockInitialAnimation) ||
@@ -14223,6 +14289,7 @@
14223
14289
  protectedKeys: {},
14224
14290
  needsAnimating: {},
14225
14291
  prevResolvedValues: {},
14292
+ out: {},
14226
14293
  };
14227
14294
  }
14228
14295
  return state;
@@ -14341,6 +14408,18 @@
14341
14408
  return duration * (repeat + 1) + repeatDelay * repeat;
14342
14409
  }
14343
14410
 
14411
+ /**
14412
+ * The time of a label, or a label with an offset like "label+1" or
14413
+ * "label-0.5". An exact label name wins over an offset.
14414
+ */
14415
+ function calcLabelTime(position, labels) {
14416
+ if (labels.has(position))
14417
+ return labels.get(position);
14418
+ const [, name, offset] = position.match(/^(.+?)([+-][\d.]+)$/) || [];
14419
+ return labels.has(name)
14420
+ ? Math.max(0, labels.get(name) + parseFloat(offset))
14421
+ : undefined;
14422
+ }
14344
14423
  /**
14345
14424
  * Given a absolute or relative time definition and current/prev time state of the sequence,
14346
14425
  * calculate an absolute time for the next keyframes.
@@ -14359,7 +14438,7 @@
14359
14438
  return Math.max(0, prev + parseFloat(next.slice(1)));
14360
14439
  }
14361
14440
  else {
14362
- return labels.get(next) ?? current;
14441
+ return calcLabelTime(next, labels) ?? current;
14363
14442
  }
14364
14443
  }
14365
14444
 
@@ -14420,7 +14499,7 @@
14420
14499
 
14421
14500
  const defaultSegmentEasing = "easeInOut";
14422
14501
  const MAX_REPEAT = 20;
14423
- function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) {
14502
+ function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators, info) {
14424
14503
  const defaultDuration = defaultTransition.duration || 0.3;
14425
14504
  const animationDefinitions = new Map();
14426
14505
  const sequences = new Map();
@@ -14433,196 +14512,233 @@
14433
14512
  * Build the timeline by mapping over the sequence array and converting
14434
14513
  * the definitions into keyframes and offsets with absolute time values.
14435
14514
  * These will later get converted into relative offsets in a second pass.
14515
+ *
14516
+ * Nested sequences are resolved recursively. `base` is the absolute
14517
+ * start of the sequence being resolved, so numeric `at` values inside
14518
+ * a nested sequence are relative to where it starts. Returns the time
14519
+ * the sequence ends.
14436
14520
  */
14437
- for (let i = 0; i < sequence.length; i++) {
14438
- const segment = sequence[i];
14439
- /**
14440
- * If this is a timeline label, mark it and skip the rest of this iteration.
14441
- */
14442
- if (typeof segment === "string") {
14443
- timeLabels.set(segment, currentTime);
14444
- continue;
14445
- }
14446
- else if (!Array.isArray(segment)) {
14447
- timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels));
14448
- continue;
14449
- }
14450
- let [subject, keyframes, transition = {}] = segment;
14451
- /**
14452
- * If a relative or absolute time value has been specified we need to resolve
14453
- * it in relation to the currentTime.
14454
- */
14455
- if (transition.at !== undefined) {
14456
- currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels);
14457
- }
14458
- /**
14459
- * Keep track of the maximum duration in this definition. This will be
14460
- * applied to currentTime once the definition has been parsed.
14461
- */
14462
- let maxDuration = 0;
14463
- const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
14464
- const valueKeyframesAsList = keyframesAsList(valueKeyframes);
14465
- const { delay = 0, times = defaultOffset$1(valueKeyframesAsList), type = defaultTransition.type || "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
14466
- let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
14467
- /**
14468
- * Resolve stagger() if defined.
14469
- */
14470
- const calculatedDelay = typeof delay === "function"
14471
- ? delay(elementIndex, numSubjects)
14472
- : delay;
14521
+ const resolveSequence = (sequence, base) => {
14522
+ let sequenceEnd = base;
14523
+ const resolveTime = (at) => typeof at === "number"
14524
+ ? base + at
14525
+ : calcNextTime(currentTime, at, prevTime, timeLabels);
14526
+ for (let i = 0; i < sequence.length; i++) {
14527
+ const segment = sequence[i];
14473
14528
  /**
14474
- * If this animation should and can use a spring, generate a spring easing function.
14529
+ * If this is a timeline label, mark it and skip the rest of this iteration.
14475
14530
  */
14476
- const numKeyframes = valueKeyframesAsList.length;
14477
- const createGenerator = isGenerator(type)
14478
- ? type
14479
- : generators?.[type || "keyframes"];
14480
- if (numKeyframes <= 2 && createGenerator) {
14481
- /**
14482
- * As we're creating an easing function from a spring,
14483
- * ideally we want to generate it using the real distance
14484
- * between the two keyframes. However this isn't always
14485
- * possible - in these situations we use 0-100.
14486
- */
14487
- let absoluteDelta = 100;
14488
- if (numKeyframes === 2 &&
14489
- isNumberKeyframesArray(valueKeyframesAsList)) {
14490
- const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
14491
- absoluteDelta = Math.abs(delta);
14531
+ if (typeof segment === "string") {
14532
+ timeLabels.set(segment, currentTime);
14533
+ continue;
14534
+ }
14535
+ else if (!Array.isArray(segment)) {
14536
+ const start = segment.at === undefined
14537
+ ? currentTime
14538
+ : resolveTime(segment.at);
14539
+ if ("sequence" in segment) {
14540
+ /**
14541
+ * Resolve the nested sequence from its start. Afterwards
14542
+ * "<" points at its start and the next segment follows
14543
+ * its end, as if it were a single segment.
14544
+ */
14545
+ prevTime = currentTime = start;
14546
+ currentTime = resolveSequence(segment.sequence, start);
14547
+ prevTime = start;
14548
+ sequenceEnd = Math.max(sequenceEnd, currentTime);
14492
14549
  }
14493
- const springTransition = {
14494
- ...defaultTransition,
14495
- ...remainingTransition,
14496
- };
14497
- if (duration !== undefined) {
14498
- springTransition.duration = secondsToMilliseconds(duration);
14550
+ else {
14551
+ timeLabels.set(segment.name, start);
14499
14552
  }
14500
- const springEasing = createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
14501
- ease = springEasing.ease;
14502
- duration = springEasing.duration;
14553
+ continue;
14503
14554
  }
14504
- duration ?? (duration = defaultDuration);
14505
- const startTime = currentTime + calculatedDelay;
14555
+ let [subject, keyframes, transition = {}] = segment;
14506
14556
  /**
14507
- * If there's only one time offset of 0, fill in a second with length 1
14557
+ * If a relative or absolute time value has been specified we need to resolve
14558
+ * it in relation to the currentTime.
14508
14559
  */
14509
- if (times.length === 1 && times[0] === 0) {
14510
- times[1] = 1;
14560
+ if (transition.at !== undefined) {
14561
+ currentTime = resolveTime(transition.at);
14511
14562
  }
14512
14563
  /**
14513
- * Fill out if offset if fewer offsets than keyframes
14564
+ * Keep track of the maximum duration in this definition. This will be
14565
+ * applied to currentTime once the definition has been parsed.
14514
14566
  */
14515
- const remainder = times.length - valueKeyframesAsList.length;
14516
- remainder > 0 && fillOffset(times, remainder);
14517
- /**
14518
- * If only one value has been set, ie [1], push a null to the start of
14519
- * the keyframe array. This will let us mark a keyframe at this point
14520
- * that will later be hydrated with the previous value.
14521
- */
14522
- valueKeyframesAsList.length === 1 &&
14523
- valueKeyframesAsList.unshift(null);
14524
- /**
14525
- * Segments can't express `repeat: Infinity` or very large
14526
- * counts — they'd leave dead time after the segment or
14527
- * explode the keyframe array. Ignore with a warning.
14528
- */
14529
- if (repeat) {
14530
- 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.`);
14531
- }
14532
- if (repeat && repeat < MAX_REPEAT) {
14567
+ let maxDuration = 0;
14568
+ const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
14569
+ const valueKeyframesAsList = keyframesAsList(valueKeyframes);
14570
+ const { delay = 0, times = defaultOffset$1(valueKeyframesAsList), type = defaultTransition.type || "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
14571
+ let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
14533
14572
  /**
14534
- * Express repeatDelay in units of a single iteration's duration
14535
- * so it can be added to the per-iteration time offsets below
14536
- * before they're normalized to 0-1.
14573
+ * Resolve stagger() if defined.
14537
14574
  */
14538
- const repeatDelayUnits = duration > 0 ? repeatDelay / duration : 0;
14539
- duration = calculateRepeatDuration(duration, repeat, repeatDelay);
14540
- const originalKeyframes = [...valueKeyframesAsList];
14541
- const originalTimes = [...times];
14542
- ease = Array.isArray(ease) ? [...ease] : [ease];
14543
- const originalEase = [...ease];
14575
+ const calculatedDelay = typeof delay === "function"
14576
+ ? delay(elementIndex, numSubjects)
14577
+ : delay;
14544
14578
  /**
14545
- * For reverse/mirror, alternate iterations play the segment
14546
- * backwards. mirror matches JSAnimation's mirroredGenerator:
14547
- * reversed keyframes, easings unchanged. reverse matches
14548
- * JSAnimation's iterationProgress = 1 - p: reversed
14549
- * keyframes, easing array reversed AND each function easing
14550
- * mapped through reverseEasing (string easings unchanged —
14551
- * they're resolved later by the keyframes engine).
14579
+ * If this animation should and can use a spring, generate a spring easing function.
14552
14580
  */
14553
- const isFlipping = repeatType === "reverse" || repeatType === "mirror";
14554
- let flippedKeyframes = originalKeyframes;
14555
- let flippedEases = originalEase;
14556
- if (isFlipping) {
14557
- flippedKeyframes = [...originalKeyframes].reverse();
14558
- if (repeatType === "reverse") {
14559
- flippedEases = [...originalEase]
14560
- .reverse()
14561
- .map((e) => typeof e === "function"
14562
- ? reverseEasing(e)
14563
- : e);
14581
+ const numKeyframes = valueKeyframesAsList.length;
14582
+ const createGenerator = isGenerator(type)
14583
+ ? type
14584
+ : generators?.[type || "keyframes"];
14585
+ if (numKeyframes <= 2 && createGenerator) {
14586
+ /**
14587
+ * As we're creating an easing function from a spring,
14588
+ * ideally we want to generate it using the real distance
14589
+ * between the two keyframes. However this isn't always
14590
+ * possible - in these situations we use 0-100.
14591
+ */
14592
+ let absoluteDelta = 100;
14593
+ if (numKeyframes === 2 &&
14594
+ isNumberKeyframesArray(valueKeyframesAsList)) {
14595
+ const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
14596
+ absoluteDelta = Math.abs(delta);
14597
+ }
14598
+ const springTransition = {
14599
+ ...defaultTransition,
14600
+ ...remainingTransition,
14601
+ };
14602
+ if (duration !== undefined) {
14603
+ springTransition.duration =
14604
+ secondsToMilliseconds(duration);
14564
14605
  }
14606
+ const springEasing = createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
14607
+ ease = springEasing.ease;
14608
+ duration = springEasing.duration;
14565
14609
  }
14566
- for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
14567
- const isFlipped = isFlipping && repeatIndex % 2 === 0;
14568
- const iterKeyframes = isFlipped
14569
- ? flippedKeyframes
14570
- : originalKeyframes;
14571
- const iterEase = isFlipped ? flippedEases : originalEase;
14572
- const iterStartOffset = (repeatIndex + 1) * (1 + repeatDelayUnits);
14610
+ duration ?? (duration = defaultDuration);
14611
+ const startTime = currentTime + calculatedDelay;
14612
+ /**
14613
+ * If there's only one time offset of 0, fill in a second with length 1
14614
+ */
14615
+ if (times.length === 1 && times[0] === 0) {
14616
+ times[1] = 1;
14617
+ }
14618
+ /**
14619
+ * Fill out if offset if fewer offsets than keyframes
14620
+ */
14621
+ const remainder = times.length - valueKeyframesAsList.length;
14622
+ remainder > 0 && fillOffset(times, remainder);
14623
+ /**
14624
+ * If only one value has been set, ie [1], push a null to the start of
14625
+ * the keyframe array. This will let us mark a keyframe at this point
14626
+ * that will later be hydrated with the previous value.
14627
+ */
14628
+ valueKeyframesAsList.length === 1 &&
14629
+ valueKeyframesAsList.unshift(null);
14630
+ /**
14631
+ * Segments can't express `repeat: Infinity` or very large
14632
+ * counts — they'd leave dead time after the segment or
14633
+ * explode the keyframe array. Ignore with a warning.
14634
+ */
14635
+ if (repeat) {
14636
+ typeof process !== "undefined" &&
14637
+ "development" !== "production" &&
14638
+ 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.`);
14639
+ }
14640
+ if (repeat && repeat < MAX_REPEAT) {
14641
+ /**
14642
+ * Express repeatDelay in units of a single iteration's duration
14643
+ * so it can be added to the per-iteration time offsets below
14644
+ * before they're normalized to 0-1.
14645
+ */
14646
+ const repeatDelayUnits = duration > 0 ? repeatDelay / duration : 0;
14647
+ duration = calculateRepeatDuration(duration, repeat, repeatDelay);
14648
+ const originalKeyframes = [...valueKeyframesAsList];
14649
+ const originalTimes = [...times];
14650
+ ease = Array.isArray(ease) ? [...ease] : [ease];
14651
+ const originalEase = [...ease];
14573
14652
  /**
14574
- * If repeatDelay is set, hold the previous iteration's
14575
- * final value through the delay by inserting a keyframe
14576
- * at the moment the next iteration begins.
14653
+ * For reverse/mirror, alternate iterations play the segment
14654
+ * backwards. mirror matches JSAnimation's mirroredGenerator:
14655
+ * reversed keyframes, easings unchanged. reverse matches
14656
+ * JSAnimation's iterationProgress = 1 - p: reversed
14657
+ * keyframes, easing array reversed AND each function easing
14658
+ * mapped through reverseEasing (string easings unchanged —
14659
+ * they're resolved later by the keyframes engine).
14577
14660
  */
14578
- if (repeatDelayUnits > 0) {
14579
- valueKeyframesAsList.push(valueKeyframesAsList[valueKeyframesAsList.length - 1]);
14580
- times.push(iterStartOffset);
14581
- ease.push("linear");
14661
+ const isFlipping = repeatType === "reverse" || repeatType === "mirror";
14662
+ let flippedKeyframes = originalKeyframes;
14663
+ let flippedEases = originalEase;
14664
+ if (isFlipping) {
14665
+ flippedKeyframes = [...originalKeyframes].reverse();
14666
+ if (repeatType === "reverse") {
14667
+ flippedEases = [...originalEase]
14668
+ .reverse()
14669
+ .map((e) => typeof e === "function"
14670
+ ? reverseEasing(e)
14671
+ : e);
14672
+ }
14582
14673
  }
14583
- valueKeyframesAsList.push(...iterKeyframes);
14584
- for (let keyframeIndex = 0; keyframeIndex < iterKeyframes.length; keyframeIndex++) {
14585
- times.push(originalTimes[keyframeIndex] + iterStartOffset);
14586
- ease.push(keyframeIndex === 0
14587
- ? "linear"
14588
- : getEasingForSegment(iterEase, keyframeIndex - 1));
14674
+ for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
14675
+ const isFlipped = isFlipping && repeatIndex % 2 === 0;
14676
+ const iterKeyframes = isFlipped
14677
+ ? flippedKeyframes
14678
+ : originalKeyframes;
14679
+ const iterEase = isFlipped ? flippedEases : originalEase;
14680
+ const iterStartOffset = (repeatIndex + 1) * (1 + repeatDelayUnits);
14681
+ /**
14682
+ * If repeatDelay is set, hold the previous iteration's
14683
+ * final value through the delay by inserting a keyframe
14684
+ * at the moment the next iteration begins.
14685
+ */
14686
+ if (repeatDelayUnits > 0) {
14687
+ valueKeyframesAsList.push(valueKeyframesAsList[valueKeyframesAsList.length - 1]);
14688
+ times.push(iterStartOffset);
14689
+ ease.push("linear");
14690
+ }
14691
+ valueKeyframesAsList.push(...iterKeyframes);
14692
+ for (let keyframeIndex = 0; keyframeIndex < iterKeyframes.length; keyframeIndex++) {
14693
+ times.push(originalTimes[keyframeIndex] + iterStartOffset);
14694
+ ease.push(keyframeIndex === 0
14695
+ ? "linear"
14696
+ : getEasingForSegment(iterEase, keyframeIndex - 1));
14697
+ }
14589
14698
  }
14699
+ normalizeTimes(times, repeat, repeatDelayUnits);
14590
14700
  }
14591
- normalizeTimes(times, repeat, repeatDelayUnits);
14701
+ const targetTime = startTime + duration;
14702
+ /**
14703
+ * Add keyframes, mapping offsets to absolute time.
14704
+ */
14705
+ addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
14706
+ maxDuration = Math.max(calculatedDelay + duration, maxDuration);
14707
+ totalDuration = Math.max(targetTime, totalDuration);
14708
+ };
14709
+ if (isMotionValue(subject)) {
14710
+ const subjectSequence = getSubjectSequence(subject, sequences);
14711
+ resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
14592
14712
  }
14593
- const targetTime = startTime + duration;
14594
- /**
14595
- * Add keyframes, mapping offsets to absolute time.
14596
- */
14597
- addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
14598
- maxDuration = Math.max(calculatedDelay + duration, maxDuration);
14599
- totalDuration = Math.max(targetTime, totalDuration);
14600
- };
14601
- if (isMotionValue(subject)) {
14602
- const subjectSequence = getSubjectSequence(subject, sequences);
14603
- resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
14604
- }
14605
- else {
14606
- const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
14607
- const numSubjects = subjects.length;
14608
- /**
14609
- * For every element in this segment, process the defined values.
14610
- */
14611
- for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
14713
+ else {
14714
+ const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
14715
+ const numSubjects = subjects.length;
14612
14716
  /**
14613
- * Cast necessary, but we know these are of this type
14717
+ * For every element in this segment, process the defined values.
14614
14718
  */
14615
- keyframes = keyframes;
14616
- transition = transition;
14617
- const thisSubject = subjects[subjectIndex];
14618
- const subjectSequence = getSubjectSequence(thisSubject, sequences);
14619
- for (const key in keyframes) {
14620
- resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
14719
+ for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
14720
+ /**
14721
+ * Cast necessary, but we know these are of this type
14722
+ */
14723
+ keyframes = keyframes;
14724
+ transition = transition;
14725
+ const thisSubject = subjects[subjectIndex];
14726
+ const subjectSequence = getSubjectSequence(thisSubject, sequences);
14727
+ for (const key in keyframes) {
14728
+ resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
14729
+ }
14621
14730
  }
14622
14731
  }
14732
+ prevTime = currentTime;
14733
+ currentTime += maxDuration;
14734
+ sequenceEnd = Math.max(sequenceEnd, currentTime);
14623
14735
  }
14624
- prevTime = currentTime;
14625
- currentTime += maxDuration;
14736
+ return sequenceEnd;
14737
+ };
14738
+ resolveSequence(sequence, 0);
14739
+ if (info) {
14740
+ info.duration = totalDuration;
14741
+ info.labels = timeLabels;
14626
14742
  }
14627
14743
  /**
14628
14744
  * For every element and value combination create a new animation.
@@ -14770,7 +14886,7 @@
14770
14886
  return animations;
14771
14887
  }
14772
14888
 
14773
- function animateSequence(sequence, options, scope) {
14889
+ function animateSequence(sequence, options, scope, info) {
14774
14890
  const animations = [];
14775
14891
  /**
14776
14892
  * Pre-process: replace function segments with MotionValue segments,
@@ -14793,13 +14909,515 @@
14793
14909
  }
14794
14910
  return segment;
14795
14911
  });
14796
- const animationDefinitions = createAnimationsFromSequence(processedSequence, options, scope, { spring });
14912
+ const animationDefinitions = createAnimationsFromSequence(processedSequence, options, scope, { spring }, info);
14797
14913
  animationDefinitions.forEach(({ keyframes, transition }, subject) => {
14798
14914
  animations.push(...animateSubject(subject, keyframes, transition));
14799
14915
  });
14800
14916
  return animations;
14801
14917
  }
14802
14918
 
14919
+ let markerId = 0;
14920
+ /**
14921
+ * Copy a segment with a new `at`, adding a transition if it has none.
14922
+ */
14923
+ function withAt(segment, at) {
14924
+ const copy = [...segment];
14925
+ const i = typeof copy[0] === "function" ? Math.max(1, copy.length - 1) : 2;
14926
+ copy[i] = { ...copy[i], at };
14927
+ return copy;
14928
+ }
14929
+ const label = (name, at) => at === undefined ? name : { name, at };
14930
+ /**
14931
+ * Rewind to the start and stop, so every value is back at the value it
14932
+ * started from and the next build reads those values as its origin.
14933
+ */
14934
+ function rewind(animation) {
14935
+ animation.pause();
14936
+ animation.time = 0;
14937
+ animation.stop();
14938
+ }
14939
+ /**
14940
+ * A sequence that can be changed after it's created. Segments, labels,
14941
+ * callbacks, pauses and other timelines can be added, moved and removed
14942
+ * at any point, including while it plays.
14943
+ *
14944
+ * Changes are batched and applied once per microtask. Each change
14945
+ * rebuilds the underlying animations from the timeline's starting
14946
+ * values, then restores the playhead, speed and play state, so changes
14947
+ * made while playing take effect without a jump.
14948
+ */
14949
+ class Timeline {
14950
+ /**
14951
+ * `animations` are already running animations of `items`, as
14952
+ * animate() makes them. The timeline plays these until it's first
14953
+ * changed, so it costs nothing over the animations themselves until
14954
+ * then.
14955
+ */
14956
+ constructor(items = [], options = {}, animations) {
14957
+ this.options = options;
14958
+ this.entries = [];
14959
+ /**
14960
+ * The repeat the playhead is in, to detect when it starts another.
14961
+ */
14962
+ this.repeatIndex = 0;
14963
+ this.prevTime = 0;
14964
+ this.markers = [];
14965
+ this.labels = new Map();
14966
+ this.totalDuration = 0;
14967
+ this.isDirty = false;
14968
+ this.isDone = false;
14969
+ this.isRebuilding = false;
14970
+ this.playbackSpeed = 1;
14971
+ /**
14972
+ * True while this plays the animations it was created with, before
14973
+ * any change compiles it as a sequence.
14974
+ */
14975
+ this.isWrapped = false;
14976
+ this.stop = () => {
14977
+ this.flush();
14978
+ this.group?.stop();
14979
+ };
14980
+ this.isPaused = options.autoplay === false;
14981
+ this.newFinished();
14982
+ items.forEach((item) => this.add(item));
14983
+ if (animations) {
14984
+ this.isDirty = false;
14985
+ this.isWrapped = true;
14986
+ this.group = new GroupAnimationWithThen(animations);
14987
+ this.watch();
14988
+ }
14989
+ else {
14990
+ /**
14991
+ * Build now, like animate(), so selectors resolve while a
14992
+ * scope is attached. Later changes are batched.
14993
+ */
14994
+ this.invalidate();
14995
+ this.flush();
14996
+ }
14997
+ /**
14998
+ * Register with the scope (for instance from useAnimate()) so it
14999
+ * stops when the scope is torn down.
15000
+ */
15001
+ const { scope } = options;
15002
+ if (scope) {
15003
+ scope.animations.push(this);
15004
+ this.finished.then(() => removeItem(scope.animations, this));
15005
+ }
15006
+ }
15007
+ /**
15008
+ * Add a segment, label or timeline. `at` overrides any `at` already
15009
+ * on the segment. The item itself is the handle for `move()` and
15010
+ * `remove()`.
15011
+ */
15012
+ add(item, at) {
15013
+ if (item instanceof Timeline)
15014
+ item.adopt(this);
15015
+ return this.push({ item, at });
15016
+ }
15017
+ /**
15018
+ * Fire a callback when the playhead crosses `at`, in either direction.
15019
+ * It receives the direction: 1 forwards, -1 backwards.
15020
+ */
15021
+ call(callback, at) {
15022
+ return this.push({
15023
+ item: callback,
15024
+ at,
15025
+ marker: { name: "_m" + markerId++, time: 0, callback },
15026
+ });
15027
+ }
15028
+ /**
15029
+ * Pause when the playhead reaches `at`.
15030
+ */
15031
+ addPause(at, callback) {
15032
+ return this.push({
15033
+ item: callback || {},
15034
+ at,
15035
+ marker: { name: "_m" + markerId++, time: 0, callback, pause: true },
15036
+ });
15037
+ }
15038
+ /**
15039
+ * Change where an item starts.
15040
+ */
15041
+ move(item, at) {
15042
+ this.entries.forEach((entry) => {
15043
+ if (entry.item === item)
15044
+ entry.at = at;
15045
+ });
15046
+ return this.invalidate();
15047
+ }
15048
+ /**
15049
+ * Remove a segment, label, callback or timeline.
15050
+ */
15051
+ remove(item) {
15052
+ if (item instanceof Timeline)
15053
+ item.parent = undefined;
15054
+ this.entries = this.entries.filter((entry) => entry.item !== item);
15055
+ return this.invalidate();
15056
+ }
15057
+ clear() {
15058
+ this.entries = [];
15059
+ return this.invalidate();
15060
+ }
15061
+ /**
15062
+ * The time of a label in seconds, or `undefined` if there's no label
15063
+ * with this name.
15064
+ */
15065
+ getLabelTime(name) {
15066
+ this.flush();
15067
+ return this.labels.get(name);
15068
+ }
15069
+ /**
15070
+ * Jump to a time in seconds or a label.
15071
+ */
15072
+ seek(position) {
15073
+ this.time = this.resolve(position);
15074
+ return this;
15075
+ }
15076
+ /**
15077
+ * Play forwards or backwards to a time or label, then pause there.
15078
+ */
15079
+ playTo(position) {
15080
+ const target = this.resolve(position);
15081
+ const speed = Math.abs(this.speed);
15082
+ this.speed = target < this.time ? -speed : speed;
15083
+ this.stopAt = target;
15084
+ this.clock || this.invalidate();
15085
+ this.play();
15086
+ return this;
15087
+ }
15088
+ reverse() {
15089
+ this.speed = -this.speed;
15090
+ this.play();
15091
+ return this;
15092
+ }
15093
+ get progress() {
15094
+ return this.duration ? this.time / this.duration : 0;
15095
+ }
15096
+ set progress(progress) {
15097
+ this.time = progress * this.duration;
15098
+ }
15099
+ get time() {
15100
+ return this.active()?.time ?? 0;
15101
+ }
15102
+ set time(time) {
15103
+ this.flush();
15104
+ this.prevTime = time;
15105
+ this.group && (this.group.time = time);
15106
+ this.repeatIndex = this.currentRepeat();
15107
+ }
15108
+ get speed() {
15109
+ return this.playbackSpeed;
15110
+ }
15111
+ set speed(speed) {
15112
+ this.playbackSpeed = speed;
15113
+ this.group && (this.group.speed = speed);
15114
+ }
15115
+ get duration() {
15116
+ this.flush();
15117
+ return this.isWrapped ? this.group.duration : this.totalDuration;
15118
+ }
15119
+ get iterationDuration() {
15120
+ this.flush();
15121
+ return this.isWrapped
15122
+ ? this.group.iterationDuration
15123
+ : this.totalDuration;
15124
+ }
15125
+ get state() {
15126
+ return this.active()?.state ?? "idle";
15127
+ }
15128
+ get startTime() {
15129
+ return this.active()?.startTime ?? null;
15130
+ }
15131
+ play() {
15132
+ this.flush();
15133
+ this.isPaused = false;
15134
+ this.group?.play();
15135
+ /**
15136
+ * Watch after playing, so it waits for the restarted animations
15137
+ * rather than the ones that already finished.
15138
+ */
15139
+ if (this.isDone) {
15140
+ this.newFinished();
15141
+ this.watch();
15142
+ }
15143
+ }
15144
+ pause() {
15145
+ this.flush();
15146
+ this.isPaused = true;
15147
+ this.group?.pause();
15148
+ }
15149
+ cancel() {
15150
+ this.flush();
15151
+ this.group?.cancel();
15152
+ }
15153
+ complete() {
15154
+ this.flush();
15155
+ this.group?.complete();
15156
+ }
15157
+ attachTimeline(timeline) {
15158
+ this.flush();
15159
+ this.scrollTimeline = timeline;
15160
+ this.detachScroll = this.group?.attachTimeline(timeline);
15161
+ return () => {
15162
+ this.scrollTimeline = undefined;
15163
+ this.detachScroll?.();
15164
+ };
15165
+ }
15166
+ then(onResolve, onReject) {
15167
+ return this.finished.then(onResolve, onReject);
15168
+ }
15169
+ /**
15170
+ * The current animations, if any are running. Skipped animations
15171
+ * (skipAnimations, reduced motion) can leave the group empty.
15172
+ */
15173
+ active() {
15174
+ this.flush();
15175
+ return this.group?.animations.length ? this.group : undefined;
15176
+ }
15177
+ push(entry) {
15178
+ this.entries.push(entry);
15179
+ return this.invalidate();
15180
+ }
15181
+ /**
15182
+ * Schedule a rebuild. A nested timeline passes changes up, as it's
15183
+ * built as part of its parent.
15184
+ */
15185
+ invalidate() {
15186
+ if (this.parent) {
15187
+ this.parent.invalidate();
15188
+ }
15189
+ else if (!this.isDirty) {
15190
+ this.isDirty = true;
15191
+ queueMicrotask(() => this.flush());
15192
+ }
15193
+ return this;
15194
+ }
15195
+ /**
15196
+ * Apply pending changes now, so reads straight after a change
15197
+ * reflect it.
15198
+ */
15199
+ flush() {
15200
+ this.isDirty && !this.parent && this.build();
15201
+ }
15202
+ adopt(parent) {
15203
+ if (this.group) {
15204
+ rewind(this.group);
15205
+ this.group = undefined;
15206
+ }
15207
+ this.isDirty = false;
15208
+ this.parent = parent;
15209
+ }
15210
+ build() {
15211
+ this.isDirty = false;
15212
+ this.isWrapped = false;
15213
+ const prev = this.group;
15214
+ let time = 0;
15215
+ if (prev) {
15216
+ this.isRebuilding = true;
15217
+ if (this.scrollTimeline) {
15218
+ /**
15219
+ * Scroll-linked animations can't be seeked in seconds.
15220
+ * Cancelling also returns them to their starting values,
15221
+ * and the new build is attached to scroll instead.
15222
+ */
15223
+ this.detachScroll?.();
15224
+ prev.cancel();
15225
+ }
15226
+ else {
15227
+ time = prev.animations.length ? prev.time : 0;
15228
+ rewind(prev);
15229
+ }
15230
+ this.isRebuilding = false;
15231
+ }
15232
+ const { defaultTransition, onComplete, onUpdate, onRepeat, onReverseComplete, autoplay, scope, ...options } = this.options;
15233
+ /**
15234
+ * Inherit MotionConfig settings from a useAnimate() scope.
15235
+ */
15236
+ const playback = {};
15237
+ if (scope?.reduceMotion !== undefined) {
15238
+ playback.reduceMotion = scope.reduceMotion;
15239
+ }
15240
+ if (scope?.skipAnimations !== undefined) {
15241
+ playback.skipAnimations = scope.skipAnimations;
15242
+ }
15243
+ Object.assign(playback, options);
15244
+ const markers = new Map();
15245
+ const info = {};
15246
+ const animations = animateSequence(this.toSequence(markers), { defaultTransition, ...playback }, scope, info);
15247
+ const labels = info.labels;
15248
+ let duration = playback.duration ?? info.duration;
15249
+ /**
15250
+ * A `duration` option stretches every animation to fit, so scale
15251
+ * label and marker times to match.
15252
+ */
15253
+ const scale = info.duration ? duration / info.duration : 1;
15254
+ labels.forEach((time, name) => labels.set(name, time * scale));
15255
+ /**
15256
+ * Markers are resolved as labels. Move them out of the label map
15257
+ * and extend the duration to reach any marker past the end.
15258
+ */
15259
+ this.markers = [];
15260
+ markers.forEach((marker, name) => {
15261
+ marker.time = labels.get(name);
15262
+ labels.delete(name);
15263
+ duration = Math.max(duration, marker.time);
15264
+ this.markers.push(marker);
15265
+ });
15266
+ this.markers.sort((a, b) => a.time - b.time);
15267
+ this.labels = labels;
15268
+ this.totalDuration = duration;
15269
+ /**
15270
+ * Callbacks, pauses and playTo() watch a linear clock. Without
15271
+ * them, the timeline is only its animations.
15272
+ */
15273
+ this.clock = this.clockAnimation = undefined;
15274
+ if (this.markers.length ||
15275
+ this.stopAt !== undefined ||
15276
+ onUpdate ||
15277
+ onRepeat) {
15278
+ const clock = (this.clock = motionValue(0));
15279
+ clock.on("change", (t) => clock === this.clock && this.onClock(t));
15280
+ animations.unshift((this.clockAnimation = animateSingleValue(clock, [0, duration], {
15281
+ ...playback,
15282
+ duration,
15283
+ ease: "linear",
15284
+ })));
15285
+ }
15286
+ const group = (this.group = new GroupAnimationWithThen(animations));
15287
+ time = Math.min(time, duration);
15288
+ if (this.isDone && time < duration)
15289
+ this.newFinished();
15290
+ if (this.scrollTimeline) {
15291
+ this.detachScroll = group.attachTimeline(this.scrollTimeline);
15292
+ }
15293
+ else {
15294
+ group.speed = this.playbackSpeed;
15295
+ if (this.isPaused)
15296
+ group.pause();
15297
+ if (time)
15298
+ group.time = time;
15299
+ }
15300
+ this.repeatIndex = this.currentRepeat();
15301
+ /**
15302
+ * Treat a start from 0 as coming from just before it, so markers
15303
+ * at 0 fire.
15304
+ */
15305
+ this.prevTime = time || -1;
15306
+ this.watch();
15307
+ }
15308
+ toSequence(markers) {
15309
+ return this.entries.map(({ item, at, marker }) => {
15310
+ if (marker) {
15311
+ markers.set(marker.name, marker);
15312
+ return label(marker.name, at);
15313
+ }
15314
+ if (item instanceof Timeline) {
15315
+ return { sequence: item.toSequence(markers), at };
15316
+ }
15317
+ if (typeof item === "string")
15318
+ return label(item, at);
15319
+ if (at === undefined)
15320
+ return item;
15321
+ return Array.isArray(item)
15322
+ ? withAt(item, at)
15323
+ : { ...item, at };
15324
+ });
15325
+ }
15326
+ resolve(position) {
15327
+ this.flush();
15328
+ return typeof position === "number"
15329
+ ? position
15330
+ : calcLabelTime(position, this.labels) ?? this.time;
15331
+ }
15332
+ /**
15333
+ * The repeat the clock is in, from its time across all repeats.
15334
+ */
15335
+ currentRepeat() {
15336
+ const { repeat = 0, repeatDelay = 0, delay = 0 } = this.options;
15337
+ const cycle = this.totalDuration + repeatDelay;
15338
+ const time = (this.clockAnimation?.time ?? 0) - delay;
15339
+ return cycle && time > 0
15340
+ ? Math.min(repeat, Math.floor(time / cycle))
15341
+ : 0;
15342
+ }
15343
+ onClock(time) {
15344
+ let prevTime = this.prevTime;
15345
+ this.prevTime = time;
15346
+ if (this.isRebuilding)
15347
+ return;
15348
+ const { onUpdate, onRepeat, repeatType = "loop" } = this.options;
15349
+ onUpdate?.(time);
15350
+ const repeatIndex = this.currentRepeat();
15351
+ if (repeatIndex !== this.repeatIndex) {
15352
+ this.repeatIndex = repeatIndex;
15353
+ onRepeat?.();
15354
+ /**
15355
+ * A loop jumps the playhead back to the start, or to the
15356
+ * end when playing backwards. Finish crossing the repeat
15357
+ * that ended, then cross the new one from its edge.
15358
+ */
15359
+ if (repeatType === "loop") {
15360
+ const isForward = this.speed > 0;
15361
+ if (this.cross(prevTime, isForward ? this.totalDuration : 0)) {
15362
+ return;
15363
+ }
15364
+ prevTime = isForward ? -1 : Infinity;
15365
+ }
15366
+ }
15367
+ this.cross(prevTime, time);
15368
+ }
15369
+ /**
15370
+ * Fire markers between two playhead times. Returns `true` if it
15371
+ * paused.
15372
+ */
15373
+ cross(from, to) {
15374
+ if (from === to)
15375
+ return;
15376
+ const isForward = to > from;
15377
+ const crossed = (t) => isForward ? t > from && t <= to : t < from && t >= to;
15378
+ const markers = isForward ? this.markers : [...this.markers].reverse();
15379
+ for (const marker of markers) {
15380
+ if (!crossed(marker.time))
15381
+ continue;
15382
+ marker.callback?.(isForward ? 1 : -1);
15383
+ if (marker.pause)
15384
+ return this.pauseAt(marker.time);
15385
+ }
15386
+ if (this.stopAt !== undefined && crossed(this.stopAt)) {
15387
+ this.pauseAt(this.stopAt);
15388
+ this.stopAt = undefined;
15389
+ return true;
15390
+ }
15391
+ }
15392
+ pauseAt(time) {
15393
+ this.pause();
15394
+ this.time = time;
15395
+ return true;
15396
+ }
15397
+ watch() {
15398
+ const group = this.group;
15399
+ group.finished.then(() => {
15400
+ if (group !== this.group || this.isDone)
15401
+ return;
15402
+ this.isDone = true;
15403
+ const { onComplete, onReverseComplete } = this.options;
15404
+ /**
15405
+ * Without onReverseComplete, onComplete fires in either
15406
+ * direction, as it does for a single animation.
15407
+ */
15408
+ const callback = (this.speed < 0 && onReverseComplete) || onComplete;
15409
+ callback?.();
15410
+ this.resolveFinished();
15411
+ });
15412
+ }
15413
+ newFinished() {
15414
+ this.isDone = false;
15415
+ this.finished = new Promise((resolve) => {
15416
+ this.resolveFinished = resolve;
15417
+ });
15418
+ }
15419
+ }
15420
+
14803
15421
  function isSequence(value) {
14804
15422
  return Array.isArray(value) && value.some(Array.isArray);
14805
15423
  }
@@ -14813,8 +15431,6 @@
14813
15431
  * Implementation
14814
15432
  */
14815
15433
  function scopedAnimate(subjectOrSequence, optionsOrKeyframes, options) {
14816
- let animations = [];
14817
- let animationOnComplete;
14818
15434
  const motionScope = activeScope.current;
14819
15435
  const scopeTransition = motionScope?.transition;
14820
15436
  const inherited = {};
@@ -14822,49 +15438,58 @@
14822
15438
  inherited.reduceMotion = reduceMotion;
14823
15439
  if (skipAnimations !== undefined)
14824
15440
  inherited.skipAnimations = skipAnimations;
15441
+ let timeline;
14825
15442
  if (isSequence(subjectOrSequence)) {
14826
- const { onComplete, ...sequenceOptions } = optionsOrKeyframes || {};
14827
- if (typeof onComplete === "function") {
14828
- animationOnComplete = onComplete;
14829
- }
15443
+ const sequenceOptions = {
15444
+ ...inherited,
15445
+ ...optionsOrKeyframes,
15446
+ scope,
15447
+ };
14830
15448
  if (scopeTransition) {
14831
15449
  sequenceOptions.defaultTransition = {
14832
15450
  ...scopeTransition,
14833
15451
  ...sequenceOptions.defaultTransition,
14834
15452
  };
14835
15453
  }
14836
- animations = animateSequence(subjectOrSequence, { ...inherited, ...sequenceOptions }, scope);
15454
+ timeline = new Timeline(subjectOrSequence, sequenceOptions);
14837
15455
  }
14838
15456
  else {
14839
15457
  // Extract top-level onComplete so it doesn't get applied per-value
14840
15458
  const { onComplete, ...rest } = options || {};
14841
- if (typeof onComplete === "function") {
14842
- animationOnComplete = onComplete;
14843
- }
14844
- animations = animateSubject(subjectOrSequence, optionsOrKeyframes, {
15459
+ const transition = {
14845
15460
  ...inherited,
14846
15461
  ...scopeTransition,
14847
15462
  ...rest,
14848
- }, scope);
14849
- }
14850
- const animation = new GroupAnimationWithThen(animations);
14851
- if (animationOnComplete) {
14852
- animation.finished.then(animationOnComplete);
15463
+ };
15464
+ /**
15465
+ * Play the animations directly, and only compile them as a
15466
+ * sequence if the timeline is changed. Values animated from a
15467
+ * number are animated via their onUpdate callback instead.
15468
+ */
15469
+ const { onUpdate } = rest;
15470
+ const segment = (typeof subjectOrSequence === "number" ||
15471
+ (typeof subjectOrSequence === "string" &&
15472
+ (typeof optionsOrKeyframes === "string" ||
15473
+ Array.isArray(optionsOrKeyframes)))
15474
+ ? [
15475
+ onUpdate || (() => { }),
15476
+ Array.isArray(optionsOrKeyframes)
15477
+ ? optionsOrKeyframes
15478
+ : [subjectOrSequence, optionsOrKeyframes],
15479
+ { ...transition, onUpdate: undefined },
15480
+ ]
15481
+ : [subjectOrSequence, optionsOrKeyframes, transition]);
15482
+ timeline = new Timeline([segment], { ...inherited, onComplete, scope }, animateSubject(subjectOrSequence, optionsOrKeyframes, transition, scope));
14853
15483
  }
14854
15484
  if (motionScope) {
14855
- animation.finished.then(motionScope.add(() => animation.stop()));
15485
+ timeline.finished.then(motionScope.add(() => timeline.stop()));
14856
15486
  }
14857
- if (scope) {
14858
- scope.animations.push(animation);
14859
- animation.finished.then(() => {
14860
- removeItem(scope.animations, animation);
14861
- });
14862
- }
14863
- return animation;
15487
+ return timeline;
14864
15488
  }
14865
15489
  return scopedAnimate;
14866
15490
  }
14867
- const animate = Object.assign(createScopedAnimate(), {
15491
+ const animate = /* @__PURE__ */ Object.assign(
15492
+ /* @__PURE__ */ createScopedAnimate(), {
14868
15493
  /**
14869
15494
  * Register an effect so `animate()` can animate the subjects it
14870
15495
  * claims, for instance `animate.addEffect(threeEffect)`. The most
@@ -14882,7 +15507,9 @@
14882
15507
  }
14883
15508
  const elements = resolveElements(elementOrSelector, scope);
14884
15509
  const numElements = elements.length;
14885
- exports.invariant(Boolean(numElements), "No valid elements provided.", "no-valid-elements");
15510
+ typeof process !== "undefined" &&
15511
+ "development" !== "production" &&
15512
+ exports.invariant(Boolean(numElements), "No valid elements provided.", "no-valid-elements");
14886
15513
  /**
14887
15514
  * WAAPI doesn't support interrupting animations.
14888
15515
  *
@@ -15191,22 +15818,26 @@
15191
15818
  const p = (i + progress(at(i), at(i + 1), v)) / n;
15192
15819
  return reverse ? 1 - p : p;
15193
15820
  }
15194
- function resolveOffsets(info, options) {
15195
- const { offset: offsetDefinition = ScrollOffset.All, axis = "y" } = options;
15196
- const axisInfo = info[axis];
15197
- /**
15198
- * Reset the length of the resolved offset array rather than creating a new one.
15199
- */
15200
- axisInfo.offset.length = 0;
15201
- /**
15202
- * Populate the offset array by resolving the user's offset definition into
15203
- * a list of pixel scroll offsets.
15204
- */
15205
- const numOffsets = offsetDefinition.length;
15206
- for (let i = 0; i < numOffsets; i++) {
15207
- axisInfo.offset[i] = resolveOffset(offsetDefinition[i], axisInfo.containerLength, axisInfo.targetLength, axisInfo.targetOffset);
15821
+ /**
15822
+ * Resolves offsets on both axes, as useScroll reports the target's progress
15823
+ * on each (#2546). `axis` only picks which progress scroll() reports.
15824
+ */
15825
+ function resolveOffsets(info, { offset: offsetDefinition = ScrollOffset.All }) {
15826
+ for (const axis in axisKeys) {
15827
+ const axisInfo = info[axis];
15828
+ /**
15829
+ * Reset the length of the resolved offset array rather than creating a new one.
15830
+ */
15831
+ axisInfo.offset.length = 0;
15832
+ /**
15833
+ * Populate the offset array by resolving the user's offset definition into
15834
+ * a list of pixel scroll offsets.
15835
+ */
15836
+ for (let i = 0; i < offsetDefinition.length; i++) {
15837
+ axisInfo.offset[i] = resolveOffset(offsetDefinition[i], axisInfo.containerLength, axisInfo.targetLength, axisInfo.targetOffset);
15838
+ }
15839
+ axisInfo.progress = clamp(0, 1, offsetsToProgress(axisInfo.offset, axisInfo.current));
15208
15840
  }
15209
- axisInfo.progress = clamp(0, 1, offsetsToProgress(axisInfo.offset, axisInfo.current));
15210
15841
  }
15211
15842
 
15212
15843
  function calcInset(element, container) {
@@ -15712,6 +16343,7 @@
15712
16343
  exports.buildSVGAttrs = buildSVGAttrs;
15713
16344
  exports.buildSVGPath = buildSVGPath;
15714
16345
  exports.buildTransform = buildTransform$1;
16346
+ exports.buildTransformer = buildTransformer;
15715
16347
  exports.calcAxisDelta = calcAxisDelta;
15716
16348
  exports.calcBoxDelta = calcBoxDelta;
15717
16349
  exports.calcChildStagger = calcChildStagger;
@@ -15890,6 +16522,7 @@
15890
16522
  exports.positionalKeys = positionalKeys;
15891
16523
  exports.prefersReducedMotion = prefersReducedMotion;
15892
16524
  exports.press = press;
16525
+ exports.processFrame = processFrame;
15893
16526
  exports.progress = progress;
15894
16527
  exports.progressPercentage = progressPercentage;
15895
16528
  exports.propEffect = propEffect;