motion 13.1.1-alpha.0 → 13.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1039,15 +1039,16 @@
1039
1039
  * to prevent infinite loops
1040
1040
  */
1041
1041
  const maxGeneratorDuration = 20000;
1042
- function calcGeneratorDuration(generator) {
1042
+ function calcGeneratorDuration(generator, timeStep = 50, maxDuration = maxGeneratorDuration, keyframes) {
1043
1043
  let duration = 0;
1044
- const timeStep = 50;
1045
1044
  let state = generator.next(duration);
1046
- while (!state.done && duration < maxGeneratorDuration) {
1045
+ keyframes?.push(state.value);
1046
+ while (!state.done && duration < maxDuration) {
1047
1047
  duration += timeStep;
1048
1048
  state = generator.next(duration);
1049
+ keyframes?.push(state.value);
1049
1050
  }
1050
- return duration >= maxGeneratorDuration ? Infinity : duration;
1051
+ return duration >= maxDuration ? Infinity : duration;
1051
1052
  }
1052
1053
 
1053
1054
  /**
@@ -1131,9 +1132,13 @@
1131
1132
  const exponentialDecay = undampedFreq * dampingRatio;
1132
1133
  const delta = exponentialDecay * duration;
1133
1134
  const d = delta * velocity + velocity;
1134
- const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
1135
+ const e = dampingRatio *
1136
+ dampingRatio *
1137
+ undampedFreq *
1138
+ undampedFreq *
1139
+ duration;
1135
1140
  const f = Math.exp(-delta);
1136
- const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
1141
+ const g = calcAngularFreq(undampedFreq * undampedFreq, dampingRatio);
1137
1142
  const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
1138
1143
  return (factor * ((d - e) * f)) / g;
1139
1144
  };
@@ -1164,7 +1169,7 @@
1164
1169
  };
1165
1170
  }
1166
1171
  else {
1167
- const stiffness = Math.pow(undampedFreq, 2) * mass;
1172
+ const stiffness = undampedFreq * undampedFreq * mass;
1168
1173
  return {
1169
1174
  stiffness,
1170
1175
  damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
@@ -1244,6 +1249,7 @@
1244
1249
  const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
1245
1250
  const initialDelta = target - origin;
1246
1251
  const undampedAngularFreq = millisecondsToSeconds(Math.sqrt(stiffness / mass));
1252
+ const decay = dampingRatio * undampedAngularFreq;
1247
1253
  /**
1248
1254
  * If we're working on a granular scale, use smaller defaults for determining
1249
1255
  * when the spring is finished.
@@ -1260,35 +1266,38 @@
1260
1266
  : springDefaults.restDelta.default);
1261
1267
  let resolveSpring;
1262
1268
  let resolveVelocity;
1263
- // Underdamped coefficients, hoisted for use in the inlined next() hot path
1264
- let angularFreq;
1265
- let A;
1266
- let sinCoeff;
1267
- let cosCoeff;
1268
1269
  if (dampingRatio < 1) {
1269
- angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
1270
- A =
1271
- (initialVelocity +
1272
- dampingRatio * undampedAngularFreq * initialDelta) /
1273
- angularFreq;
1270
+ const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
1271
+ const A = (initialVelocity + decay * initialDelta) / angularFreq;
1272
+ // Coefficients for the analytical derivative (px/ms)
1273
+ const sinCoeff = decay * A + initialDelta * angularFreq;
1274
+ const cosCoeff = decay * initialDelta - A * angularFreq;
1275
+ /**
1276
+ * The underdamped hot path needs both position and velocity every
1277
+ * frame and they share the same exp/sin/cos terms, so sample both
1278
+ * at once, memoized by t, to only calculate them once per frame.
1279
+ */
1280
+ let sampledT = -1;
1281
+ let position = 0;
1282
+ let velocityAtT = 0;
1283
+ const sample = (t) => {
1284
+ if (t !== sampledT) {
1285
+ sampledT = t;
1286
+ const envelope = Math.exp(-decay * t);
1287
+ const sin = Math.sin(angularFreq * t);
1288
+ const cos = Math.cos(angularFreq * t);
1289
+ position = target - envelope * (A * sin + initialDelta * cos);
1290
+ velocityAtT = envelope * (sinCoeff * sin + cosCoeff * cos);
1291
+ }
1292
+ };
1274
1293
  // Underdamped spring
1275
1294
  resolveSpring = (t) => {
1276
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1277
- return (target -
1278
- envelope *
1279
- (A * Math.sin(angularFreq * t) +
1280
- initialDelta * Math.cos(angularFreq * t)));
1295
+ sample(t);
1296
+ return position;
1281
1297
  };
1282
- // Analytical derivative of underdamped spring (px/ms)
1283
- sinCoeff =
1284
- dampingRatio * undampedAngularFreq * A + initialDelta * angularFreq;
1285
- cosCoeff =
1286
- dampingRatio * undampedAngularFreq * initialDelta - A * angularFreq;
1287
1298
  resolveVelocity = (t) => {
1288
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1289
- return envelope *
1290
- (sinCoeff * Math.sin(angularFreq * t) +
1291
- cosCoeff * Math.cos(angularFreq * t));
1299
+ sample(t);
1300
+ return velocityAtT;
1292
1301
  };
1293
1302
  }
1294
1303
  else if (dampingRatio === 1) {
@@ -1306,13 +1315,12 @@
1306
1315
  // Overdamped spring
1307
1316
  const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
1308
1317
  resolveSpring = (t) => {
1309
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1318
+ const envelope = Math.exp(-decay * t);
1310
1319
  // When performing sinh or cosh values can hit Infinity so we cap them here
1311
1320
  const freqForT = Math.min(dampedAngularFreq * t, 300);
1312
1321
  return (target -
1313
1322
  (envelope *
1314
- ((initialVelocity +
1315
- dampingRatio * undampedAngularFreq * initialDelta) *
1323
+ ((initialVelocity + decay * initialDelta) *
1316
1324
  Math.sinh(freqForT) +
1317
1325
  dampedAngularFreq *
1318
1326
  initialDelta *
@@ -1320,43 +1328,21 @@
1320
1328
  dampedAngularFreq);
1321
1329
  };
1322
1330
  // Analytical derivative of overdamped spring (px/ms)
1323
- const P = (initialVelocity +
1324
- dampingRatio * undampedAngularFreq * initialDelta) /
1325
- dampedAngularFreq;
1326
- const sinhCoeff = dampingRatio * undampedAngularFreq * P - initialDelta * dampedAngularFreq;
1327
- const coshCoeff = dampingRatio * undampedAngularFreq * initialDelta - P * dampedAngularFreq;
1331
+ const P = (initialVelocity + decay * initialDelta) / dampedAngularFreq;
1332
+ const sinhCoeff = decay * P - initialDelta * dampedAngularFreq;
1333
+ const coshCoeff = decay * initialDelta - P * dampedAngularFreq;
1328
1334
  resolveVelocity = (t) => {
1329
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1335
+ const envelope = Math.exp(-decay * t);
1330
1336
  const freqForT = Math.min(dampedAngularFreq * t, 300);
1331
- return envelope *
1337
+ return (envelope *
1332
1338
  (sinhCoeff * Math.sinh(freqForT) +
1333
- coshCoeff * Math.cosh(freqForT));
1339
+ coshCoeff * Math.cosh(freqForT)));
1334
1340
  };
1335
1341
  }
1336
1342
  const generator = {
1337
1343
  calculatedDuration: isResolvedFromDuration ? duration || null : null,
1338
1344
  velocity: (t) => secondsToMilliseconds(resolveVelocity(t)),
1339
1345
  next: (t) => {
1340
- /**
1341
- * For underdamped physics springs we need both position and
1342
- * velocity each tick. Compute shared trig values once to avoid
1343
- * duplicate Math.exp/sin/cos calls on the hot path.
1344
- */
1345
- if (!isResolvedFromDuration && dampingRatio < 1) {
1346
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1347
- const sin = Math.sin(angularFreq * t);
1348
- const cos = Math.cos(angularFreq * t);
1349
- const current = target -
1350
- envelope *
1351
- (A * sin + initialDelta * cos);
1352
- const currentVelocity = secondsToMilliseconds(envelope *
1353
- (sinCoeff * sin + cosCoeff * cos));
1354
- state.done =
1355
- Math.abs(currentVelocity) <= restSpeed &&
1356
- Math.abs(target - current) <= restDelta;
1357
- state.value = state.done ? target : current;
1358
- return state;
1359
- }
1360
1346
  const current = resolveSpring(t);
1361
1347
  if (!isResolvedFromDuration) {
1362
1348
  const currentVelocity = secondsToMilliseconds(resolveVelocity(t));
@@ -1387,19 +1373,13 @@
1387
1373
  return options;
1388
1374
  };
1389
1375
 
1390
- const velocitySampleDuration = 5; // ms
1391
- function getGeneratorVelocity(resolveValue, t, current) {
1392
- const prevT = Math.max(t - velocitySampleDuration, 0);
1393
- return velocityPerSecond(current - resolveValue(prevT), t - prevT);
1394
- }
1395
-
1396
1376
  function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
1397
1377
  const origin = keyframes[0];
1398
1378
  const state = {
1399
1379
  done: false,
1400
1380
  value: origin,
1401
1381
  };
1402
- const isOutOfBounds = (v) => (min !== undefined && v < min) || (max !== undefined && v > max);
1382
+ const isOutOfBounds = (v) => v < min || v > max;
1403
1383
  const nearestBoundary = (v) => {
1404
1384
  if (min === undefined)
1405
1385
  return max;
@@ -1417,12 +1397,10 @@
1417
1397
  if (target !== ideal)
1418
1398
  amplitude = target - origin;
1419
1399
  const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant);
1420
- const calcLatest = (t) => target + calcDelta(t);
1421
1400
  const applyFriction = (t) => {
1422
1401
  const delta = calcDelta(t);
1423
- const latest = calcLatest(t);
1424
1402
  state.done = Math.abs(delta) <= restDelta;
1425
- state.value = state.done ? target : latest;
1403
+ state.value = state.done ? target : target + delta;
1426
1404
  };
1427
1405
  /**
1428
1406
  * Ideally this would resolve for t in a stateless way, we could
@@ -1438,7 +1416,12 @@
1438
1416
  timeReachedBoundary = t;
1439
1417
  spring$1 = spring({
1440
1418
  keyframes: [state.value, nearestBoundary(state.value)],
1441
- velocity: getGeneratorVelocity(calcLatest, t, state.value), // TODO: This should be passing * 1000
1419
+ /**
1420
+ * The friction curve is target + calcDelta(t), so its exact
1421
+ * derivative is -calcDelta(t) / timeConstant in units/ms,
1422
+ * converted here to the units/second expected by spring.
1423
+ */
1424
+ velocity: (-calcDelta(t) / timeConstant) * 1000,
1442
1425
  damping: bounceDamping,
1443
1426
  stiffness: bounceStiffness,
1444
1427
  restDelta,
@@ -1607,6 +1590,12 @@
1607
1590
  };
1608
1591
  }
1609
1592
 
1593
+ const velocitySampleDuration = 5; // ms
1594
+ function getGeneratorVelocity(resolveValue, t, current) {
1595
+ const prevT = Math.max(t - velocitySampleDuration, 0);
1596
+ return velocityPerSecond(current - resolveValue(prevT), t - prevT);
1597
+ }
1598
+
1610
1599
  const isNotNull = (value) => value !== null;
1611
1600
  function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe, speed = 1) {
1612
1601
  const resolvedKeyframes = keyframes.filter(isNotNull);
@@ -2782,15 +2771,6 @@
2782
2771
  "transform",
2783
2772
  "backgroundColor",
2784
2773
  ]);
2785
- function hasIndependentTransform(values) {
2786
- if (!values)
2787
- return false;
2788
- for (const key in values) {
2789
- if (transformProps.has(key))
2790
- return true;
2791
- }
2792
- return false;
2793
- }
2794
2774
 
2795
2775
  const browserColorFunctions = /^(?:oklch|oklab|lab|lch|color|color-mix|light-dark)\(/;
2796
2776
  function hasBrowserOnlyColors(keyframes) {
@@ -2830,8 +2810,7 @@
2830
2810
  !(subject instanceof SVGElement)) {
2831
2811
  return false;
2832
2812
  }
2833
- const owner = motionValue.owner;
2834
- const { onUpdate, transformTemplate } = owner.getProps();
2813
+ const { onUpdate, transformTemplate } = motionValue.owner.getProps();
2835
2814
  return (supportsWaapi() &&
2836
2815
  name &&
2837
2816
  /**
@@ -2841,9 +2820,7 @@
2841
2820
  (acceleratedValues.has(name) ||
2842
2821
  (colorProperties.has(name) &&
2843
2822
  hasBrowserOnlyColors(keyframes))) &&
2844
- (name !== "transform" ||
2845
- (!transformTemplate &&
2846
- !hasIndependentTransform(owner.latestValues))) &&
2823
+ (name !== "transform" || !transformTemplate) &&
2847
2824
  /**
2848
2825
  * If we're outputting values to onUpdate then we can't use WAAPI as there's
2849
2826
  * no way to read the value from WAAPI every frame.
@@ -3990,6 +3967,7 @@
3990
3967
  return visualElement.props[optimizedAppearDataAttribute];
3991
3968
  }
3992
3969
 
3970
+ const isBrowser$1 = typeof window !== "undefined";
3993
3971
  /**
3994
3972
  * Decide whether we should block this animation. Previously, we achieved this
3995
3973
  * just by checking whether the key was listed in protectedKeys, but this
@@ -4053,7 +4031,7 @@
4053
4031
  * to see if we're handling off from an existing animation.
4054
4032
  */
4055
4033
  let isHandoff = false;
4056
- if (window.MotionHandoffAnimation) {
4034
+ if (isBrowser$1 && window.MotionHandoffAnimation) {
4057
4035
  const appearId = getOptimisedAppearId(visualElement);
4058
4036
  if (appearId) {
4059
4037
  const startTime = window.MotionHandoffAnimation(appearId, key, frame);
@@ -4623,6 +4601,47 @@
4623
4601
  return true;
4624
4602
  });
4625
4603
 
4604
+ /**
4605
+ * Effects registered via `animate.addEffect()`, most recent first.
4606
+ */
4607
+ const effects = [];
4608
+ function addEffect(effect) {
4609
+ exports.invariant(typeof effect.test === "function" && typeof effect.read === "function", "Effects passed to animate.addEffect() need test() and read().", "effect-missing-test");
4610
+ removeEffect(effect);
4611
+ effects.unshift(effect);
4612
+ }
4613
+ function removeEffect(effect) {
4614
+ removeItem(effects, effect);
4615
+ }
4616
+ function findEffect(subject) {
4617
+ return effects.find((effect) => effect.test(subject));
4618
+ }
4619
+ /**
4620
+ * Animate the keys of `subject` via `effect`. Motion values are created on
4621
+ * first animation, seeded from `effect.read()` or the first keyframe, and
4622
+ * then bound to the subject via the effect for the rest of its life.
4623
+ */
4624
+ function animateEffectSubject(effect, subject, keyframes, transition = {}) {
4625
+ const animations = [];
4626
+ for (const key in keyframes) {
4627
+ const target = keyframes[key];
4628
+ let value = effect.get(subject, key);
4629
+ if (!value) {
4630
+ const initial = effect.read(subject, key, target) ?? firstKeyframe(target);
4631
+ exports.invariant(initial !== undefined, `"${key}" can't be read from the animated subject. Provide [from, to] keyframes.`, "effect-unreadable-value");
4632
+ value = motionValue(initial);
4633
+ effect(subject, { [key]: value });
4634
+ }
4635
+ value.start(animateMotionValue(key, value, target, getValueTransition$1(transition, key)));
4636
+ value.animation && animations.push(value.animation);
4637
+ }
4638
+ return animations;
4639
+ }
4640
+ function firstKeyframe(target) {
4641
+ const first = Array.isArray(target) ? target[0] : undefined;
4642
+ return first === null ? undefined : first;
4643
+ }
4644
+
4626
4645
  function resolveElements(elementOrSelector, scope, selectorCache) {
4627
4646
  if (elementOrSelector == null) {
4628
4647
  return [];
@@ -4667,7 +4686,13 @@
4667
4686
  };
4668
4687
 
4669
4688
  class MotionValueState {
4670
- constructor() {
4689
+ /**
4690
+ * @param step - The frameloop step renders are scheduled in. Defaults
4691
+ * to `frame.render`. Effects that feed a render loop running in
4692
+ * `frame.render` (GPU scenes) should write in `frame.preRender`.
4693
+ */
4694
+ constructor(step = frame.render) {
4695
+ this.step = step;
4671
4696
  this.latest = {};
4672
4697
  this.values = new Map();
4673
4698
  }
@@ -4684,7 +4709,7 @@
4684
4709
  else {
4685
4710
  this.latest[name] = v;
4686
4711
  }
4687
- render && frame.render(render);
4712
+ render && this.step(render);
4688
4713
  };
4689
4714
  onChange();
4690
4715
  const cancelOnChange = value.on("change", onChange);
@@ -4703,10 +4728,10 @@
4703
4728
  }
4704
4729
  }
4705
4730
 
4706
- function createEffect(addValue) {
4731
+ function createEffect(addValue, { step, ...options } = {}) {
4707
4732
  const stateCache = new WeakMap();
4708
- return (subject, values) => {
4709
- const state = stateCache.get(subject) ?? new MotionValueState();
4733
+ const effect = (subject, values) => {
4734
+ const state = stateCache.get(subject) ?? new MotionValueState(step);
4710
4735
  stateCache.set(subject, state);
4711
4736
  const subscriptions = [];
4712
4737
  for (const key in values) {
@@ -4719,6 +4744,9 @@
4719
4744
  cancel();
4720
4745
  };
4721
4746
  };
4747
+ return Object.assign(effect, options, {
4748
+ get: (subject, key) => stateCache.get(subject)?.get(key),
4749
+ });
4722
4750
  }
4723
4751
 
4724
4752
  function canSetAsProperty(element, name) {
@@ -4814,37 +4842,25 @@
4814
4842
  ? `${pathRotation}deg`
4815
4843
  : pathRotation}) `;
4816
4844
  }
4817
- const userTransform = state.latest.transform;
4818
- if (userTransform && userTransform !== "none") {
4819
- transformIsDefault = false;
4820
- transform += userTransform;
4821
- }
4822
4845
  return transformIsDefault ? "none" : transform.trim();
4823
4846
  }
4824
4847
 
4825
4848
  const originProps = new Set(["originX", "originY", "originZ"]);
4826
- /**
4827
- * Internal key for the computed transform channel. The user "transform"
4828
- * value now composes into this channel, so it can't share the "transform"
4829
- * key. Prefixed with $ so it can never collide with a real style key
4830
- * (like "transformStyle", which is the transform-style CSS property).
4831
- */
4832
- const computedTransform = "$transform";
4833
4849
  const addStyleValue = (element, state, key, value) => {
4834
4850
  let render = undefined;
4835
4851
  let computed = undefined;
4836
- if (transformProps.has(key) || key === "transform") {
4837
- if (!state.get(computedTransform)) {
4852
+ if (transformProps.has(key)) {
4853
+ if (!state.get("transform")) {
4838
4854
  // If this is an HTML element, we need to set the transform-box to fill-box
4839
4855
  // to normalise the transform relative to the element's bounding box
4840
4856
  if (!isHTMLElement(element) && !state.get("transformBox")) {
4841
4857
  addStyleValue(element, state, "transformBox", new MotionValue("fill-box"));
4842
4858
  }
4843
- state.set(computedTransform, new MotionValue("none"), () => {
4859
+ state.set("transform", new MotionValue("none"), () => {
4844
4860
  element.style.transform = buildTransform$1(state);
4845
4861
  });
4846
4862
  }
4847
- computed = state.get(computedTransform);
4863
+ computed = state.get("transform");
4848
4864
  }
4849
4865
  else if (originProps.has(key)) {
4850
4866
  if (!state.get("transformOrigin")) {
@@ -7119,10 +7135,7 @@
7119
7135
  }
7120
7136
  if (value.accelerate &&
7121
7137
  acceleratedValues.has(key) &&
7122
- this.current instanceof HTMLElement &&
7123
- (key !== "transform" ||
7124
- (!this.props.transformTemplate &&
7125
- !hasIndependentTransform(this.latestValues)))) {
7138
+ this.current instanceof HTMLElement) {
7126
7139
  const { factory, keyframes, times, ease, duration } = value.accelerate;
7127
7140
  const animation = new NativeAnimation({
7128
7141
  element: this.current,
@@ -7517,12 +7530,6 @@
7517
7530
  values.skewX ||
7518
7531
  values.skewY);
7519
7532
  }
7520
- function hasVisualTransform(values) {
7521
- return (hasTransform(values) ||
7522
- (values.transform &&
7523
- values.transform !== "none" &&
7524
- values.transform !== ""));
7525
- }
7526
7533
  function has2DTranslate(values) {
7527
7534
  return is2DTranslate(values.x) || is2DTranslate(values.y);
7528
7535
  }
@@ -7717,11 +7724,6 @@
7717
7724
  transformIsDefault = false;
7718
7725
  transformString += `rotate(${getValueAsType(pathRotation, numberValueTypes.pathRotation)}) `;
7719
7726
  }
7720
- const userTransform = latestValues.transform;
7721
- if (userTransform && userTransform !== "none") {
7722
- transformIsDefault = false;
7723
- transformString += userTransform;
7724
- }
7725
7727
  transformString = transformString.trim();
7726
7728
  // If we have a custom `transform` template, pass our transform values and
7727
7729
  // generated transformString to that before returning
@@ -7747,7 +7749,7 @@
7747
7749
  */
7748
7750
  for (const key in latestValues) {
7749
7751
  const value = latestValues[key];
7750
- if (transformProps.has(key) || key === "transform") {
7752
+ if (transformProps.has(key)) {
7751
7753
  // If this is a transform, flag to enable further transform processing
7752
7754
  hasTransform = true;
7753
7755
  continue;
@@ -7770,15 +7772,17 @@
7770
7772
  }
7771
7773
  }
7772
7774
  }
7773
- if (hasTransform || transformTemplate) {
7774
- style.transform = buildTransform(latestValues, state.transform, transformTemplate);
7775
- }
7776
- else if (style.transform) {
7777
- /**
7778
- * If we have previously created a transform but currently don't have any,
7779
- * reset transform style to none.
7780
- */
7781
- style.transform = "none";
7775
+ if (!latestValues.transform) {
7776
+ if (hasTransform || transformTemplate) {
7777
+ style.transform = buildTransform(latestValues, state.transform, transformTemplate);
7778
+ }
7779
+ else if (style.transform) {
7780
+ /**
7781
+ * If we have previously created a transform but currently don't have any,
7782
+ * reset transform style to none.
7783
+ */
7784
+ style.transform = "none";
7785
+ }
7782
7786
  }
7783
7787
  /**
7784
7788
  * Build a transformOrigin style. Uses the same defaults as the browser for
@@ -8783,7 +8787,7 @@
8783
8787
  transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `;
8784
8788
  }
8785
8789
  if (latestTransform) {
8786
- const { transformPerspective, rotate, pathRotation, rotateX, rotateY, skewX, skewY, transform: userTransform, } = latestTransform;
8790
+ const { transformPerspective, rotate, pathRotation, rotateX, rotateY, skewX, skewY, } = latestTransform;
8787
8791
  if (transformPerspective)
8788
8792
  transform = `perspective(${transformPerspective}px) ${transform}`;
8789
8793
  if (rotate)
@@ -8799,8 +8803,6 @@
8799
8803
  transform += `skewX(${skewX}deg) `;
8800
8804
  if (skewY)
8801
8805
  transform += `skewY(${skewY}deg) `;
8802
- if (userTransform && userTransform !== "none")
8803
- transform += `${userTransform} `;
8804
8806
  }
8805
8807
  /**
8806
8808
  * Apply scale to match the size of the element to the size we want it.
@@ -9044,14 +9046,14 @@
9044
9046
  */
9045
9047
  const animationTarget = 1000;
9046
9048
  let id = 0;
9047
- function resetDistortingTransform(key, visualElement, values, sharedAnimationValues, resetValue = 0) {
9049
+ function resetDistortingTransform(key, visualElement, values, sharedAnimationValues) {
9048
9050
  const { latestValues } = visualElement;
9049
- // Record the distorting transform and then temporarily reset it
9051
+ // Record the distorting transform and then temporarily set it to 0
9050
9052
  if (latestValues[key]) {
9051
9053
  values[key] = latestValues[key];
9052
- visualElement.setStaticValue(key, resetValue);
9054
+ visualElement.setStaticValue(key, 0);
9053
9055
  if (sharedAnimationValues) {
9054
- sharedAnimationValues[key] = resetValue;
9056
+ sharedAnimationValues[key] = 0;
9055
9057
  }
9056
9058
  }
9057
9059
  }
@@ -9630,7 +9632,7 @@
9630
9632
  if (isResetRequested &&
9631
9633
  this.instance &&
9632
9634
  (hasProjection ||
9633
- hasVisualTransform(this.latestValues) ||
9635
+ hasTransform(this.latestValues) ||
9634
9636
  transformTemplateHasChanged)) {
9635
9637
  resetTransform(this.instance, transformTemplateValue);
9636
9638
  this.shouldResetTransform = false;
@@ -10298,8 +10300,7 @@
10298
10300
  latestValues.rotateY ||
10299
10301
  latestValues.rotateZ ||
10300
10302
  latestValues.skewX ||
10301
- latestValues.skewY ||
10302
- (latestValues.transform && latestValues.transform !== "none")) {
10303
+ latestValues.skewY) {
10303
10304
  hasDistortingTransform = true;
10304
10305
  }
10305
10306
  // If there's no distorting values, we don't need to do any more.
@@ -10309,9 +10310,6 @@
10309
10310
  if (latestValues.z) {
10310
10311
  resetDistortingTransform("z", visualElement, resetValues, this.animationValues);
10311
10312
  }
10312
- if (latestValues.transform && latestValues.transform !== "none") {
10313
- resetDistortingTransform("transform", visualElement, resetValues, this.animationValues, "none");
10314
- }
10315
10313
  // Check the skew and rotate value of all axes and reset to 0
10316
10314
  for (let i = 0; i < transformAxes.length; i++) {
10317
10315
  resetDistortingTransform(`rotate${transformAxes[i]}`, visualElement, resetValues, this.animationValues);
@@ -10361,8 +10359,7 @@
10361
10359
  targetStyle.pointerEvents =
10362
10360
  resolveMotionValue(styleProp?.pointerEvents) || "";
10363
10361
  }
10364
- if (this.hasProjected &&
10365
- !hasVisualTransform(this.latestValues)) {
10362
+ if (this.hasProjected && !hasTransform(this.latestValues)) {
10366
10363
  targetStyle.transform = transformTemplate
10367
10364
  ? transformTemplate({}, "")
10368
10365
  : "none";
@@ -11575,13 +11572,6 @@
11575
11572
  exports.invariant(Boolean(numSubjects), "No valid elements provided.", "no-valid-elements");
11576
11573
  for (let i = 0; i < numSubjects; i++) {
11577
11574
  const thisSubject = subjects[i];
11578
- const createVisualElement = thisSubject instanceof Element
11579
- ? createDOMVisualElement
11580
- : createObjectVisualElement;
11581
- if (!visualElementStore.has(thisSubject)) {
11582
- createVisualElement(thisSubject);
11583
- }
11584
- const visualElement = visualElementStore.get(thisSubject);
11585
11575
  const transition = { ...options };
11586
11576
  /**
11587
11577
  * Resolve stagger function if provided.
@@ -11590,6 +11580,23 @@
11590
11580
  typeof transition.delay === "function") {
11591
11581
  transition.delay = transition.delay(i, numSubjects);
11592
11582
  }
11583
+ const isElement = thisSubject instanceof Element;
11584
+ /**
11585
+ * Registered effects (animate.addEffect) claim non-DOM subjects
11586
+ * before we fall back to treating them as plain objects.
11587
+ */
11588
+ const effect = isElement ? undefined : findEffect(thisSubject);
11589
+ if (effect) {
11590
+ animations.push(...animateEffectSubject(effect, thisSubject, keyframes, transition));
11591
+ continue;
11592
+ }
11593
+ const createVisualElement = isElement
11594
+ ? createDOMVisualElement
11595
+ : createObjectVisualElement;
11596
+ if (!visualElementStore.has(thisSubject)) {
11597
+ createVisualElement(thisSubject);
11598
+ }
11599
+ const visualElement = visualElementStore.get(thisSubject);
11593
11600
  animations.push(...animateTarget(visualElement, { ...keyframes, transition }, {}));
11594
11601
  }
11595
11602
  }
@@ -11675,7 +11682,16 @@
11675
11682
  }
11676
11683
  return scopedAnimate;
11677
11684
  }
11678
- const animate = createScopedAnimate();
11685
+ const animate = Object.assign(createScopedAnimate(), {
11686
+ /**
11687
+ * Register an effect so `animate()` can animate the subjects it
11688
+ * claims, for instance `animate.addEffect(threeEffect)`. The most
11689
+ * recently added effect is tested first. DOM elements are always
11690
+ * animated directly.
11691
+ */
11692
+ addEffect,
11693
+ removeEffect,
11694
+ });
11679
11695
 
11680
11696
  function animateElements(elementOrSelector, keyframes, options, scope) {
11681
11697
  // Gracefully handle null/undefined elements (e.g., from querySelector returning null)
@@ -12450,6 +12466,7 @@
12450
12466
  exports.LayoutAnimationBuilder = LayoutAnimationBuilder;
12451
12467
  exports.MotionGlobalConfig = MotionGlobalConfig;
12452
12468
  exports.MotionValue = MotionValue;
12469
+ exports.MotionValueState = MotionValueState;
12453
12470
  exports.NativeAnimation = NativeAnimation;
12454
12471
  exports.NativeAnimationExtended = NativeAnimationExtended;
12455
12472
  exports.NativeAnimationWrapper = NativeAnimationWrapper;
@@ -12462,6 +12479,7 @@
12462
12479
  exports.acceleratedValues = acceleratedValues;
12463
12480
  exports.addAttrValue = addAttrValue;
12464
12481
  exports.addDomEvent = addDomEvent;
12482
+ exports.addEffect = addEffect;
12465
12483
  exports.addScaleCorrector = addScaleCorrector;
12466
12484
  exports.addStyleValue = addStyleValue;
12467
12485
  exports.addUniqueItem = addUniqueItem;
@@ -12469,6 +12487,7 @@
12469
12487
  exports.alpha = alpha;
12470
12488
  exports.analyseComplexValue = analyseComplexValue;
12471
12489
  exports.animate = animate;
12490
+ exports.animateEffectSubject = animateEffectSubject;
12472
12491
  exports.animateMini = animateMini;
12473
12492
  exports.animateMotionValue = animateMotionValue;
12474
12493
  exports.animateSingleValue = animateSingleValue;
@@ -12541,6 +12560,7 @@
12541
12560
  exports.createAxisDelta = createAxisDelta;
12542
12561
  exports.createBox = createBox;
12543
12562
  exports.createDelta = createDelta;
12563
+ exports.createEffect = createEffect;
12544
12564
  exports.createGeneratorEasing = createGeneratorEasing;
12545
12565
  exports.createProjectionNode = createProjectionNode;
12546
12566
  exports.createRenderBatcher = createRenderBatcher;
@@ -12565,6 +12585,7 @@
12565
12585
  exports.fillOffset = fillOffset;
12566
12586
  exports.fillWildcards = fillWildcards;
12567
12587
  exports.findDimensionValueType = findDimensionValueType;
12588
+ exports.findEffect = findEffect;
12568
12589
  exports.findValueType = findValueType;
12569
12590
  exports.flushKeyframeResolvers = flushKeyframeResolvers;
12570
12591
  exports.followValue = followValue;
@@ -12591,7 +12612,6 @@
12591
12612
  exports.getViewAnimations = getViewAnimations;
12592
12613
  exports.globalProjectionState = globalProjectionState;
12593
12614
  exports.has2DTranslate = has2DTranslate;
12594
- exports.hasIndependentTransform = hasIndependentTransform;
12595
12615
  exports.hasReducedMotionListener = hasReducedMotionListener;
12596
12616
  exports.hasScale = hasScale;
12597
12617
  exports.hasTransform = hasTransform;
@@ -12684,6 +12704,7 @@
12684
12704
  exports.removeAxisDelta = removeAxisDelta;
12685
12705
  exports.removeAxisTransforms = removeAxisTransforms;
12686
12706
  exports.removeBoxTransforms = removeBoxTransforms;
12707
+ exports.removeEffect = removeEffect;
12687
12708
  exports.removeItem = removeItem;
12688
12709
  exports.removePointDelta = removePointDelta;
12689
12710
  exports.renderHTML = renderHTML;