framer-motion 12.35.0 → 12.35.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/cjs/client.js +1 -1
  2. package/dist/cjs/dom.js +1 -1
  3. package/dist/cjs/dom.js.map +1 -1
  4. package/dist/cjs/{feature-bundle-jADFMKLx.js → feature-bundle-DqHxNjy5.js} +14 -4
  5. package/dist/cjs/feature-bundle-DqHxNjy5.js.map +1 -0
  6. package/dist/cjs/index.js +77 -5
  7. package/dist/cjs/index.js.map +1 -1
  8. package/dist/dom-mini.d.ts +17 -5
  9. package/dist/dom-mini.js +1 -1
  10. package/dist/dom.d.ts +18 -6
  11. package/dist/dom.js +1 -1
  12. package/dist/es/components/AnimatePresence/PopChild.mjs +4 -3
  13. package/dist/es/components/AnimatePresence/PopChild.mjs.map +1 -1
  14. package/dist/es/events/event-info.mjs +1 -3
  15. package/dist/es/events/event-info.mjs.map +1 -1
  16. package/dist/es/gestures/pan/PanSession.mjs +12 -0
  17. package/dist/es/gestures/pan/PanSession.mjs.map +1 -1
  18. package/dist/es/index.mjs +1 -0
  19. package/dist/es/index.mjs.map +1 -1
  20. package/dist/es/render/dom/scroll/info.mjs +1 -1
  21. package/dist/es/render/dom/scroll/info.mjs.map +1 -1
  22. package/dist/es/utils/transform-rotated-parent.mjs +72 -0
  23. package/dist/es/utils/transform-rotated-parent.mjs.map +1 -0
  24. package/dist/framer-motion.dev.js +294 -88
  25. package/dist/framer-motion.js +1 -1
  26. package/dist/mini.js +1 -1
  27. package/dist/size-rollup-animate.js +1 -1
  28. package/dist/size-rollup-animate.js.map +1 -1
  29. package/dist/size-rollup-dom-animation-assets.js +1 -1
  30. package/dist/size-rollup-dom-animation.js +1 -1
  31. package/dist/size-rollup-dom-max-assets.js +1 -1
  32. package/dist/size-rollup-dom-max.js +1 -1
  33. package/dist/size-rollup-m.js +1 -1
  34. package/dist/size-rollup-m.js.map +1 -1
  35. package/dist/size-rollup-motion.js +1 -1
  36. package/dist/size-rollup-motion.js.map +1 -1
  37. package/dist/size-rollup-scroll.js +1 -1
  38. package/dist/size-rollup-scroll.js.map +1 -1
  39. package/dist/size-rollup-waapi-animate.js +1 -1
  40. package/dist/size-rollup-waapi-animate.js.map +1 -1
  41. package/dist/types/index.d.ts +50 -6
  42. package/package.json +3 -3
  43. package/dist/cjs/feature-bundle-jADFMKLx.js.map +0 -1
@@ -834,8 +834,7 @@
834
834
  function parseComplexValue(v) {
835
835
  return analyseComplexValue(v).values;
836
836
  }
837
- function createTransformer(source) {
838
- const { split, types } = analyseComplexValue(source);
837
+ function buildTransformer({ split, types }) {
839
838
  const numSections = split.length;
840
839
  return (v) => {
841
840
  let output = "";
@@ -857,11 +856,34 @@
857
856
  return output;
858
857
  };
859
858
  }
859
+ function createTransformer(source) {
860
+ return buildTransformer(analyseComplexValue(source));
861
+ }
860
862
  const convertNumbersToZero = (v) => typeof v === "number" ? 0 : color.test(v) ? color.getAnimatableNone(v) : v;
863
+ /**
864
+ * Convert a parsed value to its zero equivalent, but preserve numbers
865
+ * that act as divisors in CSS calc() expressions.
866
+ *
867
+ * analyseComplexValue extracts numbers from CSS strings and puts the
868
+ * surrounding text into a `split` template array. For example:
869
+ * "calc(var(--gap) / 5)" → values: [var(--gap), 5]
870
+ * split: ["calc(", " / ", ")"]
871
+ *
872
+ * When building a zero-equivalent for animation, naively zeroing all
873
+ * numbers turns the divisor into 0 → "calc(var(--gap) / 0)" → NaN.
874
+ * We detect this by checking whether the text preceding a number
875
+ * (split[i]) ends with "/" — the CSS calc division operator.
876
+ */
877
+ const convertToZero = (value, splitBefore) => {
878
+ if (typeof value === "number") {
879
+ return splitBefore?.trim().endsWith("/") ? value : 0;
880
+ }
881
+ return convertNumbersToZero(value);
882
+ };
861
883
  function getAnimatableNone$1(v) {
862
- const parsed = parseComplexValue(v);
863
- const transformer = createTransformer(v);
864
- return transformer(parsed.map(convertNumbersToZero));
884
+ const info = analyseComplexValue(v);
885
+ const transformer = buildTransformer(info);
886
+ return transformer(info.values.map((value, i) => convertToZero(value, info.split[i])));
865
887
  }
866
888
  const complex = {
867
889
  test,
@@ -1140,12 +1162,6 @@
1140
1162
  };
1141
1163
  }
1142
1164
 
1143
- const velocitySampleDuration = 5; // ms
1144
- function calcGeneratorVelocity(resolveValue, t, current) {
1145
- const prevT = Math.max(t - velocitySampleDuration, 0);
1146
- return velocityPerSecond(current - resolveValue(prevT), t - prevT);
1147
- }
1148
-
1149
1165
  const springDefaults = {
1150
1166
  // Default spring physics
1151
1167
  stiffness: 100,
@@ -1171,7 +1187,20 @@
1171
1187
  minDamping: 0.05,
1172
1188
  maxDamping: 1,
1173
1189
  };
1174
-
1190
+ function calcAngularFreq(undampedFreq, dampingRatio) {
1191
+ return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
1192
+ }
1193
+ const rootIterations = 12;
1194
+ function approximateRoot(envelope, derivative, initialGuess) {
1195
+ let result = initialGuess;
1196
+ for (let i = 1; i < rootIterations; i++) {
1197
+ result = result - envelope(result) / derivative(result);
1198
+ }
1199
+ return result;
1200
+ }
1201
+ /**
1202
+ * This is ported from the Framer implementation of duration-based spring resolution.
1203
+ */
1175
1204
  const safeMin = 0.001;
1176
1205
  function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, velocity = springDefaults.velocity, mass = springDefaults.mass, }) {
1177
1206
  let envelope;
@@ -1240,18 +1269,6 @@
1240
1269
  };
1241
1270
  }
1242
1271
  }
1243
- const rootIterations = 12;
1244
- function approximateRoot(envelope, derivative, initialGuess) {
1245
- let result = initialGuess;
1246
- for (let i = 1; i < rootIterations; i++) {
1247
- result = result - envelope(result) / derivative(result);
1248
- }
1249
- return result;
1250
- }
1251
- function calcAngularFreq(undampedFreq, dampingRatio) {
1252
- return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
1253
- }
1254
-
1255
1272
  const durationKeys = ["duration", "bounce"];
1256
1273
  const physicsKeys = ["stiffness", "damping", "mass"];
1257
1274
  function isSpringType(options, keys) {
@@ -1339,19 +1356,37 @@
1339
1356
  ? springDefaults.restDelta.granular
1340
1357
  : springDefaults.restDelta.default);
1341
1358
  let resolveSpring;
1359
+ let resolveVelocity;
1360
+ // Underdamped coefficients, hoisted for use in the inlined next() hot path
1361
+ let angularFreq;
1362
+ let A;
1363
+ let sinCoeff;
1364
+ let cosCoeff;
1342
1365
  if (dampingRatio < 1) {
1343
- const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
1366
+ angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
1367
+ A =
1368
+ (initialVelocity +
1369
+ dampingRatio * undampedAngularFreq * initialDelta) /
1370
+ angularFreq;
1344
1371
  // Underdamped spring
1345
1372
  resolveSpring = (t) => {
1346
1373
  const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1347
1374
  return (target -
1348
1375
  envelope *
1349
- (((initialVelocity +
1350
- dampingRatio * undampedAngularFreq * initialDelta) /
1351
- angularFreq) *
1352
- Math.sin(angularFreq * t) +
1376
+ (A * Math.sin(angularFreq * t) +
1353
1377
  initialDelta * Math.cos(angularFreq * t)));
1354
1378
  };
1379
+ // Analytical derivative of underdamped spring (px/ms)
1380
+ sinCoeff =
1381
+ dampingRatio * undampedAngularFreq * A + initialDelta * angularFreq;
1382
+ cosCoeff =
1383
+ dampingRatio * undampedAngularFreq * initialDelta - A * angularFreq;
1384
+ resolveVelocity = (t) => {
1385
+ const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1386
+ return envelope *
1387
+ (sinCoeff * Math.sin(angularFreq * t) +
1388
+ cosCoeff * Math.cos(angularFreq * t));
1389
+ };
1355
1390
  }
1356
1391
  else if (dampingRatio === 1) {
1357
1392
  // Critically damped spring
@@ -1359,6 +1394,10 @@
1359
1394
  Math.exp(-undampedAngularFreq * t) *
1360
1395
  (initialDelta +
1361
1396
  (initialVelocity + undampedAngularFreq * initialDelta) * t);
1397
+ // Analytical derivative of critically damped spring (px/ms)
1398
+ const C = initialVelocity + undampedAngularFreq * initialDelta;
1399
+ resolveVelocity = (t) => Math.exp(-undampedAngularFreq * t) *
1400
+ (undampedAngularFreq * C * t - initialVelocity);
1362
1401
  }
1363
1402
  else {
1364
1403
  // Overdamped spring
@@ -1377,28 +1416,50 @@
1377
1416
  Math.cosh(freqForT))) /
1378
1417
  dampedAngularFreq);
1379
1418
  };
1419
+ // Analytical derivative of overdamped spring (px/ms)
1420
+ const P = (initialVelocity +
1421
+ dampingRatio * undampedAngularFreq * initialDelta) /
1422
+ dampedAngularFreq;
1423
+ const sinhCoeff = dampingRatio * undampedAngularFreq * P - initialDelta * dampedAngularFreq;
1424
+ const coshCoeff = dampingRatio * undampedAngularFreq * initialDelta - P * dampedAngularFreq;
1425
+ resolveVelocity = (t) => {
1426
+ const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1427
+ const freqForT = Math.min(dampedAngularFreq * t, 300);
1428
+ return envelope *
1429
+ (sinhCoeff * Math.sinh(freqForT) +
1430
+ coshCoeff * Math.cosh(freqForT));
1431
+ };
1380
1432
  }
1381
1433
  const generator = {
1382
1434
  calculatedDuration: isResolvedFromDuration ? duration || null : null,
1435
+ velocity: (t) => secondsToMilliseconds(resolveVelocity(t)),
1383
1436
  next: (t) => {
1437
+ /**
1438
+ * For underdamped physics springs we need both position and
1439
+ * velocity each tick. Compute shared trig values once to avoid
1440
+ * duplicate Math.exp/sin/cos calls on the hot path.
1441
+ */
1442
+ if (!isResolvedFromDuration && dampingRatio < 1) {
1443
+ const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1444
+ const sin = Math.sin(angularFreq * t);
1445
+ const cos = Math.cos(angularFreq * t);
1446
+ const current = target -
1447
+ envelope *
1448
+ (A * sin + initialDelta * cos);
1449
+ const currentVelocity = secondsToMilliseconds(envelope *
1450
+ (sinCoeff * sin + cosCoeff * cos));
1451
+ state.done =
1452
+ Math.abs(currentVelocity) <= restSpeed &&
1453
+ Math.abs(target - current) <= restDelta;
1454
+ state.value = state.done ? target : current;
1455
+ return state;
1456
+ }
1384
1457
  const current = resolveSpring(t);
1385
1458
  if (!isResolvedFromDuration) {
1386
- let currentVelocity = t === 0 ? initialVelocity : 0.0;
1387
- /**
1388
- * We only need to calculate velocity for under-damped springs
1389
- * as over- and critically-damped springs can't overshoot, so
1390
- * checking only for displacement is enough.
1391
- */
1392
- if (dampingRatio < 1) {
1393
- currentVelocity =
1394
- t === 0
1395
- ? secondsToMilliseconds(initialVelocity)
1396
- : calcGeneratorVelocity(resolveSpring, t, current);
1397
- }
1398
- const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
1399
- const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
1459
+ const currentVelocity = secondsToMilliseconds(resolveVelocity(t));
1400
1460
  state.done =
1401
- isBelowVelocityThreshold && isBelowDisplacementThreshold;
1461
+ Math.abs(currentVelocity) <= restSpeed &&
1462
+ Math.abs(target - current) <= restDelta;
1402
1463
  }
1403
1464
  else {
1404
1465
  state.done = t >= duration;
@@ -1423,6 +1484,12 @@
1423
1484
  return options;
1424
1485
  };
1425
1486
 
1487
+ const velocitySampleDuration = 5; // ms
1488
+ function getGeneratorVelocity(resolveValue, t, current) {
1489
+ const prevT = Math.max(t - velocitySampleDuration, 0);
1490
+ return velocityPerSecond(current - resolveValue(prevT), t - prevT);
1491
+ }
1492
+
1426
1493
  function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
1427
1494
  const origin = keyframes[0];
1428
1495
  const state = {
@@ -1468,7 +1535,7 @@
1468
1535
  timeReachedBoundary = t;
1469
1536
  spring$1 = spring({
1470
1537
  keyframes: [state.value, nearestBoundary(state.value)],
1471
- velocity: calcGeneratorVelocity(calcLatest, t, state.value), // TODO: This should be passing * 1000
1538
+ velocity: getGeneratorVelocity(calcLatest, t, state.value), // TODO: This should be passing * 1000
1472
1539
  damping: bounceDamping,
1473
1540
  stiffness: bounceStiffness,
1474
1541
  restDelta,
@@ -1868,7 +1935,7 @@
1868
1935
  const state = isInDelayPhase
1869
1936
  ? { done: false, value: keyframes[0] }
1870
1937
  : frameGenerator.next(elapsed);
1871
- if (mixKeyframes) {
1938
+ if (mixKeyframes && !isInDelayPhase) {
1872
1939
  state.value = mixKeyframes(state.value);
1873
1940
  }
1874
1941
  let { done } = state;
@@ -1921,16 +1988,42 @@
1921
1988
  else if (this.driver) {
1922
1989
  this.startTime = this.driver.now() - newTime / this.playbackSpeed;
1923
1990
  }
1924
- this.driver?.start(false);
1991
+ if (this.driver) {
1992
+ this.driver.start(false);
1993
+ }
1994
+ else {
1995
+ this.startTime = 0;
1996
+ this.state = "paused";
1997
+ this.holdTime = newTime;
1998
+ this.tick(newTime);
1999
+ }
2000
+ }
2001
+ /**
2002
+ * Returns the generator's velocity at the current time in units/second.
2003
+ * Uses the analytical derivative when available (springs), avoiding
2004
+ * the MotionValue's frame-dependent velocity estimation.
2005
+ */
2006
+ getGeneratorVelocity() {
2007
+ const t = this.currentTime;
2008
+ if (t <= 0)
2009
+ return this.options.velocity || 0;
2010
+ if (this.generator.velocity) {
2011
+ return this.generator.velocity(t);
2012
+ }
2013
+ // Fallback: finite difference
2014
+ const current = this.generator.next(t).value;
2015
+ return getGeneratorVelocity((s) => this.generator.next(s).value, t, current);
1925
2016
  }
1926
2017
  get speed() {
1927
2018
  return this.playbackSpeed;
1928
2019
  }
1929
2020
  set speed(newSpeed) {
1930
- this.updateTime(time.now());
1931
2021
  const hasChanged = this.playbackSpeed !== newSpeed;
2022
+ if (hasChanged && this.driver) {
2023
+ this.updateTime(time.now());
2024
+ }
1932
2025
  this.playbackSpeed = newSpeed;
1933
- if (hasChanged) {
2026
+ if (hasChanged && this.driver) {
1934
2027
  this.time = millisecondsToSeconds(this.currentTime);
1935
2028
  }
1936
2029
  }
@@ -2540,9 +2633,13 @@
2540
2633
  return millisecondsToSeconds(Number(this.animation.currentTime) || 0);
2541
2634
  }
2542
2635
  set time(newTime) {
2636
+ const wasFinished = this.finishedTime !== null;
2543
2637
  this.manualStartTime = null;
2544
2638
  this.finishedTime = null;
2545
2639
  this.animation.currentTime = secondsToMilliseconds(newTime);
2640
+ if (wasFinished) {
2641
+ this.animation.pause();
2642
+ }
2546
2643
  }
2547
2644
  /**
2548
2645
  * The playback speed of the animation.
@@ -2632,7 +2729,12 @@
2632
2729
  */
2633
2730
  replaceTransitionType(options);
2634
2731
  super(options);
2635
- if (options.startTime !== undefined) {
2732
+ /**
2733
+ * Only set startTime when the animation should autoplay.
2734
+ * Setting startTime on a paused WAAPI animation unpauses it
2735
+ * (per the WAAPI spec), which breaks autoplay: false.
2736
+ */
2737
+ if (options.startTime !== undefined && options.autoplay !== false) {
2636
2738
  this.startTime = options.startTime;
2637
2739
  }
2638
2740
  this.options = options;
@@ -2664,7 +2766,16 @@
2664
2766
  */
2665
2767
  const sampleTime = Math.max(sampleDelta, time.now() - this.startTime);
2666
2768
  const delta = clamp(0, sampleDelta, sampleTime - sampleDelta);
2667
- motionValue.setWithVelocity(sampleAnimation.sample(Math.max(0, sampleTime - delta)).value, sampleAnimation.sample(sampleTime).value, delta);
2769
+ const current = sampleAnimation.sample(sampleTime).value;
2770
+ /**
2771
+ * Write the estimated value to inline style so it persists
2772
+ * after cancel(), covering the async gap before the next
2773
+ * animation starts.
2774
+ */
2775
+ const { name } = this.options;
2776
+ if (element && name)
2777
+ setStyle(element, name, current);
2778
+ motionValue.setWithVelocity(sampleAnimation.sample(Math.max(0, sampleTime - delta)).value, current, delta);
2668
2779
  sampleAnimation.stop();
2669
2780
  }
2670
2781
  }
@@ -4405,19 +4516,14 @@
4405
4516
  get(name) {
4406
4517
  return this.values.get(name)?.value;
4407
4518
  }
4408
- destroy() {
4409
- for (const value of this.values.values()) {
4410
- value.onRemove();
4411
- }
4412
- }
4413
4519
  }
4414
4520
 
4415
4521
  function createEffect(addValue) {
4416
4522
  const stateCache = new WeakMap();
4417
- const subscriptions = [];
4418
4523
  return (subject, values) => {
4419
4524
  const state = stateCache.get(subject) ?? new MotionValueState();
4420
4525
  stateCache.set(subject, state);
4526
+ const subscriptions = [];
4421
4527
  for (const key in values) {
4422
4528
  const value = values[key];
4423
4529
  const remove = addValue(subject, state, key, value);
@@ -4509,8 +4615,7 @@
4509
4615
  if (!valueIsDefault) {
4510
4616
  transformIsDefault = false;
4511
4617
  const transformName = translateAlias$1[key] || key;
4512
- const valueToRender = state.latest[key];
4513
- transform += `${transformName}(${valueToRender}) `;
4618
+ transform += `${transformName}(${value}) `;
4514
4619
  }
4515
4620
  }
4516
4621
  return transformIsDefault ? "none" : transform.trim();
@@ -5218,16 +5323,23 @@
5218
5323
  }
5219
5324
  };
5220
5325
  const startAnimation = () => {
5221
- stopAnimation();
5222
5326
  const currentValue = asNumber$1(value.get());
5223
5327
  const targetValue = asNumber$1(latestValue);
5224
5328
  // Don't animate if we're already at the target
5225
5329
  if (currentValue === targetValue) {
5330
+ stopAnimation();
5226
5331
  return;
5227
5332
  }
5333
+ // Use the running animation's analytical velocity for accuracy,
5334
+ // falling back to the MotionValue's velocity for the initial animation.
5335
+ // This prevents systematic velocity loss at high frame rates (240hz+).
5336
+ const velocity = activeAnimation
5337
+ ? activeAnimation.getGeneratorVelocity()
5338
+ : value.getVelocity();
5339
+ stopAnimation();
5228
5340
  activeAnimation = new JSAnimation({
5229
5341
  keyframes: [currentValue, targetValue],
5230
- velocity: value.getVelocity(),
5342
+ velocity,
5231
5343
  // Default to spring if no type specified (matches useSpring behavior)
5232
5344
  type: "spring",
5233
5345
  restDelta: 0.001,
@@ -5236,16 +5348,19 @@
5236
5348
  onUpdate: latestSetter,
5237
5349
  });
5238
5350
  };
5351
+ // Use a stable function reference so the frame loop Set deduplicates
5352
+ // multiple calls within the same frame (e.g. rapid mouse events)
5353
+ const scheduleAnimation = () => {
5354
+ startAnimation();
5355
+ value["events"].animationStart?.notify();
5356
+ activeAnimation?.then(() => {
5357
+ value["events"].animationComplete?.notify();
5358
+ });
5359
+ };
5239
5360
  value.attach((v, set) => {
5240
5361
  latestValue = v;
5241
5362
  latestSetter = (latest) => set(parseValue(latest, unit));
5242
- frame.postRender(() => {
5243
- startAnimation();
5244
- value["events"].animationStart?.notify();
5245
- activeAnimation?.then(() => {
5246
- value["events"].animationComplete?.notify();
5247
- });
5248
- });
5363
+ frame.postRender(scheduleAnimation);
5249
5364
  }, stopAnimation);
5250
5365
  if (isMotionValue(source)) {
5251
5366
  const removeSourceOnChange = source.on("change", (v) => value.set(parseValue(v, unit)));
@@ -6541,7 +6656,7 @@
6541
6656
  applyBoxDelta(box, delta);
6542
6657
  }
6543
6658
  if (isSharedTransition && hasTransform(node.latestValues)) {
6544
- transformBox(box, node.latestValues);
6659
+ transformBox(box, node.latestValues, node.layout?.layoutBox);
6545
6660
  }
6546
6661
  }
6547
6662
  /**
@@ -6580,9 +6695,10 @@
6580
6695
  /**
6581
6696
  * Apply a transform to a box from the latest resolved motion values.
6582
6697
  */
6583
- function transformBox(box, transform) {
6584
- transformAxis(box.x, resolveAxisTranslate(transform.x, box.x), transform.scaleX, transform.scale, transform.originX);
6585
- transformAxis(box.y, resolveAxisTranslate(transform.y, box.y), transform.scaleY, transform.scale, transform.originY);
6698
+ function transformBox(box, transform, sourceBox) {
6699
+ const resolveBox = sourceBox ?? box;
6700
+ transformAxis(box.x, resolveAxisTranslate(transform.x, resolveBox.x), transform.scaleX, transform.scale, transform.originX);
6701
+ transformAxis(box.y, resolveAxisTranslate(transform.y, resolveBox.y), transform.scaleY, transform.scale, transform.originY);
6586
6702
  }
6587
6703
 
6588
6704
  function measureViewportBox(instance, transformPoint) {
@@ -8338,6 +8454,14 @@
8338
8454
  for (let i = 0; i < this.path.length; i++) {
8339
8455
  const node = this.path[i];
8340
8456
  node.shouldResetTransform = true;
8457
+ /**
8458
+ * Percentage translates resolve against layoutBox dimensions,
8459
+ * so ancestors with them must be re-measured after transform reset.
8460
+ */
8461
+ if (typeof node.latestValues.x === "string" ||
8462
+ typeof node.latestValues.y === "string") {
8463
+ node.isLayoutDirty = true;
8464
+ }
8341
8465
  node.updateScroll("snapshot");
8342
8466
  if (node.options.layoutRoot) {
8343
8467
  node.willUpdate(false);
@@ -8603,10 +8727,10 @@
8603
8727
  }
8604
8728
  if (!hasTransform(node.latestValues))
8605
8729
  continue;
8606
- transformBox(withTransforms, node.latestValues);
8730
+ transformBox(withTransforms, node.latestValues, node.layout?.layoutBox);
8607
8731
  }
8608
8732
  if (hasTransform(this.latestValues)) {
8609
- transformBox(withTransforms, this.latestValues);
8733
+ transformBox(withTransforms, this.latestValues, this.layout?.layoutBox);
8610
8734
  }
8611
8735
  return withTransforms;
8612
8736
  }
@@ -8615,15 +8739,15 @@
8615
8739
  copyBoxInto(boxWithoutTransform, box);
8616
8740
  for (let i = 0; i < this.path.length; i++) {
8617
8741
  const node = this.path[i];
8618
- if (!node.instance)
8619
- continue;
8620
8742
  if (!hasTransform(node.latestValues))
8621
8743
  continue;
8622
- hasScale(node.latestValues) && node.updateSnapshot();
8623
- const sourceBox = createBox();
8624
- const nodeBox = node.measurePageBox();
8625
- copyBoxInto(sourceBox, nodeBox);
8626
- removeBoxTransforms(boxWithoutTransform, node.latestValues, node.snapshot ? node.snapshot.layoutBox : undefined, sourceBox);
8744
+ let sourceBox;
8745
+ if (node.instance) {
8746
+ hasScale(node.latestValues) && node.updateSnapshot();
8747
+ sourceBox = createBox();
8748
+ copyBoxInto(sourceBox, node.measurePageBox());
8749
+ }
8750
+ removeBoxTransforms(boxWithoutTransform, node.latestValues, node.snapshot?.layoutBox, sourceBox);
8627
8751
  }
8628
8752
  if (hasTransform(this.latestValues)) {
8629
8753
  removeBoxTransforms(boxWithoutTransform, this.latestValues);
@@ -9944,7 +10068,7 @@
9944
10068
  class PopChildMeasure extends React__namespace.Component {
9945
10069
  getSnapshotBeforeUpdate(prevProps) {
9946
10070
  const element = this.props.childRef.current;
9947
- if (element && prevProps.isPresent && !this.props.isPresent && this.props.pop !== false) {
10071
+ if (isHTMLElement(element) && prevProps.isPresent && !this.props.isPresent && this.props.pop !== false) {
9948
10072
  const parent = element.offsetParent;
9949
10073
  const parentWidth = isHTMLElement(parent)
9950
10074
  ? parent.offsetWidth || 0
@@ -9952,9 +10076,10 @@
9952
10076
  const parentHeight = isHTMLElement(parent)
9953
10077
  ? parent.offsetHeight || 0
9954
10078
  : 0;
10079
+ const computedStyle = getComputedStyle(element);
9955
10080
  const size = this.props.sizeRef.current;
9956
- size.height = element.offsetHeight || 0;
9957
- size.width = element.offsetWidth || 0;
10081
+ size.height = parseFloat(computedStyle.height);
10082
+ size.width = parseFloat(computedStyle.width);
9958
10083
  size.top = element.offsetTop;
9959
10084
  size.left = element.offsetLeft;
9960
10085
  size.right = parentWidth - size.width - size.left;
@@ -11356,9 +11481,7 @@
11356
11481
  },
11357
11482
  };
11358
11483
  }
11359
- const addPointerInfo = (handler) => {
11360
- return (event) => isPrimaryPointer(event) && handler(event, extractEventInfo(event));
11361
- };
11484
+ const addPointerInfo = (handler) => (event) => isPrimaryPointer(event) && handler(event, extractEventInfo(event));
11362
11485
 
11363
11486
  function addPointerEvent(target, eventName, handler, options) {
11364
11487
  return addDomEvent(target, eventName, addPointerInfo(handler), options);
@@ -11395,6 +11518,12 @@
11395
11518
  * @internal
11396
11519
  */
11397
11520
  this.lastMoveEventInfo = null;
11521
+ /**
11522
+ * Raw (untransformed) event info, re-transformed each frame
11523
+ * so transformPagePoint sees the current parent matrix.
11524
+ * @internal
11525
+ */
11526
+ this.lastRawMoveEventInfo = null;
11398
11527
  /**
11399
11528
  * @internal
11400
11529
  */
@@ -11422,6 +11551,11 @@
11422
11551
  this.updatePoint = () => {
11423
11552
  if (!(this.lastMoveEvent && this.lastMoveEventInfo))
11424
11553
  return;
11554
+ // Re-transform raw point through current transformPagePoint so
11555
+ // animated parent transforms (e.g. rotation) are picked up each frame
11556
+ if (this.lastRawMoveEventInfo) {
11557
+ this.lastMoveEventInfo = transformPoint(this.lastRawMoveEventInfo, this.transformPagePoint);
11558
+ }
11425
11559
  const info = getPanInfo(this.lastMoveEventInfo, this.history);
11426
11560
  const isPanStarted = this.startEvent !== null;
11427
11561
  // Only start panning if the offset is larger than 3 pixels. If we make it
@@ -11442,6 +11576,7 @@
11442
11576
  };
11443
11577
  this.handlePointerMove = (event, info) => {
11444
11578
  this.lastMoveEvent = event;
11579
+ this.lastRawMoveEventInfo = info;
11445
11580
  this.lastMoveEventInfo = transformPoint(info, this.transformPagePoint);
11446
11581
  // Throttle mouse move event to once per frame
11447
11582
  frame.update(this.updatePoint, true);
@@ -13814,7 +13949,7 @@
13814
13949
  const { length, position } = keys[axisName];
13815
13950
  const prev = axis.current;
13816
13951
  const prevTime = info.time;
13817
- axis.current = element[`scroll${position}`];
13952
+ axis.current = Math.abs(element[`scroll${position}`]);
13818
13953
  axis.scrollLength = element[`scroll${length}`] - element[`client${length}`];
13819
13954
  axis.offset.length = 0;
13820
13955
  axis.offset[0] = 0;
@@ -15191,6 +15326,76 @@
15191
15326
  };
15192
15327
  }
15193
15328
 
15329
+ /**
15330
+ * Creates a `transformPagePoint` function that corrects pointer coordinates
15331
+ * for a parent container with CSS transforms (rotation, scale, skew).
15332
+ *
15333
+ * When dragging elements inside a transformed parent, pointer coordinates
15334
+ * need to be transformed through the inverse of the parent's transform
15335
+ * so the drag offset is in local space.
15336
+ *
15337
+ * Works with both static and continuously animating transforms.
15338
+ *
15339
+ * @example
15340
+ * ```jsx
15341
+ * function App() {
15342
+ * const ref = useRef(null)
15343
+ *
15344
+ * return (
15345
+ * <motion.div ref={ref} style={{ rotate: 90 }}>
15346
+ * <MotionConfig transformPagePoint={correctParentTransform(ref)}>
15347
+ * <motion.div drag />
15348
+ * </MotionConfig>
15349
+ * </motion.div>
15350
+ * )
15351
+ * }
15352
+ * ```
15353
+ *
15354
+ * @param parentRef - A React ref to the transformed parent element
15355
+ * @returns A transformPagePoint function for use with MotionConfig
15356
+ *
15357
+ * @public
15358
+ */
15359
+ function correctParentTransform(parentRef) {
15360
+ return (point) => {
15361
+ const parent = parentRef.current;
15362
+ if (!parent)
15363
+ return point;
15364
+ const inv = getInverseMatrix(parent);
15365
+ if (!inv)
15366
+ return point;
15367
+ // Get center of rotation in page space
15368
+ const rect = parent.getBoundingClientRect();
15369
+ const cx = rect.left + window.scrollX + rect.width / 2;
15370
+ const cy = rect.top + window.scrollY + rect.height / 2;
15371
+ // Transform (point - center) through inverse, then add center back
15372
+ const dx = point.x - cx;
15373
+ const dy = point.y - cy;
15374
+ return {
15375
+ x: cx + inv.a * dx + inv.c * dy,
15376
+ y: cy + inv.b * dx + inv.d * dy,
15377
+ };
15378
+ };
15379
+ }
15380
+ function getInverseMatrix(element) {
15381
+ const { transform } = getComputedStyle(element);
15382
+ if (!transform || transform === "none")
15383
+ return null;
15384
+ const match = transform.match(/^matrix3d\((.*)\)$/u) ||
15385
+ transform.match(/^matrix\((.*)\)$/u);
15386
+ if (!match)
15387
+ return null;
15388
+ const v = match[1].split(",").map(Number);
15389
+ const is3d = transform.startsWith("matrix3d");
15390
+ const a = v[0], b = v[1];
15391
+ const c = is3d ? v[4] : v[2];
15392
+ const d = is3d ? v[5] : v[3];
15393
+ const det = a * d - b * c;
15394
+ if (Math.abs(det) < 1e-10)
15395
+ return null;
15396
+ return { a: d / det, b: -b / det, c: -c / det, d: a / det };
15397
+ }
15398
+
15194
15399
  const appearAnimationStore = new Map();
15195
15400
  const appearComplete = new Map();
15196
15401
 
@@ -15625,6 +15830,7 @@
15625
15830
  exports.copyBoxInto = copyBoxInto;
15626
15831
  exports.correctBorderRadius = correctBorderRadius;
15627
15832
  exports.correctBoxShadow = correctBoxShadow;
15833
+ exports.correctParentTransform = correctParentTransform;
15628
15834
  exports.createAnimationState = createAnimationState;
15629
15835
  exports.createAxis = createAxis;
15630
15836
  exports.createAxisDelta = createAxisDelta;