motion 13.4.6 → 13.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,8 +40,7 @@
40
40
 
41
41
  exports.warning = () => { };
42
42
  exports.invariant = () => { };
43
- if (typeof process !== "undefined" &&
44
- process.env?.NODE_ENV !== "production") {
43
+ if (typeof process !== "undefined" && "development" !== "production") {
45
44
  exports.warning = (check, message, errorCode) => {
46
45
  if (!check && typeof console !== "undefined") {
47
46
  console.warn(formatErrorMessage(message, errorCode));
@@ -310,170 +309,15 @@
310
309
  return definition;
311
310
  };
312
311
 
313
- const stepsOrder = [
314
- "setup", // Compute
315
- "read", // Read
316
- "resolveKeyframes", // Write/Read/Write/Read
317
- "preUpdate", // Compute
318
- "update", // Compute
319
- "preRender", // Compute
320
- "render", // Write
321
- "postRender", // Compute
322
- ];
323
-
324
- function createRenderStep(runNextFrame) {
325
- /**
326
- * We create and reuse two queues, one to queue jobs for the current frame
327
- * and one for the next. We reuse to avoid triggering GC after x frames.
328
- */
329
- let thisFrame = new Set();
330
- let nextFrame = new Set();
331
- /**
332
- * Track whether we're currently processing jobs in this step. This way
333
- * we can decide whether to schedule new jobs for this frame or next.
334
- */
335
- let isProcessing = false;
336
- let flushNextFrame = false;
337
- /**
338
- * A set of processes which were marked keepAlive when scheduled.
339
- * A keepAlive process is always also held by a frame queue until
340
- * it's cancelled, so a Set has the same lifetime semantics as a
341
- * WeakSet here while being considerably faster to query every frame.
342
- */
343
- const toKeepAlive = new Set();
344
- let latestFrameData = {
345
- delta: 0.0,
346
- timestamp: 0.0,
347
- isProcessing: false,
348
- };
349
- function triggerCallback(callback) {
350
- if (toKeepAlive.has(callback)) {
351
- nextFrame.add(callback);
352
- runNextFrame();
353
- }
354
- callback(latestFrameData);
355
- }
356
- const step = {
357
- /**
358
- * Schedule a process to run on the next frame.
359
- */
360
- schedule: (callback, keepAlive = false, immediate = false) => {
361
- const addToCurrentFrame = immediate && isProcessing;
362
- const queue = addToCurrentFrame ? thisFrame : nextFrame;
363
- if (keepAlive)
364
- toKeepAlive.add(callback);
365
- queue.add(callback);
366
- return callback;
367
- },
368
- /**
369
- * Cancel the provided callback from running on the next frame.
370
- */
371
- cancel: (callback) => {
372
- nextFrame.delete(callback);
373
- toKeepAlive.delete(callback);
374
- },
375
- /**
376
- * Execute all schedule callbacks.
377
- */
378
- process: (frameData) => {
379
- latestFrameData = frameData;
380
- /**
381
- * If we're already processing we've probably been triggered by a flushSync
382
- * inside an existing process. Instead of executing, mark flushNextFrame
383
- * as true and ensure we flush the following frame at the end of this one.
384
- */
385
- if (isProcessing) {
386
- flushNextFrame = true;
387
- return;
388
- }
389
- isProcessing = true;
390
- // Swap this frame and the next to avoid GC
391
- const prevFrame = thisFrame;
392
- thisFrame = nextFrame;
393
- nextFrame = prevFrame;
394
- // Execute this frame
395
- thisFrame.forEach(triggerCallback);
396
- // Clear the frame so no callbacks remain. This is to avoid
397
- // memory leaks should this render step not run for a while.
398
- thisFrame.clear();
399
- isProcessing = false;
400
- if (flushNextFrame) {
401
- flushNextFrame = false;
402
- step.process(frameData);
403
- }
404
- },
405
- };
406
- return step;
407
- }
408
-
409
- const maxElapsed$1 = 40;
410
- function createRenderBatcher(scheduleNextBatch, allowKeepAlive) {
411
- let runNextFrame = false;
412
- let useDefaultElapsed = true;
413
- const state = {
414
- delta: 0.0,
415
- timestamp: 0.0,
416
- isProcessing: false,
417
- };
418
- const flagRunNextFrame = () => (runNextFrame = true);
419
- const steps = stepsOrder.reduce((acc, key) => {
420
- acc[key] = createRenderStep(flagRunNextFrame);
421
- return acc;
422
- }, {});
423
- const { setup, read, resolveKeyframes, preUpdate, update, preRender, render, postRender, } = steps;
424
- const processBatch = () => {
425
- const useManualTiming = MotionGlobalConfig.useManualTiming;
426
- const timestamp = useManualTiming
427
- ? state.timestamp
428
- : performance.now();
429
- runNextFrame = false;
430
- if (!useManualTiming) {
431
- state.delta = useDefaultElapsed
432
- ? 1000 / 60
433
- : Math.max(Math.min(timestamp - state.timestamp, maxElapsed$1), 1);
434
- }
435
- state.timestamp = timestamp;
436
- state.isProcessing = true;
437
- // Unrolled render loop for better per-frame performance
438
- setup.process(state);
439
- read.process(state);
440
- resolveKeyframes.process(state);
441
- preUpdate.process(state);
442
- update.process(state);
443
- preRender.process(state);
444
- render.process(state);
445
- postRender.process(state);
446
- state.isProcessing = false;
447
- if (runNextFrame && allowKeepAlive) {
448
- useDefaultElapsed = false;
449
- scheduleNextBatch(processBatch);
450
- }
451
- };
452
- const wake = () => {
453
- runNextFrame = true;
454
- useDefaultElapsed = true;
455
- if (!state.isProcessing) {
456
- scheduleNextBatch(processBatch);
457
- }
458
- };
459
- const schedule = stepsOrder.reduce((acc, key) => {
460
- const step = steps[key];
461
- acc[key] = (process, keepAlive = false, immediate = false) => {
462
- if (!runNextFrame)
463
- wake();
464
- return step.schedule(process, keepAlive, immediate);
465
- };
466
- return acc;
467
- }, {});
468
- const cancel = (process) => {
469
- for (let i = 0; i < stepsOrder.length; i++) {
470
- steps[stepsOrder[i]].cancel(process);
471
- }
472
- };
473
- return { schedule, cancel, state, steps };
474
- }
475
-
476
- const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true);
312
+ /**
313
+ * The frameloop's state, kept apart from the frameloop itself so that
314
+ * reading it doesn't bundle the scheduler.
315
+ */
316
+ const frameData = {
317
+ delta: 0.0,
318
+ timestamp: 0.0,
319
+ isProcessing: false,
320
+ };
477
321
 
478
322
  let now;
479
323
  function clearTime() {
@@ -1057,6 +901,170 @@
1057
901
  return mixer(from, to);
1058
902
  }
1059
903
 
904
+ const stepsOrder = [
905
+ "setup", // Compute
906
+ "read", // Read
907
+ "resolveKeyframes", // Write/Read/Write/Read
908
+ "preUpdate", // Compute
909
+ "update", // Compute
910
+ "preRender", // Compute
911
+ "render", // Write
912
+ "postRender", // Compute
913
+ ];
914
+
915
+ function createRenderStep(runNextFrame) {
916
+ /**
917
+ * We create and reuse two queues, one to queue jobs for the current frame
918
+ * and one for the next. We reuse to avoid triggering GC after x frames.
919
+ */
920
+ let thisFrame = new Set();
921
+ let nextFrame = new Set();
922
+ /**
923
+ * Track whether we're currently processing jobs in this step. This way
924
+ * we can decide whether to schedule new jobs for this frame or next.
925
+ */
926
+ let isProcessing = false;
927
+ let flushNextFrame = false;
928
+ /**
929
+ * A set of processes which were marked keepAlive when scheduled.
930
+ * A keepAlive process is always also held by a frame queue until
931
+ * it's cancelled, so a Set has the same lifetime semantics as a
932
+ * WeakSet here while being considerably faster to query every frame.
933
+ */
934
+ const toKeepAlive = new Set();
935
+ let latestFrameData = {
936
+ delta: 0.0,
937
+ timestamp: 0.0,
938
+ isProcessing: false,
939
+ };
940
+ function triggerCallback(callback) {
941
+ if (toKeepAlive.has(callback)) {
942
+ nextFrame.add(callback);
943
+ runNextFrame();
944
+ }
945
+ callback(latestFrameData);
946
+ }
947
+ const step = {
948
+ /**
949
+ * Schedule a process to run on the next frame.
950
+ */
951
+ schedule: (callback, keepAlive = false, immediate = false) => {
952
+ const addToCurrentFrame = immediate && isProcessing;
953
+ const queue = addToCurrentFrame ? thisFrame : nextFrame;
954
+ if (keepAlive)
955
+ toKeepAlive.add(callback);
956
+ queue.add(callback);
957
+ return callback;
958
+ },
959
+ /**
960
+ * Cancel the provided callback from running on the next frame.
961
+ */
962
+ cancel: (callback) => {
963
+ nextFrame.delete(callback);
964
+ toKeepAlive.delete(callback);
965
+ },
966
+ /**
967
+ * Execute all schedule callbacks.
968
+ */
969
+ process: (frameData) => {
970
+ latestFrameData = frameData;
971
+ /**
972
+ * If we're already processing we've probably been triggered by a flushSync
973
+ * inside an existing process. Instead of executing, mark flushNextFrame
974
+ * as true and ensure we flush the following frame at the end of this one.
975
+ */
976
+ if (isProcessing) {
977
+ flushNextFrame = true;
978
+ return;
979
+ }
980
+ isProcessing = true;
981
+ // Swap this frame and the next to avoid GC
982
+ const prevFrame = thisFrame;
983
+ thisFrame = nextFrame;
984
+ nextFrame = prevFrame;
985
+ // Execute this frame
986
+ thisFrame.forEach(triggerCallback);
987
+ // Clear the frame so no callbacks remain. This is to avoid
988
+ // memory leaks should this render step not run for a while.
989
+ thisFrame.clear();
990
+ isProcessing = false;
991
+ if (flushNextFrame) {
992
+ flushNextFrame = false;
993
+ step.process(frameData);
994
+ }
995
+ },
996
+ };
997
+ return step;
998
+ }
999
+
1000
+ const maxElapsed$1 = 40;
1001
+ function createRenderBatcher(scheduleNextBatch, allowKeepAlive, state = {
1002
+ delta: 0.0,
1003
+ timestamp: 0.0,
1004
+ isProcessing: false,
1005
+ }) {
1006
+ let runNextFrame = false;
1007
+ let useDefaultElapsed = true;
1008
+ const flagRunNextFrame = () => (runNextFrame = true);
1009
+ const steps = stepsOrder.reduce((acc, key) => {
1010
+ acc[key] = createRenderStep(flagRunNextFrame);
1011
+ return acc;
1012
+ }, {});
1013
+ const { setup, read, resolveKeyframes, preUpdate, update, preRender, render, postRender, } = steps;
1014
+ const processBatch = () => {
1015
+ const useManualTiming = MotionGlobalConfig.useManualTiming;
1016
+ const timestamp = useManualTiming
1017
+ ? state.timestamp
1018
+ : performance.now();
1019
+ runNextFrame = false;
1020
+ if (!useManualTiming) {
1021
+ state.delta = useDefaultElapsed
1022
+ ? 1000 / 60
1023
+ : Math.max(Math.min(timestamp - state.timestamp, maxElapsed$1), 1);
1024
+ }
1025
+ state.timestamp = timestamp;
1026
+ state.isProcessing = true;
1027
+ // Unrolled render loop for better per-frame performance
1028
+ setup.process(state);
1029
+ read.process(state);
1030
+ resolveKeyframes.process(state);
1031
+ preUpdate.process(state);
1032
+ update.process(state);
1033
+ preRender.process(state);
1034
+ render.process(state);
1035
+ postRender.process(state);
1036
+ state.isProcessing = false;
1037
+ if (runNextFrame && allowKeepAlive) {
1038
+ useDefaultElapsed = false;
1039
+ scheduleNextBatch(processBatch);
1040
+ }
1041
+ };
1042
+ const wake = () => {
1043
+ runNextFrame = true;
1044
+ useDefaultElapsed = true;
1045
+ if (!state.isProcessing) {
1046
+ scheduleNextBatch(processBatch);
1047
+ }
1048
+ };
1049
+ const schedule = stepsOrder.reduce((acc, key) => {
1050
+ const step = steps[key];
1051
+ acc[key] = (process, keepAlive = false, immediate = false) => {
1052
+ if (!runNextFrame)
1053
+ wake();
1054
+ return step.schedule(process, keepAlive, immediate);
1055
+ };
1056
+ return acc;
1057
+ }, {});
1058
+ const cancel = (process) => {
1059
+ for (let i = 0; i < stepsOrder.length; i++) {
1060
+ steps[stepsOrder[i]].cancel(process);
1061
+ }
1062
+ };
1063
+ return { schedule, cancel, state, steps };
1064
+ }
1065
+
1066
+ const { schedule: frame, cancel: cancelFrame, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true, frameData);
1067
+
1060
1068
  const frameloopDriver = (update) => {
1061
1069
  const passTimestamp = ({ timestamp }) => update(timestamp);
1062
1070
  return {
@@ -1135,8 +1143,37 @@
1135
1143
  minDuration: 0.01, // in seconds
1136
1144
  maxDuration: 10.0, // in seconds
1137
1145
  minDamping: 0.05,
1138
- maxDamping: 1,
1139
1146
  };
1147
+ /**
1148
+ * Maps bounce to a damping ratio as SwiftUI does: 0 is critically damped, and
1149
+ * it approaches undamped towards 1 and infinitely overdamped towards -1. Both
1150
+ * ends are limited so the spring still moves and settles.
1151
+ */
1152
+ const bounceToDampingRatio = (bounce) => bounce < 0
1153
+ ? 1 / Math.max(1 + bounce, springDefaults.minDamping)
1154
+ : Math.max(1 - bounce, springDefaults.minDamping);
1155
+ /**
1156
+ * Scales an overdamped spring's undamped frequency so that, at its defining
1157
+ * time, it has as far left to go as the critically damped spring that's
1158
+ * criticalFreqTime radians in by then. A negative bounce then changes the
1159
+ * shape of the curve, not when it arrives. 1 at a damping ratio of 1, so
1160
+ * there's no jump at a bounce of 0.
1161
+ *
1162
+ * Solved by Newton on the remaining distance (times fast - slow), which is
1163
+ * convex from criticalFreqTime onwards. criticalFreqTime is always below the
1164
+ * answer, so it converges from below without overshooting.
1165
+ */
1166
+ function overdampedFreqScale(dampingRatio, criticalFreqTime) {
1167
+ if (!(dampingRatio > 1))
1168
+ return 1;
1169
+ const root = Math.sqrt(dampingRatio * dampingRatio - 1);
1170
+ const slow = dampingRatio - root;
1171
+ const fast = dampingRatio + root;
1172
+ const target = 2 * root * Math.exp(-criticalFreqTime) * (1 + criticalFreqTime);
1173
+ return (approximateRoot((freqTime) => fast * Math.exp(-slow * freqTime) -
1174
+ slow * Math.exp(-fast * freqTime) -
1175
+ target, (freqTime) => Math.exp(-fast * freqTime) - Math.exp(-slow * freqTime), criticalFreqTime) / criticalFreqTime);
1176
+ }
1140
1177
  function calcAngularFreq(undampedFreq, dampingRatio) {
1141
1178
  return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
1142
1179
  }
@@ -1161,11 +1198,7 @@
1161
1198
  let envelope;
1162
1199
  let derivative;
1163
1200
  exports.warning(duration <= secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less", "spring-duration-limit");
1164
- let dampingRatio = 1 - bounce;
1165
- /**
1166
- * Restrict dampingRatio and duration to within acceptable ranges.
1167
- */
1168
- dampingRatio = clamp(springDefaults.minDamping, springDefaults.maxDamping, dampingRatio);
1201
+ const dampingRatio = bounceToDampingRatio(bounce);
1169
1202
  duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, millisecondsToSeconds(duration));
1170
1203
  if (dampingRatio < 1) {
1171
1204
  /**
@@ -1194,7 +1227,8 @@
1194
1227
  }
1195
1228
  else {
1196
1229
  /**
1197
- * Critically-damped spring
1230
+ * Critically-damped spring. Overdamped springs reuse this root,
1231
+ * scaled below.
1198
1232
  */
1199
1233
  envelope = (undampedFreq) => {
1200
1234
  const a = Math.exp(-undampedFreq * duration);
@@ -1208,7 +1242,9 @@
1208
1242
  };
1209
1243
  }
1210
1244
  const initialGuess = 5 / duration;
1211
- const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
1245
+ const criticalFreq = approximateRoot(envelope, derivative, initialGuess);
1246
+ const undampedFreq = criticalFreq *
1247
+ overdampedFreqScale(dampingRatio, criticalFreq * duration);
1212
1248
  const stiffness = undampedFreq * undampedFreq;
1213
1249
  return {
1214
1250
  stiffness,
@@ -1258,12 +1294,12 @@
1258
1294
  };
1259
1295
  if (springOptions.isTimeDefined) {
1260
1296
  if (options.visualDuration) {
1261
- const root = (2 * Math.PI) / (options.visualDuration * 1.2);
1297
+ const dampingRatio = bounceToDampingRatio(options.bounce || 0);
1298
+ const root = ((2 * Math.PI) / (options.visualDuration * 1.2)) *
1299
+ overdampedFreqScale(dampingRatio, (2 * Math.PI) / 1.2);
1262
1300
  springOptions.stiffness = root * root;
1263
1301
  springOptions.damping =
1264
- 2 *
1265
- clamp(0.05, 1, 1 - (options.bounce || 0)) *
1266
- Math.sqrt(springOptions.stiffness);
1302
+ 2 * dampingRatio * Math.sqrt(springOptions.stiffness);
1267
1303
  }
1268
1304
  else {
1269
1305
  Object.assign(springOptions, findSpring(springOptions));
@@ -1398,31 +1434,32 @@
1398
1434
  (undampedAngularFreq * c.C * t - s.velocity);
1399
1435
  }
1400
1436
  else {
1401
- // Overdamped spring
1437
+ /**
1438
+ * Overdamped spring: the sum of a slow and a fast decaying
1439
+ * exponential, so no term can overflow however heavily it's damped.
1440
+ */
1402
1441
  const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
1403
- resolveSpring = (t) => {
1404
- const envelope = Math.exp(-decay * t);
1405
- // When performing sinh or cosh values can hit Infinity so we cap them here
1406
- const freqForT = Math.min(dampedAngularFreq * t, 300);
1407
- return (s.target -
1408
- (envelope *
1409
- ((s.velocity + decay * s.delta) * Math.sinh(freqForT) +
1410
- dampedAngularFreq * s.delta * Math.cosh(freqForT))) /
1411
- dampedAngularFreq);
1412
- };
1413
- // Analytical derivative of overdamped spring (px/ms)
1414
- const c = { P: 0, sinh: 0, cosh: 0 };
1442
+ const slow = decay - dampedAngularFreq;
1443
+ const fast = decay + dampedAngularFreq;
1444
+ /**
1445
+ * Physics-defined springs keep a non-physical limit: once
1446
+ * dampedAngularFreq * t passes 300, both terms decay at the damping
1447
+ * rate. Heavily overdamped drag springs (dragElastic: 0,
1448
+ * dragTransition) rely on this to settle almost at once rather than
1449
+ * creep at stiffness / damping. Negative bounce needs the exact
1450
+ * curve, so time-defined springs are exempt.
1451
+ */
1452
+ const limit = isTimeDefined ? Infinity : 300 / dampedAngularFreq;
1453
+ const decayAt = (rate, t) => Math.exp(t > limit ? -rate * limit - decay * (t - limit) : -rate * t);
1454
+ const c = { S: 0, F: 0 };
1415
1455
  update = () => {
1416
- c.P = (s.velocity + decay * s.delta) / dampedAngularFreq;
1417
- c.sinh = decay * c.P - s.delta * dampedAngularFreq;
1418
- c.cosh = decay * s.delta - c.P * dampedAngularFreq;
1419
- };
1420
- resolveVelocity = (t) => {
1421
- const envelope = Math.exp(-decay * t);
1422
- const freqForT = Math.min(dampedAngularFreq * t, 300);
1423
- return (envelope *
1424
- (c.sinh * Math.sinh(freqForT) + c.cosh * Math.cosh(freqForT)));
1456
+ const P = (s.velocity + decay * s.delta) / dampedAngularFreq;
1457
+ c.S = (s.delta + P) / 2;
1458
+ c.F = (s.delta - P) / 2;
1425
1459
  };
1460
+ resolveSpring = (t) => s.target - c.S * decayAt(slow, t) - c.F * decayAt(fast, t);
1461
+ // Analytical derivative of overdamped spring (px/ms)
1462
+ resolveVelocity = (t) => slow * c.S * decayAt(slow, t) + fast * c.F * decayAt(fast, t);
1426
1463
  }
1427
1464
  update();
1428
1465
  const calculatedDuration = isResolvedFromDuration ? duration || null : null;
@@ -3000,17 +3037,17 @@
3000
3037
  /**
3001
3038
  * Attaches a timeline to the animation, for instance the `ScrollTimeline`.
3002
3039
  */
3003
- attachTimeline({ timeline, rangeStart, rangeEnd, observe, }) {
3040
+ attachTimeline({ timeline, onAttach, observe,
3041
+ // Deprecated rangeStart/rangeEnd from framer-motion 13.0–13.4. Removed in v14.
3042
+ ...range }) {
3004
3043
  if (this.allowFlatten) {
3005
3044
  this.animation.effect?.updateTiming({ easing: "linear" });
3006
3045
  }
3007
3046
  this.animation.onfinish = null;
3008
3047
  if (timeline && supportsScrollTimeline()) {
3009
3048
  this.animation.timeline = timeline;
3010
- if (rangeStart)
3011
- this.animation.rangeStart = rangeStart;
3012
- if (rangeEnd)
3013
- this.animation.rangeEnd = rangeEnd;
3049
+ Object.assign(this.animation, range);
3050
+ onAttach?.(this.animation);
3014
3051
  return noop;
3015
3052
  }
3016
3053
  else {
@@ -4347,36 +4384,29 @@
4347
4384
  return state;
4348
4385
  }
4349
4386
  function resolveVariantFromProps(props, definition, custom, visualElement) {
4387
+ const resolveFunction = (def) => typeof def === "function"
4388
+ ? def(custom !== undefined ? custom : props.custom, ...getValueState(visualElement))
4389
+ : def;
4350
4390
  /**
4351
- * If the variant definition is a function, resolve.
4352
- */
4353
- if (typeof definition === "function") {
4354
- const [current, velocity] = getValueState(visualElement);
4355
- definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
4356
- }
4357
- /**
4358
- * If the variant definition is a variant label, or
4359
- * the function returned a variant label, resolve.
4391
+ * Resolve a function, then a variant label (which the function may have
4392
+ * returned), then a function again as the label may point to one. The
4393
+ * final function can only return a target object.
4360
4394
  */
4395
+ definition = resolveFunction(definition);
4361
4396
  if (typeof definition === "string") {
4362
4397
  definition = props.variants && props.variants[definition];
4363
4398
  }
4364
- /**
4365
- * At this point we've resolved both functions and variant labels,
4366
- * but the resolved variant label might itself have been a function.
4367
- * If so, resolve. This can only have returned a valid target object.
4368
- */
4369
- if (typeof definition === "function") {
4370
- const [current, velocity] = getValueState(visualElement);
4371
- definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
4372
- }
4373
- return definition;
4399
+ return resolveFunction(definition);
4374
4400
  }
4375
4401
 
4376
4402
  function resolveVariant(visualElement, definition, custom) {
4377
- const props = visualElement.getProps();
4378
- return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, visualElement);
4403
+ return resolveVariantFromProps(visualElement.getProps(), definition, custom, visualElement);
4379
4404
  }
4405
+ /**
4406
+ * Exit variants resolve with AnimatePresence's custom, every other type
4407
+ * with the element's own.
4408
+ */
4409
+ const getTypeCustom = (visualElement, type) => type === "exit" ? visualElement.presenceContext?.custom : undefined;
4380
4410
 
4381
4411
  const positionalKeys = new Set([
4382
4412
  "width",
@@ -4459,7 +4489,7 @@
4459
4489
  * had been set to true in the meantime.
4460
4490
  */
4461
4491
  function shouldBlockAnimation({ protectedKeys, needsAnimating }, key) {
4462
- const shouldBlock = protectedKeys.hasOwnProperty(key) && needsAnimating[key] !== true;
4492
+ const shouldBlock = key in protectedKeys && !needsAnimating[key];
4463
4493
  needsAnimating[key] = false;
4464
4494
  return shouldBlock;
4465
4495
  }
@@ -4474,9 +4504,7 @@
4474
4504
  if (transitionOverride)
4475
4505
  transition = transitionOverride;
4476
4506
  const animations = [];
4477
- const animationTypeState = type &&
4478
- visualElement.animationState &&
4479
- visualElement.animationState.getState()[type];
4507
+ const animationTypeState = type && visualElement.animationState?.getState()[type];
4480
4508
  const path = transition?.path;
4481
4509
  if (path) {
4482
4510
  // path mutates `target` to claim x/y; loop below skips them.
@@ -4550,75 +4578,46 @@
4550
4578
  }
4551
4579
 
4552
4580
  function animateVariant(visualElement, variant, options = {}) {
4553
- const resolved = resolveVariant(visualElement, variant, options.type === "exit"
4554
- ? visualElement.presenceContext?.custom
4555
- : undefined);
4556
- let { transition = visualElement.getDefaultTransition() || {} } = resolved || {};
4557
- if (options.transitionOverride) {
4558
- transition = options.transitionOverride;
4559
- }
4560
- /**
4561
- * If we have a variant, create a callback that runs it as an animation.
4562
- * Otherwise, we resolve a Promise immediately for a composable no-op.
4563
- */
4564
- const getAnimation = resolved
4565
- ? () => Promise.all(animateTarget(visualElement, resolved, options))
4566
- : () => Promise.resolve();
4567
- /**
4568
- * If we have children, create a callback that runs all their animations.
4569
- * Otherwise, we resolve a Promise immediately for a composable no-op.
4570
- */
4571
- const getChildAnimations = visualElement.variantChildren && visualElement.variantChildren.size
4572
- ? (forwardDelay = 0) => {
4573
- const { delayChildren = 0, staggerChildren, staggerDirection, } = transition;
4574
- return animateChildren(visualElement, variant, forwardDelay, delayChildren, staggerChildren, staggerDirection, options);
4575
- }
4576
- : () => Promise.resolve();
4577
- /**
4578
- * If the transition explicitly defines a "when" option, we need to resolve either
4579
- * this animation or all children animations before playing the other.
4580
- */
4581
- const { when } = transition;
4582
- if (when) {
4583
- const [first, last] = when === "beforeChildren"
4584
- ? [getAnimation, getChildAnimations]
4585
- : [getChildAnimations, getAnimation];
4586
- return first().then(() => last());
4587
- }
4588
- else {
4589
- return Promise.all([getAnimation(), getChildAnimations(options.delay)]);
4590
- }
4591
- }
4592
- function animateChildren(visualElement, variant, delay = 0, delayChildren = 0, staggerChildren = 0, staggerDirection = 1, options) {
4593
- const animations = [];
4594
- for (const child of visualElement.variantChildren) {
4595
- child.notify("AnimationStart", variant);
4596
- animations.push(animateVariant(child, variant, {
4597
- ...options,
4598
- delay: delay +
4599
- (typeof delayChildren === "function" ? 0 : delayChildren) +
4600
- calcChildStagger(visualElement.variantChildren, child, delayChildren, staggerChildren, staggerDirection),
4601
- }).then(() => child.notify("AnimationComplete", variant)));
4602
- }
4603
- return Promise.all(animations);
4581
+ const resolved = resolveVariant(visualElement, variant, getTypeCustom(visualElement, options.type));
4582
+ const transition = options.transitionOverride ||
4583
+ (resolved?.transition ?? (visualElement.getDefaultTransition() || {}));
4584
+ const getAnimation = () => Promise.all(resolved ? animateTarget(visualElement, resolved, options) : []);
4585
+ const getChildAnimations = (delay = 0) => {
4586
+ const { variantChildren } = visualElement;
4587
+ const { delayChildren = 0, staggerChildren, staggerDirection, } = transition;
4588
+ const animations = [];
4589
+ variantChildren?.forEach((child) => {
4590
+ child.notify("AnimationStart", variant);
4591
+ animations.push(animateVariant(child, variant, {
4592
+ ...options,
4593
+ delay: delay +
4594
+ (typeof delayChildren === "function"
4595
+ ? 0
4596
+ : delayChildren) +
4597
+ calcChildStagger(variantChildren, child, delayChildren, staggerChildren, staggerDirection),
4598
+ }).then(() => child.notify("AnimationComplete", variant)));
4599
+ });
4600
+ return Promise.all(animations);
4601
+ };
4602
+ /**
4603
+ * when: "beforeChildren" | "afterChildren" runs this element's animation
4604
+ * and its children's in sequence.
4605
+ */
4606
+ const { when } = transition;
4607
+ return when
4608
+ ? when === "beforeChildren"
4609
+ ? getAnimation().then(() => getChildAnimations())
4610
+ : getChildAnimations().then(getAnimation)
4611
+ : Promise.all([getAnimation(), getChildAnimations(options.delay)]);
4604
4612
  }
4605
4613
 
4606
4614
  function animateVisualElement(visualElement, definition, options = {}) {
4607
4615
  visualElement.notify("AnimationStart", definition);
4608
- let animation;
4609
- if (Array.isArray(definition)) {
4610
- const animations = definition.map((variant) => animateVariant(visualElement, variant, options));
4611
- animation = Promise.all(animations);
4612
- }
4613
- else if (typeof definition === "string") {
4614
- animation = animateVariant(visualElement, definition, options);
4615
- }
4616
- else {
4617
- const resolvedDefinition = typeof definition === "function"
4618
- ? resolveVariant(visualElement, definition, options.custom)
4619
- : definition;
4620
- animation = Promise.all(animateTarget(visualElement, resolvedDefinition, options));
4621
- }
4616
+ const animation = Array.isArray(definition)
4617
+ ? Promise.all(definition.map((variant) => animateVariant(visualElement, variant, options)))
4618
+ : typeof definition === "string"
4619
+ ? animateVariant(visualElement, definition, options)
4620
+ : Promise.all(animateTarget(visualElement, resolveVariant(visualElement, definition, options.custom), options));
4622
4621
  return animation.then(() => {
4623
4622
  visualElement.notify("AnimationComplete", definition);
4624
4623
  });
@@ -5144,7 +5143,12 @@
5144
5143
  const onRemove = () => {
5145
5144
  cancelOnChange();
5146
5145
  render && !computed && this.cancel(render);
5147
- this.values.delete(name);
5146
+ /**
5147
+ * A later set() of the same name has already removed this
5148
+ * entry, so leave the one that replaced it.
5149
+ */
5150
+ this.values.get(name)?.onRemove === onRemove &&
5151
+ this.values.delete(name);
5148
5152
  };
5149
5153
  this.values.set(name, {
5150
5154
  value,
@@ -5623,24 +5627,35 @@
5623
5627
  else if (key.startsWith("attr")) {
5624
5628
  return addAttrValue(element, state, key, value, convertAttrKey(key));
5625
5629
  }
5626
- const handler = isCSSVar(key) || key in element.style ? addStyleValue : addAttrValue;
5630
+ /**
5631
+ * Transforms and origins aren't CSS properties (bar `x`/`y` in some
5632
+ * browsers), so check them by name rather than `key in element.style`.
5633
+ */
5634
+ const handler = transformProps.has(key) ||
5635
+ originProps.has(key) ||
5636
+ isCSSVar(key) ||
5637
+ key in element.style
5638
+ ? addStyleValue
5639
+ : addAttrValue;
5627
5640
  return handler(element, state, key, value);
5628
5641
  };
5629
5642
  /**
5630
5643
  * Reads the current value of `key` from an SVG element as the origin of
5631
5644
  * an animation, as the SVG VisualElement does: transforms start from
5632
5645
  * their defaults, CSS variables and the few CSS-only properties come from
5633
- * computed style
5634
- * and everything else is read from the attribute, dash-cased
5635
- * (`strokeWidth` -> `stroke-width`) or, failing that, as written
5636
- * (`baseFrequency`).
5646
+ * computed style, falling back to the attribute when that's empty (e.g.
5647
+ * the element isn't rendered), and everything else is read from the
5648
+ * attribute, dash-cased (`strokeWidth` -> `stroke-width`) or, failing
5649
+ * that, as written (`baseFrequency`).
5637
5650
  */
5638
5651
  const readSVGValue = (element, key) => {
5639
5652
  if (transformProps.has(key)) {
5640
5653
  return numberValueTypes[key]?.default || 0;
5641
5654
  }
5642
5655
  if (isCSSVar(key) || cssStyleProperties.includes(key)) {
5643
- return readStyleValue(element, key);
5656
+ return (readStyleValue(element, key) ||
5657
+ element.getAttribute(camelToDash(key)) ||
5658
+ 0);
5644
5659
  }
5645
5660
  key = convertAttrKey(key);
5646
5661
  return (element.getAttribute(camelToDash(key)) ??
@@ -6343,6 +6358,10 @@
6343
6358
  return typeof a === "function" ? resizeWindow(a) : resizeElement(a, b);
6344
6359
  }
6345
6360
 
6361
+ /**
6362
+ * @deprecated Kept because framer-motion 13.0–13.4 import it from
6363
+ * motion-dom with a ^ range. Removed in v14.
6364
+ */
6346
6365
  function observeTimeline(update, timeline) {
6347
6366
  let prevProgress;
6348
6367
  const onFrame = () => {
@@ -6471,8 +6490,7 @@
6471
6490
  this.options.onStop?.();
6472
6491
  };
6473
6492
  this.options = options;
6474
- replaceTransitionType(options);
6475
- this.factory = options.type || keyframes;
6493
+ this.factory = options.type;
6476
6494
  this.generator = this.factory(options);
6477
6495
  const { driver } = options;
6478
6496
  if (driver)
@@ -6573,41 +6591,15 @@
6573
6591
  }
6574
6592
  }
6575
6593
 
6594
+ const asNumber = (v) => typeof v === "number" ? v : parseFloat(v);
6595
+
6576
6596
  /**
6577
- * Create a `MotionValue` that animates to its latest value using any transition type.
6578
- * Can either be a value or track another `MotionValue`.
6579
- *
6580
- * ```jsx
6581
- * const x = motionValue(0)
6582
- * const y = followValue(x, { type: "spring", stiffness: 300 })
6583
- * // or with tween
6584
- * const z = followValue(x, { type: "tween", duration: 0.5, ease: "easeOut" })
6585
- * ```
6586
- *
6587
- * @param source - Initial value or MotionValue to track
6588
- * @param options - Animation transition options
6589
- * @returns `MotionValue`
6590
- *
6591
- * @public
6592
- */
6593
- function followValue(source, options) {
6594
- const initialValue = isMotionValue(source) ? source.get() : source;
6595
- const value = motionValue(initialValue);
6596
- attachFollow(value, source, options);
6597
- return value;
6598
- }
6599
- /**
6600
- * Attach an animation to a MotionValue that will animate whenever the value changes.
6601
- * Similar to attachSpring but supports any transition type (spring, tween, inertia, etc.)
6602
- *
6603
- * @param value - The MotionValue to animate
6604
- * @param source - Initial value or MotionValue to track
6605
- * @param options - Animation transition options
6606
- * @returns Cleanup function
6607
- *
6608
- * @public
6597
+ * Animate `value` to its latest value with the generator in
6598
+ * `options.type`, tracking `source` if it's a `MotionValue`. Takes a
6599
+ * generator rather than a type name, so spring-only callers don't
6600
+ * bundle every generator.
6609
6601
  */
6610
- function attachFollow(value, source, options = {}) {
6602
+ function follow(value, source, options) {
6611
6603
  const initialValue = value.get();
6612
6604
  let activeAnimation = null;
6613
6605
  let set;
@@ -6625,7 +6617,7 @@
6625
6617
  };
6626
6618
  value.attach((v, safeSet) => {
6627
6619
  set = safeSet;
6628
- const target = asNumber$1(v);
6620
+ const target = asNumber(v);
6629
6621
  if (activeAnimation?.state === "running") {
6630
6622
  /**
6631
6623
  * Steer the running animation rather than replacing it. This
@@ -6636,7 +6628,7 @@
6636
6628
  activeAnimation.setTarget(target, options.velocity);
6637
6629
  return;
6638
6630
  }
6639
- const current = asNumber$1(value.get());
6631
+ const current = asNumber(value.get());
6640
6632
  const velocity = activeAnimation
6641
6633
  ? activeAnimation.getGeneratorVelocity()
6642
6634
  : value.getVelocity();
@@ -6647,8 +6639,6 @@
6647
6639
  const animationOptions = {
6648
6640
  keyframes: [current, target],
6649
6641
  velocity,
6650
- // Default to spring if no type specified (matches useSpring behavior)
6651
- type: "spring",
6652
6642
  restDelta: 0.001,
6653
6643
  restSpeed: 0.01,
6654
6644
  ...options,
@@ -6690,8 +6680,47 @@
6690
6680
  function parseValue(v, unit) {
6691
6681
  return unit ? v + unit : v;
6692
6682
  }
6693
- function asNumber$1(v) {
6694
- return typeof v === "number" ? v : parseFloat(v);
6683
+
6684
+ /**
6685
+ * Create a `MotionValue` that animates to its latest value using any transition type.
6686
+ * Can either be a value or track another `MotionValue`.
6687
+ *
6688
+ * ```jsx
6689
+ * const x = motionValue(0)
6690
+ * const y = followValue(x, { type: "spring", stiffness: 300 })
6691
+ * // or with tween
6692
+ * const z = followValue(x, { type: "tween", duration: 0.5, ease: "easeOut" })
6693
+ * ```
6694
+ *
6695
+ * @param source - Initial value or MotionValue to track
6696
+ * @param options - Animation transition options
6697
+ * @returns `MotionValue`
6698
+ *
6699
+ * @public
6700
+ */
6701
+ function followValue(source, options) {
6702
+ const initialValue = isMotionValue(source) ? source.get() : source;
6703
+ const value = motionValue(initialValue);
6704
+ attachFollow(value, source, options);
6705
+ return value;
6706
+ }
6707
+ /**
6708
+ * Attach an animation to a MotionValue that will animate whenever the value changes.
6709
+ * Similar to attachSpring but supports any transition type (spring, tween, inertia, etc.)
6710
+ *
6711
+ * @param value - The MotionValue to animate
6712
+ * @param source - Initial value or MotionValue to track
6713
+ * @param options - Animation transition options
6714
+ * @returns Cleanup function
6715
+ *
6716
+ * @public
6717
+ */
6718
+ function attachFollow(value, source, options = {}) {
6719
+ // Default to spring if no type specified (matches useSpring behavior)
6720
+ const transition = { type: "spring", ...options };
6721
+ replaceTransitionType(transition);
6722
+ transition.type = transition.type || keyframes;
6723
+ return follow(value, source, transition);
6695
6724
  }
6696
6725
 
6697
6726
  function subscribeValue(inputValues, outputValue, getLatest) {
@@ -6793,7 +6822,9 @@
6793
6822
  * @public
6794
6823
  */
6795
6824
  function springValue(source, options) {
6796
- return followValue(source, { type: "spring", ...options });
6825
+ const value = motionValue(isMotionValue(source) ? source.get() : source);
6826
+ attachSpring(value, source, options);
6827
+ return value;
6797
6828
  }
6798
6829
  /**
6799
6830
  * Attach a spring animation to a MotionValue that will animate whenever the value changes.
@@ -6806,7 +6837,7 @@
6806
6837
  * @public
6807
6838
  */
6808
6839
  function attachSpring(value, source, options) {
6809
- return attachFollow(value, source, { type: "spring", ...options });
6840
+ return follow(value, source, { ...options, type: spring });
6810
6841
  }
6811
6842
 
6812
6843
  /**
@@ -8228,8 +8259,20 @@
8228
8259
  if (this.projection && !this.projection.instance) {
8229
8260
  this.projection.mount(instance);
8230
8261
  }
8231
- if (this.parent && this.isVariantNode && !this.isControllingVariants) {
8232
- this.removeFromVariantTree = this.parent.addVariantChild(this);
8262
+ /**
8263
+ * Join the closest variant node above, so its variant changes
8264
+ * propagate here, unless inherit={false} cuts the chain.
8265
+ */
8266
+ if (this.isVariantNode && !this.isControllingVariants) {
8267
+ let node = this;
8268
+ do
8269
+ node = node.props.inherit !== false && node.parent;
8270
+ while (node && !node.isVariantNode);
8271
+ if (node) {
8272
+ const { variantChildren } = node;
8273
+ variantChildren.add(this);
8274
+ this.removeFromVariantTree = () => variantChildren.delete(this);
8275
+ }
8233
8276
  }
8234
8277
  this.values.forEach((value, key) => this.bindToMotionValue(key, value));
8235
8278
  /**
@@ -8339,9 +8382,11 @@
8339
8382
  }
8340
8383
  sortNodePosition(other) {
8341
8384
  /**
8342
- * If these nodes aren't even of the same type we can't compare their depth.
8385
+ * If either node isn't mounted, or they aren't even of the same type,
8386
+ * we can't compare their document order.
8343
8387
  */
8344
8388
  if (!this.current ||
8389
+ !other.current ||
8345
8390
  !this.sortInstanceNodePosition ||
8346
8391
  this.type !== other.type) {
8347
8392
  return 0;
@@ -8447,24 +8492,6 @@
8447
8492
  getTransformPagePoint() {
8448
8493
  return this.props.transformPagePoint;
8449
8494
  }
8450
- getClosestVariantNode() {
8451
- return this.isVariantNode
8452
- ? this
8453
- : this.parent
8454
- ? this.parent.getClosestVariantNode()
8455
- : undefined;
8456
- }
8457
- /**
8458
- * Add a child visual element to our set of children.
8459
- */
8460
- addVariantChild(child) {
8461
- const closestVariantNode = this.getClosestVariantNode();
8462
- if (closestVariantNode) {
8463
- closestVariantNode.variantChildren &&
8464
- closestVariantNode.variantChildren.add(child);
8465
- return () => closestVariantNode.variantChildren.delete(child);
8466
- }
8467
- }
8468
8495
  /**
8469
8496
  * Add a motion value and bind it to this visual element.
8470
8497
  */
@@ -8476,7 +8503,15 @@
8476
8503
  this.removeValue(key);
8477
8504
  this.bindToMotionValue(key, value);
8478
8505
  this.values.set(key, value);
8479
- this.latestValues[key] = value.get();
8506
+ /**
8507
+ * An animated value with no base value is undefined until the
8508
+ * keyframe resolver reads its origin. Rendering it before then
8509
+ * writes an invalid placeholder (e.g. points="undefined") that
8510
+ * the resolver can read back from the DOM as the origin.
8511
+ */
8512
+ const latest = value.get();
8513
+ if (latest !== undefined)
8514
+ this.latestValues[key] = latest;
8480
8515
  }
8481
8516
  }
8482
8517
  /**
@@ -8504,7 +8539,9 @@
8504
8539
  }
8505
8540
  let value = this.values.get(key);
8506
8541
  if (value === undefined && defaultValue !== undefined) {
8507
- value = motionValue(defaultValue === null ? undefined : defaultValue, { owner: this });
8542
+ value = motionValue(defaultValue ?? this.getDefaultValue?.(key), {
8543
+ owner: this,
8544
+ });
8508
8545
  this.addValue(key, value);
8509
8546
  }
8510
8547
  return value;
@@ -8549,41 +8586,6 @@
8549
8586
  setBaseTarget(key, value) {
8550
8587
  this.baseTarget[key] = value;
8551
8588
  }
8552
- /**
8553
- * Find the base target for a value thats been removed from all animation
8554
- * props.
8555
- */
8556
- getBaseTarget(key) {
8557
- const { initial } = this.props;
8558
- let valueFromInitial;
8559
- if (typeof initial === "string" || typeof initial === "object") {
8560
- const variant = resolveVariantFromProps(this.props, initial, this.presenceContext?.custom);
8561
- if (variant) {
8562
- valueFromInitial = variant[key];
8563
- }
8564
- }
8565
- /**
8566
- * If this value still exists in the current initial variant, read that.
8567
- */
8568
- if (initial && valueFromInitial !== undefined) {
8569
- return valueFromInitial;
8570
- }
8571
- /**
8572
- * Alternatively, if this VisualElement config has defined a getBaseTarget
8573
- * so we can read the value from an alternative source, try that.
8574
- */
8575
- const target = this.getBaseTargetFromProps(this.props, key);
8576
- if (target !== undefined && !isMotionValue(target))
8577
- return target;
8578
- /**
8579
- * If the value was initially defined on initial, but it doesn't any more,
8580
- * return undefined. Otherwise return the value as initially read from the DOM.
8581
- */
8582
- return this.initialValues[key] !== undefined &&
8583
- valueFromInitial === undefined
8584
- ? undefined
8585
- : this.baseTarget[key];
8586
- }
8587
8589
  on(eventName, callback) {
8588
8590
  if (!this.events[eventName]) {
8589
8591
  this.events[eventName] = new SubscriptionManager();
@@ -8666,86 +8668,7 @@
8666
8668
  }
8667
8669
  }
8668
8670
 
8669
- function pixelsToPercent(pixels, axis) {
8670
- if (axis.max === axis.min)
8671
- return 0;
8672
- return (pixels / (axis.max - axis.min)) * 100;
8673
- }
8674
- /**
8675
- * We always correct borderRadius as a percentage rather than pixels to reduce paints.
8676
- * For example, if you are projecting a box that is 100px wide with a 10px borderRadius
8677
- * into a box that is 200px wide with a 20px borderRadius, that is actually a 10%
8678
- * borderRadius in both states. If we animate between the two in pixels that will trigger
8679
- * a paint each time. If we animate between the two in percentage we'll avoid a paint.
8680
- */
8681
- const correctBorderRadius = {
8682
- correct: (latest, node) => {
8683
- if (!node.target)
8684
- return latest;
8685
- /**
8686
- * If latest is a string, if it's a percentage we can return immediately as it's
8687
- * going to be stretched appropriately. Otherwise, if it's a pixel, convert it to a number.
8688
- */
8689
- if (typeof latest === "string") {
8690
- if (px.test(latest)) {
8691
- latest = parseFloat(latest);
8692
- }
8693
- else {
8694
- return latest;
8695
- }
8696
- }
8697
- /**
8698
- * If latest is a number, it's a pixel value. We use the current viewportBox to calculate that
8699
- * pixel value as a percentage of each axis
8700
- */
8701
- const x = pixelsToPercent(latest, node.target.x);
8702
- const y = pixelsToPercent(latest, node.target.y);
8703
- return `${x}% ${y}%`;
8704
- },
8705
- };
8706
-
8707
- const correctBoxShadow = {
8708
- correct: (latest, { treeScale, projectionDelta }) => {
8709
- const original = latest;
8710
- const shadow = complex.parse(latest);
8711
- // TODO: Doesn't support multiple shadows
8712
- if (shadow.length > 5)
8713
- return original;
8714
- const template = complex.createTransformer(latest);
8715
- const offset = typeof shadow[0] !== "number" ? 1 : 0;
8716
- // Calculate the overall context scale
8717
- const xScale = projectionDelta.x.scale * treeScale.x;
8718
- const yScale = projectionDelta.y.scale * treeScale.y;
8719
- shadow[0 + offset] /= xScale;
8720
- shadow[1 + offset] /= yScale;
8721
- /**
8722
- * Ideally we'd correct x and y scales individually, but because blur and
8723
- * spread apply to both we have to take a scale average and apply that instead.
8724
- * We could potentially improve the outcome of this by incorporating the ratio between
8725
- * the two scales.
8726
- */
8727
- const averageScale = mixNumber$1(xScale, yScale, 0.5);
8728
- // Blur
8729
- if (typeof shadow[2 + offset] === "number")
8730
- shadow[2 + offset] /= averageScale;
8731
- // Spread
8732
- if (typeof shadow[3 + offset] === "number")
8733
- shadow[3 + offset] /= averageScale;
8734
- return template(shadow);
8735
- },
8736
- };
8737
-
8738
- const scaleCorrectors = {
8739
- borderRadius: {
8740
- ...correctBorderRadius,
8741
- applyTo: [...cornerRadiusProps],
8742
- },
8743
- borderTopLeftRadius: correctBorderRadius,
8744
- borderTopRightRadius: correctBorderRadius,
8745
- borderBottomLeftRadius: correctBorderRadius,
8746
- borderBottomRightRadius: correctBorderRadius,
8747
- boxShadow: correctBoxShadow,
8748
- };
8671
+ const scaleCorrectors = {};
8749
8672
  function addScaleCorrector(correctors) {
8750
8673
  for (const key in correctors) {
8751
8674
  scaleCorrectors[key] = correctors[key];
@@ -8919,7 +8842,24 @@
8919
8842
  this.measureInstanceViewportBox = createBox;
8920
8843
  }
8921
8844
  getBaseTargetFromProps(props, key) {
8922
- return props[key];
8845
+ /**
8846
+ * An independent transform's base is never an attribute of the same
8847
+ * name, e.g. <rect x> is a position, not a translate. Like HTML it
8848
+ * comes from style, falling back to the transform's default.
8849
+ */
8850
+ return transformProps.has(key)
8851
+ ? super.getBaseTargetFromProps(props, key) ??
8852
+ defaultTransformValue(key)
8853
+ : props[key];
8854
+ }
8855
+ /**
8856
+ * An independent transform's origin is never read from the DOM, so it
8857
+ * starts from its latest or default value and renders on mount.
8858
+ */
8859
+ getDefaultValue(key) {
8860
+ return transformProps.has(key)
8861
+ ? this.latestValues[key] ?? defaultTransformValue(key)
8862
+ : undefined;
8923
8863
  }
8924
8864
  readValueFromInstance(instance, key) {
8925
8865
  if (transformProps.has(key)) {
@@ -8950,34 +8890,6 @@
8950
8890
  }
8951
8891
  }
8952
8892
 
8953
- const numVariantProps = variantProps.length;
8954
- /**
8955
- * Get variant context from a visual element's parent chain.
8956
- * Uses `any` type for visualElement to avoid circular dependencies.
8957
- */
8958
- function getVariantContext(visualElement) {
8959
- if (!visualElement)
8960
- return undefined;
8961
- if (!visualElement.isControllingVariants) {
8962
- const context = visualElement.parent
8963
- ? getVariantContext(visualElement.parent) || {}
8964
- : {};
8965
- if (visualElement.props.initial !== undefined) {
8966
- context.initial = visualElement.props.initial;
8967
- }
8968
- return context;
8969
- }
8970
- const context = {};
8971
- for (let i = 0; i < numVariantProps; i++) {
8972
- const name = variantProps[i];
8973
- const prop = visualElement.props[name];
8974
- if (isVariantLabel(prop) || prop === false) {
8975
- context[name] = prop;
8976
- }
8977
- }
8978
- return context;
8979
- }
8980
-
8981
8893
  function shallowCompare(next, prev) {
8982
8894
  if (!Array.isArray(prev))
8983
8895
  return false;
@@ -8992,332 +8904,211 @@
8992
8904
  }
8993
8905
 
8994
8906
  const reversePriorityOrder = [...variantPriorityOrder].reverse();
8995
- const numAnimationTypes = variantPriorityOrder.length;
8996
- function createAnimateFunction(visualElement) {
8997
- return (animations) => {
8998
- return Promise.all(animations.map(({ animation, options }) => animateVisualElement(visualElement, animation, options)));
8999
- };
9000
- }
8907
+ /**
8908
+ * Diffs the element's animation props (one layer per AnimationType, highest
8909
+ * priority wins each value) against the previous call and starts animations
8910
+ * for what changed.
8911
+ *
8912
+ * Uses `any` type for visualElement to avoid circular dependencies. It reads
8913
+ * props, parent, presenceContext, variantChildren, enteringChildren,
8914
+ * manuallyAnimateOnMount, blockInitialAnimation, getValue(),
8915
+ * getBaseTargetFromProps(), baseTarget and initialValues.
8916
+ */
9001
8917
  function createAnimationState(visualElement) {
9002
- let animate = createAnimateFunction(visualElement);
8918
+ let animate = (animations) => Promise.all(animations.map(({ animation, options }) => animateVisualElement(visualElement, animation, options)));
9003
8919
  let state = createState();
9004
8920
  let isInitialRender = true;
9005
8921
  /**
9006
- * Track whether the animation state has been reset (e.g. via StrictMode
9007
- * double-invocation or Suspense unmount/remount). On the first
9008
- * animateChanges() call after a reset we need to behave like the initial
9009
- * render for variant-inheritance checks, even though isInitialRender is
9010
- * already false.
8922
+ * After a reset (StrictMode, Suspense, AnimatePresence re-entry) the next
8923
+ * animateChanges() mounts again, but initial={false} no longer applies.
9011
8924
  */
9012
8925
  let wasReset = false;
9013
- /**
9014
- * This function will be used to reduce the animation definitions for
9015
- * each active animation type into an object of resolved values for it.
9016
- */
9017
- const buildResolvedTypeValues = (type) => (acc, definition) => {
9018
- const resolved = resolveVariant(visualElement, definition, type === "exit"
9019
- ? visualElement.presenceContext?.custom
9020
- : undefined);
9021
- if (resolved) {
9022
- const { transition, transitionEnd, ...target } = resolved;
9023
- acc = { ...acc, ...target, ...transitionEnd };
9024
- }
9025
- return acc;
9026
- };
9027
- /**
9028
- * This just allows us to inject mocked animation functions
9029
- * @internal
9030
- */
9031
- function setAnimateFunction(makeAnimator) {
9032
- animate = makeAnimator(visualElement);
9033
- }
9034
- /**
9035
- * When we receive new props, we need to:
9036
- * 1. Create a list of protected keys for each type. This is a directory of
9037
- * value keys that are currently being "handled" by types of a higher priority
9038
- * so that whenever an animation is played of a given type, these values are
9039
- * protected from being animated.
9040
- * 2. Determine if an animation type needs animating.
9041
- * 3. Determine if any values have been removed from a type and figure out
9042
- * what to animate those to.
9043
- */
9044
8926
  function animateChanges(changedActiveType) {
9045
- const { props } = visualElement;
9046
- const context = getVariantContext(visualElement.parent) || {};
9047
- /**
9048
- * A list of animations that we'll build into as we iterate through the animation
9049
- * types. This will get executed at the end of the function.
9050
- */
8927
+ const { props, parent, manuallyAnimateOnMount } = visualElement;
8928
+ const isMounting = isInitialRender || wasReset;
9051
8929
  const animations = [];
9052
8930
  /**
9053
- * Keep track of which values have been removed. Then, as we hit lower priority
9054
- * animation types, we can check if they contain removed values and animate to that.
8931
+ * Values removed from a type. A lower-priority type that defines
8932
+ * them animates them back, otherwise they fall back to a base target.
9055
8933
  */
9056
8934
  const removedKeys = new Set();
9057
8935
  /**
9058
- * A dictionary of all encountered keys. This is an object to let us build into and
9059
- * copy it without iteration. Each time we hit an animation type we set its protected
9060
- * keys - the keys its not allowed to animate - to the latest version of this object.
8936
+ * Values claimed by active, higher-priority types.
9061
8937
  */
9062
8938
  let encounteredKeys = {};
9063
8939
  /**
9064
- * If a variant has been removed at a given index, and this component is controlling
9065
- * variant animations, we want to ensure lower-priority variants are forced to animate.
8940
+ * Once a type has been deactivated, lower-priority variant labels
8941
+ * re-animate so variant children animate back too.
9066
8942
  */
9067
8943
  let removedVariantIndex = Infinity;
9068
8944
  /**
9069
- * Iterate through all animation types in reverse priority order. For each, we want to
9070
- * detect which values it's handling and whether or not they've changed (and therefore
9071
- * need to be animated). If any values have been removed, we want to detect those in
9072
- * lower priority props and flag for animation.
8945
+ * Variant labels are inherited from the closest variant-controlling
8946
+ * ancestor, unless an element on the way has inherit={false}.
9073
8947
  */
9074
- for (let i = 0; i < numAnimationTypes; i++) {
9075
- const type = reversePriorityOrder[i];
8948
+ let source = visualElement;
8949
+ do
8950
+ source = source.props.inherit !== false && source.parent;
8951
+ while (source && !source.isControllingVariants);
8952
+ reversePriorityOrder.forEach((type, i) => {
9076
8953
  const typeState = state[type];
9077
- const prop = props[type] !== undefined
9078
- ? props[type]
9079
- : context[type];
8954
+ const ownProp = props[type];
8955
+ const inheritedProp = source && source.props[type];
8956
+ const prop = ownProp !== undefined
8957
+ ? ownProp
8958
+ : isVariantLabel(inheritedProp) || inheritedProp === false
8959
+ ? inheritedProp
8960
+ : undefined;
9080
8961
  const propIsVariant = isVariantLabel(prop);
9081
- /**
9082
- * If this type has *just* changed isActive status, set activeDelta
9083
- * to that status. Otherwise set to null.
9084
- */
9085
8962
  const activeDelta = type === changedActiveType ? typeState.isActive : null;
9086
8963
  if (activeDelta === false)
9087
8964
  removedVariantIndex = i;
9088
8965
  /**
9089
- * If this prop is an inherited variant, rather than been set directly on the
9090
- * component itself, we want to make sure we allow the parent to trigger animations.
9091
- *
9092
- * TODO: Can probably change this to a !isControllingVariants check
9093
- */
9094
- let isInherited = prop === context[type] &&
9095
- prop !== props[type] &&
9096
- propIsVariant;
9097
- if (isInherited &&
9098
- (isInitialRender || wasReset) &&
9099
- visualElement.manuallyAnimateOnMount) {
9100
- isInherited = false;
9101
- }
9102
- /**
9103
- * Set all encountered keys so far as the protected keys for this type. This will
9104
- * be any key that has been animated or otherwise handled by active, higher-priority types.
8966
+ * Inherited labels are animated by the parent through
8967
+ * variantChildren, unless this element mounted after its parent.
9105
8968
  */
8969
+ const isInherited = propIsVariant &&
8970
+ ownProp === undefined &&
8971
+ !(isMounting && manuallyAnimateOnMount);
9106
8972
  typeState.protectedKeys = { ...encounteredKeys };
9107
- // Check if we can skip analysing this prop early
9108
- if (
9109
- // If it isn't active and hasn't *just* been set as inactive
9110
- (!typeState.isActive && activeDelta === null) ||
9111
- // If we didn't and don't have any defined prop for this animation type
8973
+ if ((!typeState.isActive && activeDelta === null) ||
9112
8974
  (!prop && !typeState.prevProp) ||
9113
- // Or if the prop doesn't define an animation
9114
8975
  isAnimationControls(prop) ||
9115
- typeof prop === "boolean") {
9116
- continue;
9117
- }
9118
- /**
9119
- * If exit is already active and wasn't just activated, skip
9120
- * re-processing to prevent interrupting running exit animations.
9121
- * Re-resolving exit with a changed custom value can start new
9122
- * value animations that stop the originals, leaving the exit
9123
- * animation promise unresolved and the component stuck in the DOM.
9124
- */
9125
- if (type === "exit" && typeState.isActive && activeDelta !== true) {
9126
- if (typeState.prevResolvedValues) {
9127
- encounteredKeys = {
9128
- ...encounteredKeys,
9129
- ...typeState.prevResolvedValues,
9130
- };
9131
- }
9132
- continue;
8976
+ typeof prop === "boolean") {
8977
+ return;
9133
8978
  }
9134
8979
  /**
9135
- * As we go look through the values defined on this type, if we detect
9136
- * a changed value or a value that was removed in a higher priority, we set
9137
- * this to true and add this prop to the animation list.
8980
+ * Don't re-resolve a running exit: a changed custom would start
8981
+ * new value animations that stop the originals, leaving the exit
8982
+ * promise unresolved and the element stuck in the DOM.
9138
8983
  */
8984
+ if (type === "exit" && typeState.isActive && activeDelta !== true) {
8985
+ encounteredKeys = {
8986
+ ...encounteredKeys,
8987
+ ...typeState.prevResolvedValues,
8988
+ };
8989
+ return;
8990
+ }
9139
8991
  const variantDidChange = checkVariantsDidChange(typeState.prevProp, prop);
9140
8992
  let shouldAnimateType = variantDidChange ||
9141
- // If we're making this variant active, we want to always make it active
9142
- (type === changedActiveType &&
9143
- typeState.isActive &&
9144
- !isInherited &&
9145
- propIsVariant) ||
9146
- // If we removed a higher-priority variant (i is in reverse order)
9147
- (i > removedVariantIndex && propIsVariant);
8993
+ (propIsVariant &&
8994
+ ((activeDelta && !isInherited) || i > removedVariantIndex));
9148
8995
  let handledRemovedValues = false;
9149
- /**
9150
- * As animations can be set as variant lists, variants or target objects, we
9151
- * coerce everything to an array if it isn't one already
9152
- */
9153
8996
  const definitionList = Array.isArray(prop) ? prop : [prop];
9154
- /**
9155
- * Build an object of all the resolved values. We'll use this in the subsequent
9156
- * animateChanges calls to determine whether a value has changed.
9157
- */
9158
- let resolvedValues = definitionList.reduce(buildResolvedTypeValues(type), {});
9159
- if (activeDelta === false)
9160
- resolvedValues = {};
9161
- /**
9162
- * Now we need to loop through all the keys in the prev prop and this prop,
9163
- * and decide:
9164
- * 1. If the value has changed, and needs animating
9165
- * 2. If it has been removed, and needs adding to the removedKeys set
9166
- * 3. If it has been removed in a higher priority type and needs animating
9167
- * 4. If it hasn't been removed in a higher priority but hasn't changed, and
9168
- * needs adding to the type's protectedKeys list.
9169
- */
9170
- const { prevResolvedValues = {} } = typeState;
9171
- const allKeys = {
9172
- ...prevResolvedValues,
9173
- ...resolvedValues,
9174
- };
9175
- const markToAnimate = (key) => {
9176
- shouldAnimateType = true;
9177
- if (removedKeys.has(key)) {
9178
- handledRemovedValues = true;
9179
- removedKeys.delete(key);
8997
+ let resolvedValues = {};
8998
+ if (activeDelta !== false) {
8999
+ for (const definition of definitionList) {
9000
+ const { transition, transitionEnd, ...target } = resolveVariant(visualElement, definition, getTypeCustom(visualElement, type)) || {};
9001
+ resolvedValues = {
9002
+ ...resolvedValues,
9003
+ ...target,
9004
+ ...transitionEnd,
9005
+ };
9180
9006
  }
9181
- typeState.needsAnimating[key] = true;
9182
- const motionValue = visualElement.getValue(key);
9183
- if (motionValue)
9184
- motionValue.liveStyle = false;
9185
- };
9186
- for (const key in allKeys) {
9007
+ }
9008
+ const { prevResolvedValues } = typeState;
9009
+ for (const key in { ...prevResolvedValues, ...resolvedValues }) {
9010
+ if (key in encounteredKeys)
9011
+ continue;
9187
9012
  const next = resolvedValues[key];
9188
9013
  const prev = prevResolvedValues[key];
9189
- // If we've already handled this we can just skip ahead
9190
- if (encounteredKeys.hasOwnProperty(key))
9191
- continue;
9192
9014
  /**
9193
- * If the value has changed, we probably want to animate it.
9015
+ * Keyframes compare by value, but replay whenever the
9016
+ * variant label changed.
9194
9017
  */
9195
- let valueHasChanged = false;
9196
- if (isKeyframesTarget(next) && isKeyframesTarget(prev)) {
9197
- valueHasChanged =
9198
- !shallowCompare(next, prev) || variantDidChange;
9199
- }
9200
- else {
9201
- valueHasChanged = next !== prev;
9202
- }
9203
- if (valueHasChanged) {
9204
- if (next !== undefined && next !== null) {
9205
- // If next is defined and doesn't equal prev, it needs animating
9206
- markToAnimate(key);
9207
- }
9208
- else {
9209
- // If it's undefined, it's been removed.
9210
- removedKeys.add(key);
9211
- }
9018
+ const valueHasChanged = Array.isArray(next) && Array.isArray(prev)
9019
+ ? !shallowCompare(next, prev) || variantDidChange
9020
+ : next !== prev;
9021
+ if (valueHasChanged &&
9022
+ (next === undefined || next === null)) {
9023
+ removedKeys.add(key);
9212
9024
  }
9213
- else if (next !== undefined && removedKeys.has(key)) {
9214
- /**
9215
- * If next hasn't changed and it isn't undefined, we want to check if it's
9216
- * been removed by a higher priority
9217
- */
9218
- markToAnimate(key);
9025
+ else if (valueHasChanged ||
9026
+ (next !== undefined && removedKeys.has(key))) {
9027
+ shouldAnimateType = true;
9028
+ if (removedKeys.delete(key))
9029
+ handledRemovedValues = true;
9030
+ typeState.needsAnimating[key] = true;
9031
+ const motionValue = visualElement.getValue(key);
9032
+ if (motionValue)
9033
+ motionValue.liveStyle = false;
9219
9034
  }
9220
9035
  else {
9221
- /**
9222
- * If it hasn't changed, we add it to the list of protected values
9223
- * to ensure it doesn't get animated.
9224
- */
9225
9036
  typeState.protectedKeys[key] = true;
9226
9037
  }
9227
9038
  }
9228
- /**
9229
- * Update the typeState so next time animateChanges is called we can compare the
9230
- * latest prop and resolvedValues to these.
9231
- */
9232
9039
  typeState.prevProp = prop;
9233
9040
  typeState.prevResolvedValues = resolvedValues;
9234
9041
  if (typeState.isActive) {
9235
9042
  encounteredKeys = { ...encounteredKeys, ...resolvedValues };
9236
9043
  }
9237
- if ((isInitialRender || wasReset) &&
9238
- visualElement.blockInitialAnimation) {
9239
- shouldAnimateType = false;
9240
- }
9241
- /**
9242
- * If this is an inherited prop we want to skip this animation
9243
- * unless the inherited variants haven't changed on this render.
9244
- */
9245
- const willAnimateViaParent = isInherited && variantDidChange;
9246
- const needsAnimating = !willAnimateViaParent || handledRemovedValues;
9247
- if (shouldAnimateType && needsAnimating) {
9248
- animations.push(...definitionList.map((animation) => {
9044
+ if (shouldAnimateType &&
9045
+ (!(isInherited && variantDidChange) || handledRemovedValues)) {
9046
+ for (const animation of definitionList) {
9249
9047
  const options = { type };
9250
9048
  /**
9251
- * If we're performing the initial animation, but we're not
9252
- * rendering at the same time as the variant-controlling parent,
9253
- * we want to use the parent's transition to calculate the stagger.
9049
+ * Elements that mount into an already-mounted parent
9050
+ * animate themselves, staggered by the parent variant's
9051
+ * delayChildren.
9254
9052
  */
9255
9053
  if (typeof animation === "string" &&
9256
- (isInitialRender || wasReset) &&
9257
- !willAnimateViaParent &&
9258
- visualElement.manuallyAnimateOnMount &&
9259
- visualElement.parent) {
9260
- const { parent } = visualElement;
9261
- const parentVariant = resolveVariant(parent, animation);
9262
- if (parent.enteringChildren && parentVariant) {
9263
- const { delayChildren } = parentVariant.transition || {};
9264
- options.delay = calcChildStagger(parent.enteringChildren, visualElement, delayChildren);
9265
- }
9054
+ isMounting &&
9055
+ manuallyAnimateOnMount &&
9056
+ parent?.enteringChildren) {
9057
+ const delayChildren = resolveVariant(parent, animation)
9058
+ ?.transition?.delayChildren;
9059
+ options.delay = calcChildStagger(parent.enteringChildren, visualElement, delayChildren);
9266
9060
  }
9267
- return {
9268
- animation: animation,
9269
- options,
9270
- };
9271
- }));
9061
+ animations.push({ animation, options });
9062
+ }
9272
9063
  }
9273
- }
9064
+ });
9274
9065
  /**
9275
- * If there are some removed value that haven't been dealt with,
9276
- * we need to create a new animation that falls back either to the value
9277
- * defined in the style prop, or the last read value.
9066
+ * Removed values that no lower-priority type defines animate back to
9067
+ * initial, then style, then the value as first read. If initial no
9068
+ * longer defines a value it once did, it stays where it is.
9278
9069
  */
9279
9070
  if (removedKeys.size) {
9071
+ const { initial } = props;
9280
9072
  const fallbackAnimation = {};
9281
- /**
9282
- * If the initial prop contains a transition we can use that, otherwise
9283
- * allow the animation function to use the visual element's default.
9284
- */
9285
- if (typeof props.initial !== "boolean") {
9286
- const initialTransition = resolveVariant(visualElement, Array.isArray(props.initial)
9287
- ? props.initial[0]
9288
- : props.initial);
9289
- if (initialTransition && initialTransition.transition) {
9290
- fallbackAnimation.transition = initialTransition.transition;
9291
- }
9073
+ const resolvedInitial = typeof initial !== "boolean" &&
9074
+ resolveVariant(visualElement, Array.isArray(initial) ? initial[0] : initial, visualElement.presenceContext?.custom);
9075
+ if (resolvedInitial && resolvedInitial.transition) {
9076
+ fallbackAnimation.transition = resolvedInitial.transition;
9292
9077
  }
9293
9078
  removedKeys.forEach((key) => {
9294
- const fallbackTarget = visualElement.getBaseTarget(key);
9295
9079
  const motionValue = visualElement.getValue(key);
9296
9080
  if (motionValue)
9297
9081
  motionValue.liveStyle = true;
9298
- // @ts-expect-error - @mattgperry to figure if we should do something here
9299
- fallbackAnimation[key] = fallbackTarget ?? null;
9082
+ const fromInitial = resolvedInitial && !Array.isArray(initial)
9083
+ ? resolvedInitial[key]
9084
+ : undefined;
9085
+ const fromProps = visualElement.getBaseTargetFromProps(props, key);
9086
+ fallbackAnimation[key] =
9087
+ (fromInitial !== undefined
9088
+ ? fromInitial
9089
+ : fromProps !== undefined && !isMotionValue(fromProps)
9090
+ ? fromProps
9091
+ : visualElement.initialValues[key] === undefined
9092
+ ? visualElement.baseTarget[key]
9093
+ : undefined) ?? null;
9300
9094
  });
9301
9095
  animations.push({ animation: fallbackAnimation });
9302
9096
  }
9303
- let shouldAnimate = Boolean(animations.length);
9304
- if (isInitialRender &&
9305
- (props.initial === false || props.initial === props.animate) &&
9306
- !visualElement.manuallyAnimateOnMount) {
9307
- shouldAnimate = false;
9308
- }
9309
- isInitialRender = false;
9310
- wasReset = false;
9311
- return shouldAnimate ? animate(animations) : Promise.resolve();
9097
+ const blockAnimation = (isMounting && visualElement.blockInitialAnimation) ||
9098
+ (isInitialRender &&
9099
+ !manuallyAnimateOnMount &&
9100
+ (props.initial === false || props.initial === props.animate));
9101
+ isInitialRender = wasReset = false;
9102
+ return !blockAnimation && animations.length
9103
+ ? animate(animations)
9104
+ : Promise.resolve();
9312
9105
  }
9313
9106
  /**
9314
9107
  * Change whether a certain animation type is active.
9315
9108
  */
9316
9109
  function setActive(type, isActive) {
9317
- // If the active state hasn't changed, we can safely do nothing here
9318
9110
  if (state[type].isActive === isActive)
9319
9111
  return Promise.resolve();
9320
- // Propagate active change to children
9321
9112
  visualElement.variantChildren?.forEach((child) => child.animationState?.setActive(type, isActive));
9322
9113
  state[type].isActive = isActive;
9323
9114
  const animations = animateChanges(type);
@@ -9329,7 +9120,13 @@
9329
9120
  return {
9330
9121
  animateChanges,
9331
9122
  setActive,
9332
- setAnimateFunction,
9123
+ /**
9124
+ * Allows tests to inject a mocked animate function.
9125
+ * @internal
9126
+ */
9127
+ setAnimateFunction: (makeAnimator) => {
9128
+ animate = makeAnimator(visualElement);
9129
+ },
9333
9130
  getState: () => state,
9334
9131
  reset: () => {
9335
9132
  state = createState();
@@ -9346,24 +9143,45 @@
9346
9143
  }
9347
9144
  return false;
9348
9145
  }
9349
- function createTypeState(isActive = false) {
9350
- return {
9351
- isActive,
9352
- protectedKeys: {},
9353
- needsAnimating: {},
9354
- prevResolvedValues: {},
9355
- };
9356
- }
9357
9146
  function createState() {
9358
- return {
9359
- animate: createTypeState(true),
9360
- whileInView: createTypeState(),
9361
- whileHover: createTypeState(),
9362
- whileTap: createTypeState(),
9363
- whileDrag: createTypeState(),
9364
- whileFocus: createTypeState(),
9365
- exit: createTypeState(),
9366
- };
9147
+ const state = {};
9148
+ for (const type of variantPriorityOrder) {
9149
+ state[type] = {
9150
+ isActive: type === "animate",
9151
+ protectedKeys: {},
9152
+ needsAnimating: {},
9153
+ prevResolvedValues: {},
9154
+ };
9155
+ }
9156
+ return state;
9157
+ }
9158
+
9159
+ const numVariantProps = variantProps.length;
9160
+ /**
9161
+ * Get variant context from a visual element's parent chain.
9162
+ * Uses `any` type for visualElement to avoid circular dependencies.
9163
+ */
9164
+ function getVariantContext(visualElement) {
9165
+ if (!visualElement)
9166
+ return undefined;
9167
+ if (!visualElement.isControllingVariants) {
9168
+ const context = visualElement.parent
9169
+ ? getVariantContext(visualElement.parent) || {}
9170
+ : {};
9171
+ if (visualElement.props.initial !== undefined) {
9172
+ context.initial = visualElement.props.initial;
9173
+ }
9174
+ return context;
9175
+ }
9176
+ const context = {};
9177
+ for (let i = 0; i < numVariantProps; i++) {
9178
+ const name = variantProps[i];
9179
+ const prop = visualElement.props[name];
9180
+ if (isVariantLabel(prop) || prop === false) {
9181
+ context[name] = prop;
9182
+ }
9183
+ }
9184
+ return context;
9367
9185
  }
9368
9186
 
9369
9187
  /**
@@ -9532,6 +9350,75 @@
9532
9350
  return [callback("x"), callback("y")];
9533
9351
  }
9534
9352
 
9353
+ function pixelsToPercent(pixels, axis) {
9354
+ if (axis.max === axis.min)
9355
+ return 0;
9356
+ return (pixels / (axis.max - axis.min)) * 100;
9357
+ }
9358
+ /**
9359
+ * We always correct borderRadius as a percentage rather than pixels to reduce paints.
9360
+ * For example, if you are projecting a box that is 100px wide with a 10px borderRadius
9361
+ * into a box that is 200px wide with a 20px borderRadius, that is actually a 10%
9362
+ * borderRadius in both states. If we animate between the two in pixels that will trigger
9363
+ * a paint each time. If we animate between the two in percentage we'll avoid a paint.
9364
+ */
9365
+ const correctBorderRadius = {
9366
+ correct: (latest, node) => {
9367
+ if (!node.target)
9368
+ return latest;
9369
+ /**
9370
+ * If latest is a string, if it's a percentage we can return immediately as it's
9371
+ * going to be stretched appropriately. Otherwise, if it's a pixel, convert it to a number.
9372
+ */
9373
+ if (typeof latest === "string") {
9374
+ if (px.test(latest)) {
9375
+ latest = parseFloat(latest);
9376
+ }
9377
+ else {
9378
+ return latest;
9379
+ }
9380
+ }
9381
+ /**
9382
+ * If latest is a number, it's a pixel value. We use the current viewportBox to calculate that
9383
+ * pixel value as a percentage of each axis
9384
+ */
9385
+ const x = pixelsToPercent(latest, node.target.x);
9386
+ const y = pixelsToPercent(latest, node.target.y);
9387
+ return `${x}% ${y}%`;
9388
+ },
9389
+ };
9390
+
9391
+ const correctBoxShadow = {
9392
+ correct: (latest, { treeScale, projectionDelta }) => {
9393
+ const original = latest;
9394
+ const shadow = complex.parse(latest);
9395
+ // TODO: Doesn't support multiple shadows
9396
+ if (shadow.length > 5)
9397
+ return original;
9398
+ const template = complex.createTransformer(latest);
9399
+ const offset = typeof shadow[0] !== "number" ? 1 : 0;
9400
+ // Calculate the overall context scale
9401
+ const xScale = projectionDelta.x.scale * treeScale.x;
9402
+ const yScale = projectionDelta.y.scale * treeScale.y;
9403
+ shadow[0 + offset] /= xScale;
9404
+ shadow[1 + offset] /= yScale;
9405
+ /**
9406
+ * Ideally we'd correct x and y scales individually, but because blur and
9407
+ * spread apply to both we have to take a scale average and apply that instead.
9408
+ * We could potentially improve the outcome of this by incorporating the ratio between
9409
+ * the two scales.
9410
+ */
9411
+ const averageScale = mixNumber$1(xScale, yScale, 0.5);
9412
+ // Blur
9413
+ if (typeof shadow[2 + offset] === "number")
9414
+ shadow[2 + offset] /= averageScale;
9415
+ // Spread
9416
+ if (typeof shadow[3 + offset] === "number")
9417
+ shadow[3 + offset] /= averageScale;
9418
+ return template(shadow);
9419
+ },
9420
+ };
9421
+
9535
9422
  function buildProjectionTransform(delta, treeScale, latestTransform) {
9536
9423
  let transform = "";
9537
9424
  /**
@@ -9584,7 +9471,6 @@
9584
9471
  }
9585
9472
 
9586
9473
  const numBorders = cornerRadiusProps.length;
9587
- const asNumber = (value) => typeof value === "string" ? parseFloat(value) : value;
9588
9474
  const isPx = (value) => typeof value === "number" || px.test(value);
9589
9475
  function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
9590
9476
  if (shouldCrossfadeOpacity) {
@@ -9783,6 +9669,18 @@
9783
9669
  }
9784
9670
  }
9785
9671
 
9672
+ const defaultScaleCorrectors = {
9673
+ borderRadius: {
9674
+ ...correctBorderRadius,
9675
+ applyTo: [...cornerRadiusProps],
9676
+ },
9677
+ borderTopLeftRadius: correctBorderRadius,
9678
+ borderTopRightRadius: correctBorderRadius,
9679
+ borderBottomLeftRadius: correctBorderRadius,
9680
+ borderBottomRightRadius: correctBorderRadius,
9681
+ boxShadow: correctBoxShadow,
9682
+ };
9683
+
9786
9684
  /**
9787
9685
  * This should only ever be modified on the client otherwise it'll
9788
9686
  * persist through server requests. If we need instanced states we
@@ -9801,6 +9699,14 @@
9801
9699
  hasEverUpdated: false,
9802
9700
  };
9803
9701
 
9702
+ /**
9703
+ * Correctors registered via addScaleCorrector before projection loaded
9704
+ * (e.g. with an async LazyMotion bundle) take precedence over the defaults.
9705
+ */
9706
+ Object.assign(scaleCorrectors, {
9707
+ ...defaultScaleCorrectors,
9708
+ ...scaleCorrectors,
9709
+ });
9804
9710
  const metrics = {
9805
9711
  nodes: 0,
9806
9712
  calculatedTargetDeltas: 0,
@@ -12556,12 +12462,6 @@
12556
12462
  };
12557
12463
  const animateMini = /*@__PURE__*/ createScopedWaapiAnimate();
12558
12464
 
12559
- function canUseNativeTimeline(target) {
12560
- if (typeof window === "undefined")
12561
- return false;
12562
- return target ? supportsViewTimeline() : supportsScrollTimeline();
12563
- }
12564
-
12565
12465
  /**
12566
12466
  * A time in milliseconds, beyond which we consider the scroll velocity to be 0.
12567
12467
  */
@@ -12701,18 +12601,6 @@
12701
12601
  }
12702
12602
 
12703
12603
  const ScrollOffset = {
12704
- Enter: [
12705
- [0, 1],
12706
- [1, 1],
12707
- ],
12708
- Exit: [
12709
- [0, 0],
12710
- [1, 0],
12711
- ],
12712
- Any: [
12713
- [1, 0],
12714
- [0, 1],
12715
- ],
12716
12604
  All: [
12717
12605
  [0, 0],
12718
12606
  [1, 1],
@@ -12964,158 +12852,163 @@
12964
12852
  };
12965
12853
  }
12966
12854
 
12967
- /**
12968
- * Maps from ProgressIntersection pairs used by Motion's preset offsets to
12969
- * ViewTimeline named ranges. Returns undefined for unrecognised patterns,
12970
- * which signals the caller to fall back to JS-based scroll tracking.
12971
- */
12972
- const presets = [
12973
- [ScrollOffset.Enter, "entry"],
12974
- [ScrollOffset.Exit, "exit"],
12975
- [ScrollOffset.Any, "cover"],
12976
- [ScrollOffset.All, "contain"],
12977
- ];
12978
- const stringToProgress = {
12979
- start: 0,
12980
- end: 1,
12981
- };
12982
- function parseStringOffset(s) {
12983
- const parts = s.trim().split(/\s+/);
12984
- if (parts.length !== 2)
12985
- return undefined;
12986
- const a = stringToProgress[parts[0]];
12987
- const b = stringToProgress[parts[1]];
12988
- if (a === undefined || b === undefined)
12989
- return undefined;
12990
- return [a, b];
12991
- }
12992
- function normaliseOffset(offset) {
12993
- if (offset.length !== 2)
12994
- return undefined;
12995
- const result = [];
12996
- for (const item of offset) {
12997
- if (Array.isArray(item)) {
12998
- result.push(item);
12999
- }
13000
- else if (typeof item === "string") {
13001
- const parsed = parseStringOffset(item);
13002
- if (!parsed)
13003
- return undefined;
13004
- result.push(parsed);
13005
- }
13006
- else {
13007
- return undefined;
13008
- }
13009
- }
13010
- return result;
13011
- }
13012
- function matchesPreset(offset, preset) {
13013
- const normalised = normaliseOffset(offset);
13014
- if (!normalised)
12855
+ function canUseNativeTimeline(target) {
12856
+ if (typeof window === "undefined")
13015
12857
  return false;
13016
- for (let i = 0; i < 2; i++) {
13017
- const o = normalised[i];
13018
- const p = preset[i];
13019
- if (o[0] !== p[0] || o[1] !== p[1])
13020
- return false;
13021
- }
13022
- return true;
13023
- }
13024
- function offsetToViewTimelineRange(offset) {
13025
- if (!offset) {
13026
- return { rangeStart: "contain 0%", rangeEnd: "contain 100%" };
13027
- }
13028
- for (const [preset, name] of presets) {
13029
- if (matchesPreset(offset, preset)) {
13030
- return { rangeStart: `${name} 0%`, rangeEnd: `${name} 100%` };
13031
- }
13032
- }
13033
- return undefined;
12858
+ return target ? supportsViewTimeline() : supportsScrollTimeline();
13034
12859
  }
13035
12860
 
13036
12861
  const timelineCache = new Map();
13037
- function scrollTimelineFallback(options) {
13038
- const currentTime = { value: 0 };
13039
- const cancel = scrollInfo((info) => {
13040
- currentTime.value = info[options.axis].progress * 100;
13041
- }, options);
13042
- return { currentTime, cancel };
13043
- }
13044
- function getTimeline({ container, ...options }) {
13045
- const { axis } = options;
12862
+ /**
12863
+ * Native timelines are only attached to WAAPI animations. Offsets are
12864
+ * applied to each animation as a range, so a timeline is shared by every
12865
+ * offset.
12866
+ */
12867
+ function getTimeline({ container, target, axis, }) {
13046
12868
  let containerCache = timelineCache.get(container);
13047
12869
  if (!containerCache) {
13048
12870
  containerCache = new Map();
13049
12871
  timelineCache.set(container, containerCache);
13050
12872
  }
13051
- const targetKey = options.target ?? "self";
12873
+ const targetKey = target ?? "self";
13052
12874
  let targetCache = containerCache.get(targetKey);
13053
12875
  if (!targetCache) {
13054
12876
  targetCache = {};
13055
12877
  containerCache.set(targetKey, targetCache);
13056
12878
  }
13057
- const axisKey = axis + (options.offset ?? []).join(",");
13058
- if (!targetCache[axisKey]) {
13059
- if (options.target && canUseNativeTimeline(options.target)) {
13060
- const range = offsetToViewTimelineRange(options.offset);
13061
- if (range) {
13062
- targetCache[axisKey] = new ViewTimeline({
13063
- subject: options.target,
13064
- axis,
13065
- });
13066
- }
13067
- else {
13068
- targetCache[axisKey] = scrollTimelineFallback({
13069
- container,
13070
- ...options,
13071
- });
13072
- }
13073
- }
13074
- else if (canUseNativeTimeline()) {
13075
- targetCache[axisKey] = new ScrollTimeline({
13076
- source: container,
13077
- axis,
13078
- });
13079
- }
13080
- else {
13081
- targetCache[axisKey] = scrollTimelineFallback({
13082
- container,
13083
- ...options,
13084
- });
13085
- }
12879
+ return (targetCache[axis] || (targetCache[axis] = target
12880
+ ? new ViewTimeline({ subject: target, axis })
12881
+ : new ScrollTimeline({ source: container, axis })));
12882
+ }
12883
+
12884
+ /**
12885
+ * Resolved offsets are linear in the target and container lengths, so
12886
+ * probing them gives [target progress, container progress, pixels].
12887
+ * vw/vh can resolve to 0px, so they're rejected up front.
12888
+ */
12889
+ const toIntersection = (o) => {
12890
+ if (/v/u.test(o))
12891
+ return [];
12892
+ const px = resolveOffset(o, 0, 0, 0);
12893
+ return [resolveOffset(o, 0, 1, 0) - px, px - resolveOffset(o, 1, 0, 0), px];
12894
+ };
12895
+ const toRange = ([t, c, px]) => !px &&
12896
+ (c === 0 || c === 1) &&
12897
+ `${c ? "entry" : "exit"}-crossing ${t * 100}%`;
12898
+ /**
12899
+ * Maps an offset to an equivalent ViewTimeline range. Returns undefined when
12900
+ * there isn't one, which signals the caller to fall back to JS-based scroll
12901
+ * tracking.
12902
+ */
12903
+ function offsetToViewTimelineRange(offset = ScrollOffset.All) {
12904
+ if (offset.length !== 2)
12905
+ return;
12906
+ const [start, end] = offset.map(toIntersection);
12907
+ const points = [toRange(start), toRange(end)];
12908
+ const a = end[0] - start[0];
12909
+ const b = start[1] - end[1];
12910
+ if (points[0] && points[1] && (a || b)) {
12911
+ return {
12912
+ points: points,
12913
+ a,
12914
+ b,
12915
+ cover: !start[0] && a === 1 && b === 1,
12916
+ };
13086
12917
  }
13087
- return targetCache[axisKey];
13088
12918
  }
13089
12919
 
13090
12920
  function attachToAnimation(animation, options) {
13091
- const timeline = getTimeline(options);
13092
- const range = options.target
13093
- ? offsetToViewTimelineRange(options.offset)
13094
- : undefined;
12921
+ const { target, container, axis } = options;
12922
+ const range = target && offsetToViewTimelineRange(options.offset);
13095
12923
  /**
13096
12924
  * Use native timeline when:
13097
- * - No target: ScrollTimeline (existing behaviour)
12925
+ * - No target and no offset: ScrollTimeline
13098
12926
  * - Target with mappable offset: ViewTimeline with named range
13099
- * - Target with unmappable offset: fall back to JS observe
13100
- */
13101
- const useNative = options.target
13102
- ? canUseNativeTimeline(options.target) && !!range
13103
- : canUseNativeTimeline();
13104
- return animation.attachTimeline({
13105
- timeline: useNative ? timeline : undefined,
13106
- ...(range &&
13107
- useNative && {
13108
- rangeStart: range.rangeStart,
13109
- rangeEnd: range.rangeEnd,
12927
+ * - Otherwise: fall back to JS observe. A ScrollTimeline has no offset,
12928
+ * so page offsets are applied by the JS timeline.
12929
+ */
12930
+ const native = canUseNativeTimeline(target) && (target ? !!range : !options.offset);
12931
+ const animations = new Map();
12932
+ const observed = new Set();
12933
+ let stopObserving;
12934
+ let reverse = false;
12935
+ /**
12936
+ * When an offset's second point comes first, its range is swapped and
12937
+ * the animation played backwards. The range is set after the direction,
12938
+ * as that's what realigns a running animation with its timeline. Chrome
12939
+ * skips that realignment when the range changes in the frame the
12940
+ * animation starts, leaving it offset for good, so it's replayed too.
12941
+ */
12942
+ const apply = (direction, waapi) => {
12943
+ const [start, end] = range.points;
12944
+ waapi.effect.updateTiming({
12945
+ direction: reverse
12946
+ ? direction === "normal"
12947
+ ? "reverse"
12948
+ : "alternate-reverse"
12949
+ : direction,
12950
+ });
12951
+ Object.assign(waapi, {
12952
+ rangeStart: reverse ? end : start,
12953
+ rangeEnd: reverse ? start : end,
12954
+ });
12955
+ waapi.play();
12956
+ };
12957
+ const stops = [
12958
+ animation.attachTimeline({
12959
+ /**
12960
+ * Read only by WAAPI animations, so values driven from JS don't
12961
+ * create a native timeline they won't use.
12962
+ */
12963
+ get timeline() {
12964
+ return native ? getTimeline(options) : undefined;
12965
+ },
12966
+ onAttach: range &&
12967
+ ((waapi) => {
12968
+ const direction = waapi.effect.getTiming().direction;
12969
+ animations.set(waapi, direction);
12970
+ apply(direction, waapi);
12971
+ }),
12972
+ /**
12973
+ * Values driven from JS all track the offset with scrollInfo,
12974
+ * which measures once per frame for all of them.
12975
+ */
12976
+ observe: (valueAnimation) => {
12977
+ valueAnimation.pause();
12978
+ observed.add(valueAnimation);
12979
+ stopObserving || (stopObserving = scrollInfo((info) => {
12980
+ observed.forEach((observedAnimation) => {
12981
+ observedAnimation.time =
12982
+ observedAnimation.iterationDuration *
12983
+ info[axis].progress;
12984
+ });
12985
+ }, options));
12986
+ return () => observed.delete(valueAnimation);
12987
+ },
13110
12988
  }),
13111
- observe: (valueAnimation) => {
13112
- valueAnimation.pause();
13113
- return observeTimeline((progress) => {
13114
- valueAnimation.time =
13115
- valueAnimation.iterationDuration * progress;
13116
- }, timeline);
13117
- },
13118
- });
12989
+ ];
12990
+ if (native && range) {
12991
+ const { a, b } = range;
12992
+ const length = axis === "y" ? "clientHeight" : "clientWidth";
12993
+ /**
12994
+ * Offsets like All only run forwards when the target is longer than
12995
+ * the container, so their direction is remeasured on resize.
12996
+ */
12997
+ const update = () => {
12998
+ if (reverse !==
12999
+ (reverse = a * target[length] + b * container[length] < 0)) {
13000
+ animations.forEach(apply);
13001
+ }
13002
+ };
13003
+ update();
13004
+ if (a * b < 0) {
13005
+ stops.push(resize(update), resize([target, container], update));
13006
+ }
13007
+ }
13008
+ return () => {
13009
+ stops.forEach((stop) => stop());
13010
+ stopObserving?.();
13011
+ };
13119
13012
  }
13120
13013
 
13121
13014
  function scroll(onScroll, { axis = "y", source, container = document.scrollingElement, ...options } = {}) {
@@ -13418,6 +13311,7 @@
13418
13311
  exports.observeTimeline = observeTimeline;
13419
13312
  exports.optimizedAppearDataAttribute = optimizedAppearDataAttribute;
13420
13313
  exports.optimizedAppearDataId = optimizedAppearDataId;
13314
+ exports.originProps = originProps;
13421
13315
  exports.parseAnimateLayoutArgs = parseAnimateLayoutArgs;
13422
13316
  exports.parseCSSVariable = parseCSSVariable;
13423
13317
  exports.parseValueFromTransform = parseValueFromTransform;