@betterstore/react 0.2.42 → 0.2.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -3274,7 +3274,7 @@ const CheckoutEmbed$2 = {
3274
3274
  day: "den",
3275
3275
  days: "dny",
3276
3276
  estimatedDeliveryDate: "Odhadované datum dodání:",
3277
- shipTo: "Ship to:",
3277
+ address: "Adresa:",
3278
3278
  shipping: "Doprava:",
3279
3279
  title: "Přeprava",
3280
3280
  description: {
@@ -3341,7 +3341,7 @@ const CheckoutEmbed$1 = {
3341
3341
  Shipping: {
3342
3342
  title: "Shipping",
3343
3343
  change: "Change",
3344
- shipTo: "Ship to:",
3344
+ address: "Address:",
3345
3345
  contact: "Contact:",
3346
3346
  button: "Continue to checkout",
3347
3347
  back: "Back to information",
@@ -3943,8 +3943,10 @@ const AnimatePresence = ({ children, custom, initial = true, onExitComplete, pre
3943
3943
  };
3944
3944
 
3945
3945
  const stepsOrder = [
3946
+ "setup", // Compute
3946
3947
  "read", // Read
3947
3948
  "resolveKeyframes", // Write/Read/Write/Read
3949
+ "preUpdate", // Compute
3948
3950
  "update", // Compute
3949
3951
  "preRender", // Compute
3950
3952
  "render", // Write
@@ -4043,9 +4045,7 @@ function createRenderStep(runNextFrame, stepName) {
4043
4045
  return step;
4044
4046
  }
4045
4047
 
4046
- const MotionGlobalConfig = {
4047
- useManualTiming: false,
4048
- };
4048
+ const MotionGlobalConfig = {};
4049
4049
 
4050
4050
  const maxElapsed = 40;
4051
4051
  function createRenderBatcher(scheduleNextBatch, allowKeepAlive) {
@@ -4061,11 +4061,13 @@ function createRenderBatcher(scheduleNextBatch, allowKeepAlive) {
4061
4061
  acc[key] = createRenderStep(flagRunNextFrame, allowKeepAlive ? key : undefined);
4062
4062
  return acc;
4063
4063
  }, {});
4064
- const { read, resolveKeyframes, update, preRender, render, postRender } = steps;
4064
+ const { setup, read, resolveKeyframes, preUpdate, update, preRender, render, postRender, } = steps;
4065
4065
  const processBatch = () => {
4066
- const timestamp = performance.now();
4066
+ const timestamp = MotionGlobalConfig.useManualTiming
4067
+ ? state.timestamp
4068
+ : performance.now();
4067
4069
  runNextFrame = false;
4068
- {
4070
+ if (!MotionGlobalConfig.useManualTiming) {
4069
4071
  state.delta = useDefaultElapsed
4070
4072
  ? 1000 / 60
4071
4073
  : Math.max(Math.min(timestamp - state.timestamp, maxElapsed), 1);
@@ -4073,8 +4075,10 @@ function createRenderBatcher(scheduleNextBatch, allowKeepAlive) {
4073
4075
  state.timestamp = timestamp;
4074
4076
  state.isProcessing = true;
4075
4077
  // Unrolled render loop for better per-frame performance
4078
+ setup.process(state);
4076
4079
  read.process(state);
4077
4080
  resolveKeyframes.process(state);
4081
+ preUpdate.process(state);
4078
4082
  update.process(state);
4079
4083
  preRender.process(state);
4080
4084
  render.process(state);
@@ -4689,7 +4693,7 @@ const transformPropOrder = [
4689
4693
  /**
4690
4694
  * A quick lookup for transform props.
4691
4695
  */
4692
- const transformProps = new Set(transformPropOrder);
4696
+ const transformProps = /*@__PURE__*/ (() => new Set(transformPropOrder))();
4693
4697
 
4694
4698
  function isForcedMotionValue(key, { layout, layoutId }) {
4695
4699
  return (transformProps.has(key) ||
@@ -4731,6 +4735,12 @@ const scale = {
4731
4735
  default: 1,
4732
4736
  };
4733
4737
 
4738
+ const int = {
4739
+ ...number,
4740
+ transform: Math.round,
4741
+ };
4742
+
4743
+ /*#__NO_SIDE_EFFECTS__*/
4734
4744
  const createUnitType = (unit) => ({
4735
4745
  test: (v) => typeof v === "string" && v.endsWith(unit) && v.split(" ").length === 1,
4736
4746
  parse: parseFloat,
@@ -4741,13 +4751,40 @@ const percent = /*@__PURE__*/ createUnitType("%");
4741
4751
  const px = /*@__PURE__*/ createUnitType("px");
4742
4752
  const vh = /*@__PURE__*/ createUnitType("vh");
4743
4753
  const vw = /*@__PURE__*/ createUnitType("vw");
4744
- const progressPercentage = {
4754
+ const progressPercentage = /*@__PURE__*/ (() => ({
4745
4755
  ...percent,
4746
4756
  parse: (v) => percent.parse(v) / 100,
4747
4757
  transform: (v) => percent.transform(v * 100),
4758
+ }))();
4759
+
4760
+ const transformValueTypes = {
4761
+ rotate: degrees,
4762
+ rotateX: degrees,
4763
+ rotateY: degrees,
4764
+ rotateZ: degrees,
4765
+ scale,
4766
+ scaleX: scale,
4767
+ scaleY: scale,
4768
+ scaleZ: scale,
4769
+ skew: degrees,
4770
+ skewX: degrees,
4771
+ skewY: degrees,
4772
+ distance: px,
4773
+ translateX: px,
4774
+ translateY: px,
4775
+ translateZ: px,
4776
+ x: px,
4777
+ y: px,
4778
+ z: px,
4779
+ perspective: px,
4780
+ transformPerspective: px,
4781
+ opacity: alpha,
4782
+ originX: progressPercentage,
4783
+ originY: progressPercentage,
4784
+ originZ: px,
4748
4785
  };
4749
4786
 
4750
- const browserNumberValueTypes = {
4787
+ const numberValueTypes = {
4751
4788
  // Border props
4752
4789
  borderWidth: px,
4753
4790
  borderTopWidth: px,
@@ -4783,45 +4820,8 @@ const browserNumberValueTypes = {
4783
4820
  // Misc
4784
4821
  backgroundPositionX: px,
4785
4822
  backgroundPositionY: px,
4786
- };
4787
-
4788
- const transformValueTypes = {
4789
- rotate: degrees,
4790
- rotateX: degrees,
4791
- rotateY: degrees,
4792
- rotateZ: degrees,
4793
- scale,
4794
- scaleX: scale,
4795
- scaleY: scale,
4796
- scaleZ: scale,
4797
- skew: degrees,
4798
- skewX: degrees,
4799
- skewY: degrees,
4800
- distance: px,
4801
- translateX: px,
4802
- translateY: px,
4803
- translateZ: px,
4804
- x: px,
4805
- y: px,
4806
- z: px,
4807
- perspective: px,
4808
- transformPerspective: px,
4809
- opacity: alpha,
4810
- originX: progressPercentage,
4811
- originY: progressPercentage,
4812
- originZ: px,
4813
- };
4814
-
4815
- const int = {
4816
- ...number,
4817
- transform: Math.round,
4818
- };
4819
-
4820
- const numberValueTypes = {
4821
- ...browserNumberValueTypes,
4822
4823
  ...transformValueTypes,
4823
4824
  zIndex: int,
4824
- size: px,
4825
4825
  // SVG
4826
4826
  fillOpacity: alpha,
4827
4827
  strokeOpacity: alpha,
@@ -5234,28 +5234,13 @@ function resolveVariantFromProps(props, definition, custom, visualElement) {
5234
5234
  return definition;
5235
5235
  }
5236
5236
 
5237
- const isKeyframesTarget = (v) => {
5238
- return Array.isArray(v);
5239
- };
5240
-
5241
- const isCustomValue = (v) => {
5242
- return Boolean(v && typeof v === "object" && v.mix && v.toValue);
5243
- };
5244
- const resolveFinalValueInKeyframes = (v) => {
5245
- // TODO maybe throw if v.length - 1 is placeholder token?
5246
- return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
5247
- };
5248
-
5249
5237
  /**
5250
5238
  * If the provided value is a MotionValue, this returns the actual value, otherwise just the value itself
5251
5239
  *
5252
5240
  * TODO: Remove and move to library
5253
5241
  */
5254
5242
  function resolveMotionValue(value) {
5255
- const unwrappedValue = isMotionValue(value) ? value.get() : value;
5256
- return isCustomValue(unwrappedValue)
5257
- ? unwrappedValue.toValue()
5258
- : unwrappedValue;
5243
+ return isMotionValue(value) ? value.get() : value;
5259
5244
  }
5260
5245
 
5261
5246
  function makeState({ scrapeMotionValuesFromProps, createRenderState, onUpdate, }, props, context, presenceContext) {
@@ -5501,15 +5486,9 @@ function resolveVariant(visualElement, definition, custom) {
5501
5486
  return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, visualElement);
5502
5487
  }
5503
5488
 
5504
- const positionalKeys = new Set([
5505
- "width",
5506
- "height",
5507
- "top",
5508
- "left",
5509
- "right",
5510
- "bottom",
5511
- ...transformPropOrder,
5512
- ]);
5489
+ const isKeyframesTarget = (v) => {
5490
+ return Array.isArray(v);
5491
+ };
5513
5492
 
5514
5493
  let now;
5515
5494
  function clearTime() {
@@ -5620,7 +5599,7 @@ class MotionValue {
5620
5599
  * This will be replaced by the build step with the latest version number.
5621
5600
  * When MotionValues are provided to motion components, warn if versions are mixed.
5622
5601
  */
5623
- this.version = "12.7.4";
5602
+ this.version = "12.8.0";
5624
5603
  /**
5625
5604
  * Tracks whether this value can output a velocity. Currently this is only true
5626
5605
  * if the value is numerical, but we might be able to widen the scope here and support
@@ -5764,6 +5743,8 @@ class MotionValue {
5764
5743
  * @public
5765
5744
  */
5766
5745
  set(v, render = true) {
5746
+ if (v === "none")
5747
+ console.trace();
5767
5748
  if (!render || !this.passiveEffect) {
5768
5749
  this.updateAndNotify(v, render);
5769
5750
  }
@@ -5884,6 +5865,7 @@ class MotionValue {
5884
5865
  * @public
5885
5866
  */
5886
5867
  destroy() {
5868
+ this.events.destroy?.notify();
5887
5869
  this.clearListeners();
5888
5870
  this.stop();
5889
5871
  if (this.stopPassiveEffect) {
@@ -5907,6 +5889,10 @@ function setMotionValue(visualElement, key, value) {
5907
5889
  visualElement.addValue(key, motionValue(value));
5908
5890
  }
5909
5891
  }
5892
+ function resolveFinalValueInKeyframes(v) {
5893
+ // TODO maybe throw if v.length - 1 is placeholder token?
5894
+ return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
5895
+ }
5910
5896
  function setTarget(visualElement, definition) {
5911
5897
  const resolved = resolveVariant(visualElement, definition);
5912
5898
  let { transitionEnd = {}, transition = {}, ...target } = resolved || {};
@@ -5941,89 +5927,79 @@ function getOptimisedAppearId(visualElement) {
5941
5927
  return visualElement.props[optimizedAppearDataAttribute];
5942
5928
  }
5943
5929
 
5944
- /*
5945
- Bezier function generator
5946
- This has been modified from Gaëtan Renaudeau's BezierEasing
5947
- https://github.com/gre/bezier-easing/blob/master/src/index.js
5948
- https://github.com/gre/bezier-easing/blob/master/LICENSE
5949
-
5950
- I've removed the newtonRaphsonIterate algo because in benchmarking it
5951
- wasn't noticiably faster than binarySubdivision, indeed removing it
5952
- usually improved times, depending on the curve.
5953
- I also removed the lookup table, as for the added bundle size and loop we're
5954
- only cutting ~4 or so subdivision iterations. I bumped the max iterations up
5955
- to 12 to compensate and this still tended to be faster for no perceivable
5956
- loss in accuracy.
5957
- Usage
5958
- const easeOut = cubicBezier(.17,.67,.83,.67);
5959
- const x = easeOut(0.5); // returns 0.627...
5960
- */
5961
- // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
5962
- const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) *
5963
- t;
5964
- const subdivisionPrecision = 0.0000001;
5965
- const subdivisionMaxIterations = 12;
5966
- function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
5967
- let currentX;
5968
- let currentT;
5969
- let i = 0;
5970
- do {
5971
- currentT = lowerBound + (upperBound - lowerBound) / 2.0;
5972
- currentX = calcBezier(currentT, mX1, mX2) - x;
5973
- if (currentX > 0.0) {
5974
- upperBound = currentT;
5975
- }
5976
- else {
5977
- lowerBound = currentT;
5978
- }
5979
- } while (Math.abs(currentX) > subdivisionPrecision &&
5980
- ++i < subdivisionMaxIterations);
5981
- return currentT;
5982
- }
5983
- function cubicBezier(mX1, mY1, mX2, mY2) {
5984
- // If this is a linear gradient, return linear easing
5985
- if (mX1 === mY1 && mX2 === mY2)
5986
- return noop;
5987
- const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
5988
- // If animation is at start/end, return t without easing
5989
- return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
5930
+ const isNotNull$1 = (value) => value !== null;
5931
+ function getFinalKeyframe$1(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) {
5932
+ const resolvedKeyframes = keyframes.filter(isNotNull$1);
5933
+ const index = repeat && repeatType !== "loop" && repeat % 2 === 1
5934
+ ? 0
5935
+ : resolvedKeyframes.length - 1;
5936
+ return resolvedKeyframes[index]
5937
+ ;
5990
5938
  }
5991
5939
 
5992
- // Accepts an easing function and returns a new one that outputs mirrored values for
5993
- // the second half of the animation. Turns easeIn into easeInOut.
5994
- const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
5995
-
5996
- // Accepts an easing function and returns a new one that outputs reversed values.
5997
- // Turns easeIn into easeOut.
5998
- const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
5999
-
6000
- const backOut = /*@__PURE__*/ cubicBezier(0.33, 1.53, 0.69, 0.99);
6001
- const backIn = /*@__PURE__*/ reverseEasing(backOut);
6002
- const backInOut = /*@__PURE__*/ mirrorEasing(backIn);
6003
-
6004
- const anticipate = (p) => (p *= 2) < 1 ? 0.5 * backIn(p) : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
6005
-
6006
- const circIn = (p) => 1 - Math.sin(Math.acos(p));
6007
- const circOut = reverseEasing(circIn);
6008
- const circInOut = mirrorEasing(circIn);
6009
-
5940
+ const underDampedSpring = {
5941
+ type: "spring",
5942
+ stiffness: 500,
5943
+ damping: 25,
5944
+ restSpeed: 10,
5945
+ };
5946
+ const criticallyDampedSpring = (target) => ({
5947
+ type: "spring",
5948
+ stiffness: 550,
5949
+ damping: target === 0 ? 2 * Math.sqrt(550) : 30,
5950
+ restSpeed: 10,
5951
+ });
5952
+ const keyframesTransition = {
5953
+ type: "keyframes",
5954
+ duration: 0.8,
5955
+ };
6010
5956
  /**
6011
- * Check if the value is a zero value string like "0px" or "0%"
5957
+ * Default easing curve is a slightly shallower version of
5958
+ * the default browser easing curve.
6012
5959
  */
6013
- const isZeroValueString = (v) => /^0[^.\s]+$/u.test(v);
6014
-
6015
- function isNone(value) {
6016
- if (typeof value === "number") {
6017
- return value === 0;
6018
- }
6019
- else if (value !== null) {
6020
- return value === "none" || value === "0" || isZeroValueString(value);
5960
+ const ease = {
5961
+ type: "keyframes",
5962
+ ease: [0.25, 0.1, 0.35, 1],
5963
+ duration: 0.3,
5964
+ };
5965
+ const getDefaultTransition = (valueKey, { keyframes }) => {
5966
+ if (keyframes.length > 2) {
5967
+ return keyframesTransition;
6021
5968
  }
6022
- else {
6023
- return true;
5969
+ else if (transformProps.has(valueKey)) {
5970
+ return valueKey.startsWith("scale")
5971
+ ? criticallyDampedSpring(keyframes[1])
5972
+ : underDampedSpring;
6024
5973
  }
5974
+ return ease;
5975
+ };
5976
+
5977
+ /**
5978
+ * Decide whether a transition is defined on a given Transition.
5979
+ * This filters out orchestration options and returns true
5980
+ * if any options are left.
5981
+ */
5982
+ function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, elapsed, ...transition }) {
5983
+ return !!Object.keys(transition).length;
5984
+ }
5985
+
5986
+ function getValueTransition(transition, key) {
5987
+ return (transition?.[key] ??
5988
+ transition?.["default"] ??
5989
+ transition);
6025
5990
  }
6026
5991
 
5992
+ /**
5993
+ * Converts seconds to milliseconds
5994
+ *
5995
+ * @param seconds - Time in seconds.
5996
+ * @return milliseconds - Converted time in milliseconds.
5997
+ */
5998
+ /*#__NO_SIDE_EFFECTS__*/
5999
+ const secondsToMilliseconds = (seconds) => seconds * 1000;
6000
+ /*#__NO_SIDE_EFFECTS__*/
6001
+ const millisecondsToSeconds = (milliseconds) => milliseconds / 1000;
6002
+
6027
6003
  // If this number is a decimal, make it just five decimal places
6028
6004
  // to avoid exponents
6029
6005
  const sanitize = (v) => Math.round(v * 100000) / 100000;
@@ -6240,765 +6216,6 @@ const complex = {
6240
6216
  getAnimatableNone: getAnimatableNone$1,
6241
6217
  };
6242
6218
 
6243
- /**
6244
- * Properties that should default to 1 or 100%
6245
- */
6246
- const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]);
6247
- function applyDefaultFilter(v) {
6248
- const [name, value] = v.slice(0, -1).split("(");
6249
- if (name === "drop-shadow")
6250
- return v;
6251
- const [number] = value.match(floatRegex) || [];
6252
- if (!number)
6253
- return v;
6254
- const unit = value.replace(number, "");
6255
- let defaultValue = maxDefaults.has(name) ? 1 : 0;
6256
- if (number !== value)
6257
- defaultValue *= 100;
6258
- return name + "(" + defaultValue + unit + ")";
6259
- }
6260
- const functionRegex = /\b([a-z-]*)\(.*?\)/gu;
6261
- const filter = {
6262
- ...complex,
6263
- getAnimatableNone: (v) => {
6264
- const functions = v.match(functionRegex);
6265
- return functions ? functions.map(applyDefaultFilter).join(" ") : v;
6266
- },
6267
- };
6268
-
6269
- /**
6270
- * A map of default value types for common values
6271
- */
6272
- const defaultValueTypes = {
6273
- ...numberValueTypes,
6274
- // Color props
6275
- color,
6276
- backgroundColor: color,
6277
- outlineColor: color,
6278
- fill: color,
6279
- stroke: color,
6280
- // Border props
6281
- borderColor: color,
6282
- borderTopColor: color,
6283
- borderRightColor: color,
6284
- borderBottomColor: color,
6285
- borderLeftColor: color,
6286
- filter,
6287
- WebkitFilter: filter,
6288
- };
6289
- /**
6290
- * Gets the default ValueType for the provided value key
6291
- */
6292
- const getDefaultValueType = (key) => defaultValueTypes[key];
6293
-
6294
- function getAnimatableNone(key, value) {
6295
- let defaultValueType = getDefaultValueType(key);
6296
- if (defaultValueType !== filter)
6297
- defaultValueType = complex;
6298
- // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
6299
- return defaultValueType.getAnimatableNone
6300
- ? defaultValueType.getAnimatableNone(value)
6301
- : undefined;
6302
- }
6303
-
6304
- /**
6305
- * If we encounter keyframes like "none" or "0" and we also have keyframes like
6306
- * "#fff" or "200px 200px" we want to find a keyframe to serve as a template for
6307
- * the "none" keyframes. In this case "#fff" or "200px 200px" - then these get turned into
6308
- * zero equivalents, i.e. "#fff0" or "0px 0px".
6309
- */
6310
- const invalidTemplates = new Set(["auto", "none", "0"]);
6311
- function makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name) {
6312
- let i = 0;
6313
- let animatableTemplate = undefined;
6314
- while (i < unresolvedKeyframes.length && !animatableTemplate) {
6315
- const keyframe = unresolvedKeyframes[i];
6316
- if (typeof keyframe === "string" &&
6317
- !invalidTemplates.has(keyframe) &&
6318
- analyseComplexValue(keyframe).values.length) {
6319
- animatableTemplate = unresolvedKeyframes[i];
6320
- }
6321
- i++;
6322
- }
6323
- if (animatableTemplate && name) {
6324
- for (const noneIndex of noneKeyframeIndexes) {
6325
- unresolvedKeyframes[noneIndex] = getAnimatableNone(name, animatableTemplate);
6326
- }
6327
- }
6328
- }
6329
-
6330
- const radToDeg = (rad) => (rad * 180) / Math.PI;
6331
- const rotate = (v) => {
6332
- const angle = radToDeg(Math.atan2(v[1], v[0]));
6333
- return rebaseAngle(angle);
6334
- };
6335
- const matrix2dParsers = {
6336
- x: 4,
6337
- y: 5,
6338
- translateX: 4,
6339
- translateY: 5,
6340
- scaleX: 0,
6341
- scaleY: 3,
6342
- scale: (v) => (Math.abs(v[0]) + Math.abs(v[3])) / 2,
6343
- rotate,
6344
- rotateZ: rotate,
6345
- skewX: (v) => radToDeg(Math.atan(v[1])),
6346
- skewY: (v) => radToDeg(Math.atan(v[2])),
6347
- skew: (v) => (Math.abs(v[1]) + Math.abs(v[2])) / 2,
6348
- };
6349
- const rebaseAngle = (angle) => {
6350
- angle = angle % 360;
6351
- if (angle < 0)
6352
- angle += 360;
6353
- return angle;
6354
- };
6355
- const rotateZ = rotate;
6356
- const scaleX = (v) => Math.sqrt(v[0] * v[0] + v[1] * v[1]);
6357
- const scaleY = (v) => Math.sqrt(v[4] * v[4] + v[5] * v[5]);
6358
- const matrix3dParsers = {
6359
- x: 12,
6360
- y: 13,
6361
- z: 14,
6362
- translateX: 12,
6363
- translateY: 13,
6364
- translateZ: 14,
6365
- scaleX,
6366
- scaleY,
6367
- scale: (v) => (scaleX(v) + scaleY(v)) / 2,
6368
- rotateX: (v) => rebaseAngle(radToDeg(Math.atan2(v[6], v[5]))),
6369
- rotateY: (v) => rebaseAngle(radToDeg(Math.atan2(-v[2], v[0]))),
6370
- rotateZ,
6371
- rotate: rotateZ,
6372
- skewX: (v) => radToDeg(Math.atan(v[4])),
6373
- skewY: (v) => radToDeg(Math.atan(v[1])),
6374
- skew: (v) => (Math.abs(v[1]) + Math.abs(v[4])) / 2,
6375
- };
6376
- function defaultTransformValue(name) {
6377
- return name.includes("scale") ? 1 : 0;
6378
- }
6379
- function parseValueFromTransform(transform, name) {
6380
- if (!transform || transform === "none") {
6381
- return defaultTransformValue(name);
6382
- }
6383
- const matrix3dMatch = transform.match(/^matrix3d\(([-\d.e\s,]+)\)$/u);
6384
- let parsers;
6385
- let match;
6386
- if (matrix3dMatch) {
6387
- parsers = matrix3dParsers;
6388
- match = matrix3dMatch;
6389
- }
6390
- else {
6391
- const matrix2dMatch = transform.match(/^matrix\(([-\d.e\s,]+)\)$/u);
6392
- parsers = matrix2dParsers;
6393
- match = matrix2dMatch;
6394
- }
6395
- if (!match) {
6396
- return defaultTransformValue(name);
6397
- }
6398
- const valueParser = parsers[name];
6399
- const values = match[1].split(",").map(convertTransformToNumber);
6400
- return typeof valueParser === "function"
6401
- ? valueParser(values)
6402
- : values[valueParser];
6403
- }
6404
- const readTransformValue = (instance, name) => {
6405
- const { transform = "none" } = getComputedStyle(instance);
6406
- return parseValueFromTransform(transform, name);
6407
- };
6408
- function convertTransformToNumber(value) {
6409
- return parseFloat(value.trim());
6410
- }
6411
-
6412
- const isNumOrPxType = (v) => v === number || v === px;
6413
- const transformKeys = new Set(["x", "y", "z"]);
6414
- const nonTranslationalTransformKeys = transformPropOrder.filter((key) => !transformKeys.has(key));
6415
- function removeNonTranslationalTransform(visualElement) {
6416
- const removedTransforms = [];
6417
- nonTranslationalTransformKeys.forEach((key) => {
6418
- const value = visualElement.getValue(key);
6419
- if (value !== undefined) {
6420
- removedTransforms.push([key, value.get()]);
6421
- value.set(key.startsWith("scale") ? 1 : 0);
6422
- }
6423
- });
6424
- return removedTransforms;
6425
- }
6426
- const positionalValues = {
6427
- // Dimensions
6428
- width: ({ x }, { paddingLeft = "0", paddingRight = "0" }) => x.max - x.min - parseFloat(paddingLeft) - parseFloat(paddingRight),
6429
- height: ({ y }, { paddingTop = "0", paddingBottom = "0" }) => y.max - y.min - parseFloat(paddingTop) - parseFloat(paddingBottom),
6430
- top: (_bbox, { top }) => parseFloat(top),
6431
- left: (_bbox, { left }) => parseFloat(left),
6432
- bottom: ({ y }, { top }) => parseFloat(top) + (y.max - y.min),
6433
- right: ({ x }, { left }) => parseFloat(left) + (x.max - x.min),
6434
- // Transform
6435
- x: (_bbox, { transform }) => parseValueFromTransform(transform, "x"),
6436
- y: (_bbox, { transform }) => parseValueFromTransform(transform, "y"),
6437
- };
6438
- // Alias translate longform names
6439
- positionalValues.translateX = positionalValues.x;
6440
- positionalValues.translateY = positionalValues.y;
6441
-
6442
- const toResolve = new Set();
6443
- let isScheduled = false;
6444
- let anyNeedsMeasurement = false;
6445
- function measureAllKeyframes() {
6446
- if (anyNeedsMeasurement) {
6447
- const resolversToMeasure = Array.from(toResolve).filter((resolver) => resolver.needsMeasurement);
6448
- const elementsToMeasure = new Set(resolversToMeasure.map((resolver) => resolver.element));
6449
- const transformsToRestore = new Map();
6450
- /**
6451
- * Write pass
6452
- * If we're measuring elements we want to remove bounding box-changing transforms.
6453
- */
6454
- elementsToMeasure.forEach((element) => {
6455
- const removedTransforms = removeNonTranslationalTransform(element);
6456
- if (!removedTransforms.length)
6457
- return;
6458
- transformsToRestore.set(element, removedTransforms);
6459
- element.render();
6460
- });
6461
- // Read
6462
- resolversToMeasure.forEach((resolver) => resolver.measureInitialState());
6463
- // Write
6464
- elementsToMeasure.forEach((element) => {
6465
- element.render();
6466
- const restore = transformsToRestore.get(element);
6467
- if (restore) {
6468
- restore.forEach(([key, value]) => {
6469
- element.getValue(key)?.set(value);
6470
- });
6471
- }
6472
- });
6473
- // Read
6474
- resolversToMeasure.forEach((resolver) => resolver.measureEndState());
6475
- // Write
6476
- resolversToMeasure.forEach((resolver) => {
6477
- if (resolver.suspendedScrollY !== undefined) {
6478
- window.scrollTo(0, resolver.suspendedScrollY);
6479
- }
6480
- });
6481
- }
6482
- anyNeedsMeasurement = false;
6483
- isScheduled = false;
6484
- toResolve.forEach((resolver) => resolver.complete());
6485
- toResolve.clear();
6486
- }
6487
- function readAllKeyframes() {
6488
- toResolve.forEach((resolver) => {
6489
- resolver.readKeyframes();
6490
- if (resolver.needsMeasurement) {
6491
- anyNeedsMeasurement = true;
6492
- }
6493
- });
6494
- }
6495
- function flushKeyframeResolvers() {
6496
- readAllKeyframes();
6497
- measureAllKeyframes();
6498
- }
6499
- class KeyframeResolver {
6500
- constructor(unresolvedKeyframes, onComplete, name, motionValue, element, isAsync = false) {
6501
- /**
6502
- * Track whether this resolver has completed. Once complete, it never
6503
- * needs to attempt keyframe resolution again.
6504
- */
6505
- this.isComplete = false;
6506
- /**
6507
- * Track whether this resolver is async. If it is, it'll be added to the
6508
- * resolver queue and flushed in the next frame. Resolvers that aren't going
6509
- * to trigger read/write thrashing don't need to be async.
6510
- */
6511
- this.isAsync = false;
6512
- /**
6513
- * Track whether this resolver needs to perform a measurement
6514
- * to resolve its keyframes.
6515
- */
6516
- this.needsMeasurement = false;
6517
- /**
6518
- * Track whether this resolver is currently scheduled to resolve
6519
- * to allow it to be cancelled and resumed externally.
6520
- */
6521
- this.isScheduled = false;
6522
- this.unresolvedKeyframes = [...unresolvedKeyframes];
6523
- this.onComplete = onComplete;
6524
- this.name = name;
6525
- this.motionValue = motionValue;
6526
- this.element = element;
6527
- this.isAsync = isAsync;
6528
- }
6529
- scheduleResolve() {
6530
- this.isScheduled = true;
6531
- if (this.isAsync) {
6532
- toResolve.add(this);
6533
- if (!isScheduled) {
6534
- isScheduled = true;
6535
- frame.read(readAllKeyframes);
6536
- frame.resolveKeyframes(measureAllKeyframes);
6537
- }
6538
- }
6539
- else {
6540
- this.readKeyframes();
6541
- this.complete();
6542
- }
6543
- }
6544
- readKeyframes() {
6545
- const { unresolvedKeyframes, name, element, motionValue } = this;
6546
- /**
6547
- * If a keyframe is null, we hydrate it either by reading it from
6548
- * the instance, or propagating from previous keyframes.
6549
- */
6550
- for (let i = 0; i < unresolvedKeyframes.length; i++) {
6551
- if (unresolvedKeyframes[i] === null) {
6552
- /**
6553
- * If the first keyframe is null, we need to find its value by sampling the element
6554
- */
6555
- if (i === 0) {
6556
- const currentValue = motionValue?.get();
6557
- const finalKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
6558
- if (currentValue !== undefined) {
6559
- unresolvedKeyframes[0] = currentValue;
6560
- }
6561
- else if (element && name) {
6562
- const valueAsRead = element.readValue(name, finalKeyframe);
6563
- if (valueAsRead !== undefined && valueAsRead !== null) {
6564
- unresolvedKeyframes[0] = valueAsRead;
6565
- }
6566
- }
6567
- if (unresolvedKeyframes[0] === undefined) {
6568
- unresolvedKeyframes[0] = finalKeyframe;
6569
- }
6570
- if (motionValue && currentValue === undefined) {
6571
- motionValue.set(unresolvedKeyframes[0]);
6572
- }
6573
- }
6574
- else {
6575
- unresolvedKeyframes[i] = unresolvedKeyframes[i - 1];
6576
- }
6577
- }
6578
- }
6579
- }
6580
- setFinalKeyframe() { }
6581
- measureInitialState() { }
6582
- renderEndStyles() { }
6583
- measureEndState() { }
6584
- complete() {
6585
- this.isComplete = true;
6586
- this.onComplete(this.unresolvedKeyframes, this.finalKeyframe);
6587
- toResolve.delete(this);
6588
- }
6589
- cancel() {
6590
- if (!this.isComplete) {
6591
- this.isScheduled = false;
6592
- toResolve.delete(this);
6593
- }
6594
- }
6595
- resume() {
6596
- if (!this.isComplete)
6597
- this.scheduleResolve();
6598
- }
6599
- }
6600
-
6601
- /**
6602
- * Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1"
6603
- */
6604
- const isNumericalString = (v) => /^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(v);
6605
-
6606
- /**
6607
- * Parse Framer's special CSS variable format into a CSS token and a fallback.
6608
- *
6609
- * ```
6610
- * `var(--foo, #fff)` => [`--foo`, '#fff']
6611
- * ```
6612
- *
6613
- * @param current
6614
- */
6615
- const splitCSSVariableRegex =
6616
- // eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive, as it can match a lot of words
6617
- /^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u;
6618
- function parseCSSVariable(current) {
6619
- const match = splitCSSVariableRegex.exec(current);
6620
- if (!match)
6621
- return [,];
6622
- const [, token1, token2, fallback] = match;
6623
- return [`--${token1 ?? token2}`, fallback];
6624
- }
6625
- const maxDepth = 4;
6626
- function getVariableValue(current, element, depth = 1) {
6627
- invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`);
6628
- const [token, fallback] = parseCSSVariable(current);
6629
- // No CSS variable detected
6630
- if (!token)
6631
- return;
6632
- // Attempt to read this CSS variable off the element
6633
- const resolved = window.getComputedStyle(element).getPropertyValue(token);
6634
- if (resolved) {
6635
- const trimmed = resolved.trim();
6636
- return isNumericalString(trimmed) ? parseFloat(trimmed) : trimmed;
6637
- }
6638
- return isCSSVariableToken(fallback)
6639
- ? getVariableValue(fallback, element, depth + 1)
6640
- : fallback;
6641
- }
6642
-
6643
- /**
6644
- * Tests a provided value against a ValueType
6645
- */
6646
- const testValueType = (v) => (type) => type.test(v);
6647
-
6648
- /**
6649
- * ValueType for "auto"
6650
- */
6651
- const auto = {
6652
- test: (v) => v === "auto",
6653
- parse: (v) => v,
6654
- };
6655
-
6656
- /**
6657
- * A list of value types commonly used for dimensions
6658
- */
6659
- const dimensionValueTypes = [number, px, percent, degrees, vw, vh, auto];
6660
- /**
6661
- * Tests a dimensional value against the list of dimension ValueTypes
6662
- */
6663
- const findDimensionValueType = (v) => dimensionValueTypes.find(testValueType(v));
6664
-
6665
- class DOMKeyframesResolver extends KeyframeResolver {
6666
- constructor(unresolvedKeyframes, onComplete, name, motionValue, element) {
6667
- super(unresolvedKeyframes, onComplete, name, motionValue, element, true);
6668
- }
6669
- readKeyframes() {
6670
- const { unresolvedKeyframes, element, name } = this;
6671
- if (!element || !element.current)
6672
- return;
6673
- super.readKeyframes();
6674
- /**
6675
- * If any keyframe is a CSS variable, we need to find its value by sampling the element
6676
- */
6677
- for (let i = 0; i < unresolvedKeyframes.length; i++) {
6678
- let keyframe = unresolvedKeyframes[i];
6679
- if (typeof keyframe === "string") {
6680
- keyframe = keyframe.trim();
6681
- if (isCSSVariableToken(keyframe)) {
6682
- const resolved = getVariableValue(keyframe, element.current);
6683
- if (resolved !== undefined) {
6684
- unresolvedKeyframes[i] = resolved;
6685
- }
6686
- if (i === unresolvedKeyframes.length - 1) {
6687
- this.finalKeyframe = keyframe;
6688
- }
6689
- }
6690
- }
6691
- }
6692
- /**
6693
- * Resolve "none" values. We do this potentially twice - once before and once after measuring keyframes.
6694
- * This could be seen as inefficient but it's a trade-off to avoid measurements in more situations, which
6695
- * have a far bigger performance impact.
6696
- */
6697
- this.resolveNoneKeyframes();
6698
- /**
6699
- * Check to see if unit type has changed. If so schedule jobs that will
6700
- * temporarily set styles to the destination keyframes.
6701
- * Skip if we have more than two keyframes or this isn't a positional value.
6702
- * TODO: We can throw if there are multiple keyframes and the value type changes.
6703
- */
6704
- if (!positionalKeys.has(name) || unresolvedKeyframes.length !== 2) {
6705
- return;
6706
- }
6707
- const [origin, target] = unresolvedKeyframes;
6708
- const originType = findDimensionValueType(origin);
6709
- const targetType = findDimensionValueType(target);
6710
- /**
6711
- * Either we don't recognise these value types or we can animate between them.
6712
- */
6713
- if (originType === targetType)
6714
- return;
6715
- /**
6716
- * If both values are numbers or pixels, we can animate between them by
6717
- * converting them to numbers.
6718
- */
6719
- if (isNumOrPxType(originType) && isNumOrPxType(targetType)) {
6720
- for (let i = 0; i < unresolvedKeyframes.length; i++) {
6721
- const value = unresolvedKeyframes[i];
6722
- if (typeof value === "string") {
6723
- unresolvedKeyframes[i] = parseFloat(value);
6724
- }
6725
- }
6726
- }
6727
- else {
6728
- /**
6729
- * Else, the only way to resolve this is by measuring the element.
6730
- */
6731
- this.needsMeasurement = true;
6732
- }
6733
- }
6734
- resolveNoneKeyframes() {
6735
- const { unresolvedKeyframes, name } = this;
6736
- const noneKeyframeIndexes = [];
6737
- for (let i = 0; i < unresolvedKeyframes.length; i++) {
6738
- if (isNone(unresolvedKeyframes[i])) {
6739
- noneKeyframeIndexes.push(i);
6740
- }
6741
- }
6742
- if (noneKeyframeIndexes.length) {
6743
- makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name);
6744
- }
6745
- }
6746
- measureInitialState() {
6747
- const { element, unresolvedKeyframes, name } = this;
6748
- if (!element || !element.current)
6749
- return;
6750
- if (name === "height") {
6751
- this.suspendedScrollY = window.pageYOffset;
6752
- }
6753
- this.measuredOrigin = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
6754
- unresolvedKeyframes[0] = this.measuredOrigin;
6755
- // Set final key frame to measure after next render
6756
- const measureKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
6757
- if (measureKeyframe !== undefined) {
6758
- element.getValue(name, measureKeyframe).jump(measureKeyframe, false);
6759
- }
6760
- }
6761
- measureEndState() {
6762
- const { element, name, unresolvedKeyframes } = this;
6763
- if (!element || !element.current)
6764
- return;
6765
- const value = element.getValue(name);
6766
- value && value.jump(this.measuredOrigin, false);
6767
- const finalKeyframeIndex = unresolvedKeyframes.length - 1;
6768
- const finalKeyframe = unresolvedKeyframes[finalKeyframeIndex];
6769
- unresolvedKeyframes[finalKeyframeIndex] = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
6770
- if (finalKeyframe !== null && this.finalKeyframe === undefined) {
6771
- this.finalKeyframe = finalKeyframe;
6772
- }
6773
- // If we removed transform values, reapply them before the next render
6774
- if (this.removedTransforms?.length) {
6775
- this.removedTransforms.forEach(([unsetTransformName, unsetTransformValue]) => {
6776
- element
6777
- .getValue(unsetTransformName)
6778
- .set(unsetTransformValue);
6779
- });
6780
- }
6781
- this.resolveNoneKeyframes();
6782
- }
6783
- }
6784
-
6785
- /**
6786
- * Check if a value is animatable. Examples:
6787
- *
6788
- * ✅: 100, "100px", "#fff"
6789
- * ❌: "block", "url(2.jpg)"
6790
- * @param value
6791
- *
6792
- * @internal
6793
- */
6794
- const isAnimatable = (value, name) => {
6795
- // If the list of keys tat might be non-animatable grows, replace with Set
6796
- if (name === "zIndex")
6797
- return false;
6798
- // If it's a number or a keyframes array, we can animate it. We might at some point
6799
- // need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
6800
- // but for now lets leave it like this for performance reasons
6801
- if (typeof value === "number" || Array.isArray(value))
6802
- return true;
6803
- if (typeof value === "string" && // It's animatable if we have a string
6804
- (complex.test(value) || value === "0") && // And it contains numbers and/or colors
6805
- !value.startsWith("url(") // Unless it starts with "url("
6806
- ) {
6807
- return true;
6808
- }
6809
- return false;
6810
- };
6811
-
6812
- function isGenerator(type) {
6813
- return typeof type === "function" && "applyToOptions" in type;
6814
- }
6815
-
6816
- function hasKeyframesChanged(keyframes) {
6817
- const current = keyframes[0];
6818
- if (keyframes.length === 1)
6819
- return true;
6820
- for (let i = 0; i < keyframes.length; i++) {
6821
- if (keyframes[i] !== current)
6822
- return true;
6823
- }
6824
- }
6825
- function canAnimate(keyframes, name, type, velocity) {
6826
- /**
6827
- * Check if we're able to animate between the start and end keyframes,
6828
- * and throw a warning if we're attempting to animate between one that's
6829
- * animatable and another that isn't.
6830
- */
6831
- const originKeyframe = keyframes[0];
6832
- if (originKeyframe === null)
6833
- return false;
6834
- /**
6835
- * These aren't traditionally animatable but we do support them.
6836
- * In future we could look into making this more generic or replacing
6837
- * this function with mix() === mixImmediate
6838
- */
6839
- if (name === "display" || name === "visibility")
6840
- return true;
6841
- const targetKeyframe = keyframes[keyframes.length - 1];
6842
- const isOriginAnimatable = isAnimatable(originKeyframe, name);
6843
- const isTargetAnimatable = isAnimatable(targetKeyframe, name);
6844
- warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${name} from "${originKeyframe}" to "${targetKeyframe}". ${originKeyframe} is not an animatable value - to enable this animation set ${originKeyframe} to a value animatable to ${targetKeyframe} via the \`style\` property.`);
6845
- // Always skip if any of these are true
6846
- if (!isOriginAnimatable || !isTargetAnimatable) {
6847
- return false;
6848
- }
6849
- return (hasKeyframesChanged(keyframes) ||
6850
- ((type === "spring" || isGenerator(type)) && velocity));
6851
- }
6852
-
6853
- const isNotNull = (value) => value !== null;
6854
- function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) {
6855
- const resolvedKeyframes = keyframes.filter(isNotNull);
6856
- const index = repeat && repeatType !== "loop" && repeat % 2 === 1
6857
- ? 0
6858
- : resolvedKeyframes.length - 1;
6859
- return !index || finalKeyframe === undefined
6860
- ? resolvedKeyframes[index]
6861
- : finalKeyframe;
6862
- }
6863
-
6864
- /**
6865
- * Maximum time allowed between an animation being created and it being
6866
- * resolved for us to use the latter as the start time.
6867
- *
6868
- * This is to ensure that while we prefer to "start" an animation as soon
6869
- * as it's triggered, we also want to avoid a visual jump if there's a big delay
6870
- * between these two moments.
6871
- */
6872
- const MAX_RESOLVE_DELAY = 40;
6873
- class BaseAnimation {
6874
- constructor({ autoplay = true, delay = 0, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", ...options }) {
6875
- // Track whether the animation has been stopped. Stopped animations won't restart.
6876
- this.isStopped = false;
6877
- this.hasAttemptedResolve = false;
6878
- this.createdAt = time.now();
6879
- this.options = {
6880
- autoplay,
6881
- delay,
6882
- type,
6883
- repeat,
6884
- repeatDelay,
6885
- repeatType,
6886
- ...options,
6887
- };
6888
- this.updateFinishedPromise();
6889
- }
6890
- /**
6891
- * This method uses the createdAt and resolvedAt to calculate the
6892
- * animation startTime. *Ideally*, we would use the createdAt time as t=0
6893
- * as the following frame would then be the first frame of the animation in
6894
- * progress, which would feel snappier.
6895
- *
6896
- * However, if there's a delay (main thread work) between the creation of
6897
- * the animation and the first commited frame, we prefer to use resolvedAt
6898
- * to avoid a sudden jump into the animation.
6899
- */
6900
- calcStartTime() {
6901
- if (!this.resolvedAt)
6902
- return this.createdAt;
6903
- return this.resolvedAt - this.createdAt > MAX_RESOLVE_DELAY
6904
- ? this.resolvedAt
6905
- : this.createdAt;
6906
- }
6907
- /**
6908
- * A getter for resolved data. If keyframes are not yet resolved, accessing
6909
- * this.resolved will synchronously flush all pending keyframe resolvers.
6910
- * This is a deoptimisation, but at its worst still batches read/writes.
6911
- */
6912
- get resolved() {
6913
- if (!this._resolved && !this.hasAttemptedResolve) {
6914
- flushKeyframeResolvers();
6915
- }
6916
- return this._resolved;
6917
- }
6918
- /**
6919
- * A method to be called when the keyframes resolver completes. This method
6920
- * will check if its possible to run the animation and, if not, skip it.
6921
- * Otherwise, it will call initPlayback on the implementing class.
6922
- */
6923
- onKeyframesResolved(keyframes, finalKeyframe) {
6924
- this.resolvedAt = time.now();
6925
- this.hasAttemptedResolve = true;
6926
- const { name, type, velocity, delay, onComplete, onUpdate, isGenerator, } = this.options;
6927
- /**
6928
- * If we can't animate this value with the resolved keyframes
6929
- * then we should complete it immediately.
6930
- */
6931
- if (!isGenerator && !canAnimate(keyframes, name, type, velocity)) {
6932
- // Finish immediately
6933
- if (!delay) {
6934
- onUpdate &&
6935
- onUpdate(getFinalKeyframe(keyframes, this.options, finalKeyframe));
6936
- onComplete && onComplete();
6937
- this.resolveFinishedPromise();
6938
- return;
6939
- }
6940
- // Finish after a delay
6941
- else {
6942
- this.options.duration = 0;
6943
- }
6944
- }
6945
- const resolvedAnimation = this.initPlayback(keyframes, finalKeyframe);
6946
- if (resolvedAnimation === false)
6947
- return;
6948
- this._resolved = {
6949
- keyframes,
6950
- finalKeyframe,
6951
- ...resolvedAnimation,
6952
- };
6953
- this.onPostResolved();
6954
- }
6955
- onPostResolved() { }
6956
- /**
6957
- * Allows the returned animation to be awaited or promise-chained. Currently
6958
- * resolves when the animation finishes at all but in a future update could/should
6959
- * reject if its cancels.
6960
- */
6961
- then(resolve, reject) {
6962
- return this.currentFinishedPromise.then(resolve, reject);
6963
- }
6964
- flatten() {
6965
- if (!this.options.allowFlatten)
6966
- return;
6967
- this.options.type = "keyframes";
6968
- this.options.ease = "linear";
6969
- }
6970
- updateFinishedPromise() {
6971
- this.currentFinishedPromise = new Promise((resolve) => {
6972
- this.resolveFinishedPromise = resolve;
6973
- });
6974
- }
6975
- }
6976
-
6977
- /*
6978
- Value in range from progress
6979
-
6980
- Given a lower limit and an upper limit, we return the value within
6981
- that range as expressed by progress (usually a number from 0 to 1)
6982
-
6983
- So progress = 0.5 would change
6984
-
6985
- from -------- to
6986
-
6987
- to
6988
-
6989
- from ---- to
6990
-
6991
- E.g. from = 10, to = 20, progress = 0.5 => 15
6992
-
6993
- @param [number]: Lower limit of range
6994
- @param [number]: Upper limit of range
6995
- @param [number]: The progress between lower and upper limits expressed 0-1
6996
- @return [number]: Value as calculated from progress within range (not limited within range)
6997
- */
6998
- const mixNumber$1 = (from, to, progress) => {
6999
- return from + (to - from) * progress;
7000
- };
7001
-
7002
6219
  // Adapted from https://gist.github.com/mjackson/5311256
7003
6220
  function hueToRgb(p, q, t) {
7004
6221
  if (t < 0)
@@ -7044,6 +6261,31 @@ function mixImmediate(a, b) {
7044
6261
  return (p) => (p > 0 ? b : a);
7045
6262
  }
7046
6263
 
6264
+ /*
6265
+ Value in range from progress
6266
+
6267
+ Given a lower limit and an upper limit, we return the value within
6268
+ that range as expressed by progress (usually a number from 0 to 1)
6269
+
6270
+ So progress = 0.5 would change
6271
+
6272
+ from -------- to
6273
+
6274
+ to
6275
+
6276
+ from ---- to
6277
+
6278
+ E.g. from = 10, to = 20, progress = 0.5 => 15
6279
+
6280
+ @param [number]: Lower limit of range
6281
+ @param [number]: Upper limit of range
6282
+ @param [number]: The progress between lower and upper limits expressed 0-1
6283
+ @return [number]: Value as calculated from progress within range (not limited within range)
6284
+ */
6285
+ const mixNumber$1 = (from, to, progress) => {
6286
+ return from + (to - from) * progress;
6287
+ };
6288
+
7047
6289
  // Linear color space blending
7048
6290
  // Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
7049
6291
  // Demonstrated http://codepen.io/osublake/pen/xGVVaN
@@ -7082,16 +6324,6 @@ const mixColor = (from, to) => {
7082
6324
  };
7083
6325
  };
7084
6326
 
7085
- /**
7086
- * Pipe
7087
- * Compose other transformers to run linearily
7088
- * pipe(min(20), max(40))
7089
- * @param {...functions} transformers
7090
- * @return {function}
7091
- */
7092
- const combineFunctions = (a, b) => (v) => b(a(v));
7093
- const pipe = (...transformers) => transformers.reduce(combineFunctions);
7094
-
7095
6327
  const invisibleValues = new Set(["none", "hidden"]);
7096
6328
  /**
7097
6329
  * Returns a function that, when provided a progress value between 0 and 1,
@@ -7107,6 +6339,16 @@ function mixVisibility(origin, target) {
7107
6339
  }
7108
6340
  }
7109
6341
 
6342
+ /**
6343
+ * Pipe
6344
+ * Compose other transformers to run linearily
6345
+ * pipe(min(20), max(40))
6346
+ * @param {...functions} transformers
6347
+ * @return {function}
6348
+ */
6349
+ const combineFunctions = (a, b) => (v) => b(a(v));
6350
+ const pipe = (...transformers) => transformers.reduce(combineFunctions);
6351
+
7110
6352
  function mixNumber(a, b) {
7111
6353
  return (p) => mixNumber$1(a, b, p);
7112
6354
  }
@@ -7189,14 +6431,69 @@ const mixComplex = (origin, target) => {
7189
6431
  }
7190
6432
  };
7191
6433
 
7192
- function mix(from, to, p) {
7193
- if (typeof from === "number" &&
7194
- typeof to === "number" &&
7195
- typeof p === "number") {
7196
- return mixNumber$1(from, to, p);
6434
+ function mix(from, to, p) {
6435
+ if (typeof from === "number" &&
6436
+ typeof to === "number" &&
6437
+ typeof p === "number") {
6438
+ return mixNumber$1(from, to, p);
6439
+ }
6440
+ const mixer = getMixer(from);
6441
+ return mixer(from, to);
6442
+ }
6443
+
6444
+ const frameloopDriver = (update) => {
6445
+ const passTimestamp = ({ timestamp }) => update(timestamp);
6446
+ return {
6447
+ start: () => frame.update(passTimestamp, true),
6448
+ stop: () => cancelFrame(passTimestamp),
6449
+ /**
6450
+ * If we're processing this frame we can use the
6451
+ * framelocked timestamp to keep things in sync.
6452
+ */
6453
+ now: () => (frameData.isProcessing ? frameData.timestamp : time.now()),
6454
+ };
6455
+ };
6456
+
6457
+ const generateLinearEasing = (easing, duration, // as milliseconds
6458
+ resolution = 10 // as milliseconds
6459
+ ) => {
6460
+ let points = "";
6461
+ const numPoints = Math.max(Math.round(duration / resolution), 2);
6462
+ for (let i = 0; i < numPoints; i++) {
6463
+ points += easing(i / (numPoints - 1)) + ", ";
6464
+ }
6465
+ return `linear(${points.substring(0, points.length - 2)})`;
6466
+ };
6467
+
6468
+ /**
6469
+ * Implement a practical max duration for keyframe generation
6470
+ * to prevent infinite loops
6471
+ */
6472
+ const maxGeneratorDuration = 20000;
6473
+ function calcGeneratorDuration(generator) {
6474
+ let duration = 0;
6475
+ const timeStep = 50;
6476
+ let state = generator.next(duration);
6477
+ while (!state.done && duration < maxGeneratorDuration) {
6478
+ duration += timeStep;
6479
+ state = generator.next(duration);
7197
6480
  }
7198
- const mixer = getMixer(from);
7199
- return mixer(from, to);
6481
+ return duration >= maxGeneratorDuration ? Infinity : duration;
6482
+ }
6483
+
6484
+ /**
6485
+ * Create a progress => progress easing function from a generator.
6486
+ */
6487
+ function createGeneratorEasing(options, scale = 100, createGenerator) {
6488
+ const generator = createGenerator({ ...options, keyframes: [0, scale] });
6489
+ const duration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration);
6490
+ return {
6491
+ type: "keyframes",
6492
+ ease: (progress) => {
6493
+ return generator.next(duration * progress).value / scale;
6494
+ },
6495
+ duration: millisecondsToSeconds(duration),
6496
+ };
7200
6497
  }
7201
6498
 
7202
6499
  const velocitySampleDuration = 5; // ms
@@ -7231,17 +6528,6 @@ const springDefaults = {
7231
6528
  maxDamping: 1,
7232
6529
  };
7233
6530
 
7234
- /**
7235
- * Converts seconds to milliseconds
7236
- *
7237
- * @param seconds - Time in seconds.
7238
- * @return milliseconds - Converted time in milliseconds.
7239
- */
7240
- /*#__NO_SIDE_EFFECTS__*/
7241
- const secondsToMilliseconds = (seconds) => seconds * 1000;
7242
- /*#__NO_SIDE_EFFECTS__*/
7243
- const millisecondsToSeconds = (milliseconds) => milliseconds / 1000;
7244
-
7245
6531
  const safeMin = 0.001;
7246
6532
  function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, velocity = springDefaults.velocity, mass = springDefaults.mass, }) {
7247
6533
  let envelope;
@@ -7322,81 +6608,6 @@ function calcAngularFreq(undampedFreq, dampingRatio) {
7322
6608
  return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
7323
6609
  }
7324
6610
 
7325
- /**
7326
- * Implement a practical max duration for keyframe generation
7327
- * to prevent infinite loops
7328
- */
7329
- const maxGeneratorDuration = 20000;
7330
- function calcGeneratorDuration(generator) {
7331
- let duration = 0;
7332
- const timeStep = 50;
7333
- let state = generator.next(duration);
7334
- while (!state.done && duration < maxGeneratorDuration) {
7335
- duration += timeStep;
7336
- state = generator.next(duration);
7337
- }
7338
- return duration >= maxGeneratorDuration ? Infinity : duration;
7339
- }
7340
-
7341
- /**
7342
- * Create a progress => progress easing function from a generator.
7343
- */
7344
- function createGeneratorEasing(options, scale = 100, createGenerator) {
7345
- const generator = createGenerator({ ...options, keyframes: [0, scale] });
7346
- const duration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration);
7347
- return {
7348
- type: "keyframes",
7349
- ease: (progress) => {
7350
- return generator.next(duration * progress).value / scale;
7351
- },
7352
- duration: millisecondsToSeconds(duration),
7353
- };
7354
- }
7355
-
7356
- /**
7357
- * Add the ability for test suites to manually set support flags
7358
- * to better test more environments.
7359
- */
7360
- const supportsFlags = {};
7361
-
7362
- /*#__NO_SIDE_EFFECTS__*/
7363
- function memo(callback) {
7364
- let result;
7365
- return () => {
7366
- if (result === undefined)
7367
- result = callback();
7368
- return result;
7369
- };
7370
- }
7371
-
7372
- function memoSupports(callback, supportsFlag) {
7373
- const memoized = memo(callback);
7374
- return () => supportsFlags[supportsFlag] ?? memoized();
7375
- }
7376
-
7377
- const supportsLinearEasing = /*@__PURE__*/ memoSupports(() => {
7378
- try {
7379
- document
7380
- .createElement("div")
7381
- .animate({ opacity: 0 }, { easing: "linear(0, 1)" });
7382
- }
7383
- catch (e) {
7384
- return false;
7385
- }
7386
- return true;
7387
- }, "linearEasing");
7388
-
7389
- const generateLinearEasing = (easing, duration, // as milliseconds
7390
- resolution = 10 // as milliseconds
7391
- ) => {
7392
- let points = "";
7393
- const numPoints = Math.max(Math.round(duration / resolution), 2);
7394
- for (let i = 0; i < numPoints; i++) {
7395
- points += easing(i / (numPoints - 1)) + ", ";
7396
- }
7397
- return `linear(${points.substring(0, points.length - 2)})`;
7398
- };
7399
-
7400
6611
  const durationKeys = ["duration", "bounce"];
7401
6612
  const physicsKeys = ["stiffness", "damping", "mass"];
7402
6613
  function isSpringType(options, keys) {
@@ -7523,7 +6734,7 @@ function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce
7523
6734
  next: (t) => {
7524
6735
  const current = resolveSpring(t);
7525
6736
  if (!isResolvedFromDuration) {
7526
- let currentVelocity = 0.0;
6737
+ let currentVelocity = t === 0 ? initialVelocity : 0.0;
7527
6738
  /**
7528
6739
  * We only need to calculate velocity for under-damped springs
7529
6740
  * as over- and critically-damped springs can't overshoot, so
@@ -7557,7 +6768,7 @@ function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce
7557
6768
  }
7558
6769
  spring.applyToOptions = (options) => {
7559
6770
  const generatorOptions = createGeneratorEasing(options, 100, spring);
7560
- options.ease = supportsLinearEasing() ? generatorOptions.ease : "easeOut";
6771
+ options.ease = generatorOptions.ease;
7561
6772
  options.duration = secondsToMilliseconds(generatorOptions.duration);
7562
6773
  options.type = "keyframes";
7563
6774
  return options;
@@ -7646,44 +6857,6 @@ function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, b
7646
6857
  };
7647
6858
  }
7648
6859
 
7649
- const easeIn = /*@__PURE__*/ cubicBezier(0.42, 0, 1, 1);
7650
- const easeOut = /*@__PURE__*/ cubicBezier(0, 0, 0.58, 1);
7651
- const easeInOut = /*@__PURE__*/ cubicBezier(0.42, 0, 0.58, 1);
7652
-
7653
- const isEasingArray = (ease) => {
7654
- return Array.isArray(ease) && typeof ease[0] !== "number";
7655
- };
7656
-
7657
- const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number";
7658
-
7659
- const easingLookup = {
7660
- linear: noop,
7661
- easeIn,
7662
- easeInOut,
7663
- easeOut,
7664
- circIn,
7665
- circInOut,
7666
- circOut,
7667
- backIn,
7668
- backInOut,
7669
- backOut,
7670
- anticipate,
7671
- };
7672
- const easingDefinitionToFunction = (definition) => {
7673
- if (isBezierDefinition(definition)) {
7674
- // If cubic bezier definition, create bezier curve
7675
- invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`);
7676
- const [x1, y1, x2, y2] = definition;
7677
- return cubicBezier(x1, y1, x2, y2);
7678
- }
7679
- else if (typeof definition === "string") {
7680
- // Else lookup from table
7681
- invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`);
7682
- return easingLookup[definition];
7683
- }
7684
- return definition;
7685
- };
7686
-
7687
6860
  /*
7688
6861
  Progress within given range
7689
6862
 
@@ -7704,7 +6877,7 @@ const progress = (from, to, value) => {
7704
6877
 
7705
6878
  function createMixers(output, ease, customMixer) {
7706
6879
  const mixers = [];
7707
- const mixerFactory = customMixer || mix;
6880
+ const mixerFactory = customMixer || MotionGlobalConfig.mix || mix;
7708
6881
  const numMixers = output.length - 1;
7709
6882
  for (let i = 0; i < numMixers; i++) {
7710
6883
  let mixer = mixerFactory(output[i], output[i + 1]);
@@ -7786,9 +6959,116 @@ function defaultOffset(arr) {
7786
6959
  return offset;
7787
6960
  }
7788
6961
 
7789
- function convertOffsetToTimes(offset, duration) {
7790
- return offset.map((o) => o * duration);
7791
- }
6962
+ function convertOffsetToTimes(offset, duration) {
6963
+ return offset.map((o) => o * duration);
6964
+ }
6965
+
6966
+ /*
6967
+ Bezier function generator
6968
+ This has been modified from Gaëtan Renaudeau's BezierEasing
6969
+ https://github.com/gre/bezier-easing/blob/master/src/index.js
6970
+ https://github.com/gre/bezier-easing/blob/master/LICENSE
6971
+
6972
+ I've removed the newtonRaphsonIterate algo because in benchmarking it
6973
+ wasn't noticiably faster than binarySubdivision, indeed removing it
6974
+ usually improved times, depending on the curve.
6975
+ I also removed the lookup table, as for the added bundle size and loop we're
6976
+ only cutting ~4 or so subdivision iterations. I bumped the max iterations up
6977
+ to 12 to compensate and this still tended to be faster for no perceivable
6978
+ loss in accuracy.
6979
+ Usage
6980
+ const easeOut = cubicBezier(.17,.67,.83,.67);
6981
+ const x = easeOut(0.5); // returns 0.627...
6982
+ */
6983
+ // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
6984
+ const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) *
6985
+ t;
6986
+ const subdivisionPrecision = 0.0000001;
6987
+ const subdivisionMaxIterations = 12;
6988
+ function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
6989
+ let currentX;
6990
+ let currentT;
6991
+ let i = 0;
6992
+ do {
6993
+ currentT = lowerBound + (upperBound - lowerBound) / 2.0;
6994
+ currentX = calcBezier(currentT, mX1, mX2) - x;
6995
+ if (currentX > 0.0) {
6996
+ upperBound = currentT;
6997
+ }
6998
+ else {
6999
+ lowerBound = currentT;
7000
+ }
7001
+ } while (Math.abs(currentX) > subdivisionPrecision &&
7002
+ ++i < subdivisionMaxIterations);
7003
+ return currentT;
7004
+ }
7005
+ function cubicBezier(mX1, mY1, mX2, mY2) {
7006
+ // If this is a linear gradient, return linear easing
7007
+ if (mX1 === mY1 && mX2 === mY2)
7008
+ return noop;
7009
+ const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
7010
+ // If animation is at start/end, return t without easing
7011
+ return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
7012
+ }
7013
+
7014
+ const easeIn = /*@__PURE__*/ cubicBezier(0.42, 0, 1, 1);
7015
+ const easeOut = /*@__PURE__*/ cubicBezier(0, 0, 0.58, 1);
7016
+ const easeInOut = /*@__PURE__*/ cubicBezier(0.42, 0, 0.58, 1);
7017
+
7018
+ const isEasingArray = (ease) => {
7019
+ return Array.isArray(ease) && typeof ease[0] !== "number";
7020
+ };
7021
+
7022
+ // Accepts an easing function and returns a new one that outputs mirrored values for
7023
+ // the second half of the animation. Turns easeIn into easeInOut.
7024
+ const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
7025
+
7026
+ // Accepts an easing function and returns a new one that outputs reversed values.
7027
+ // Turns easeIn into easeOut.
7028
+ const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
7029
+
7030
+ const backOut = /*@__PURE__*/ cubicBezier(0.33, 1.53, 0.69, 0.99);
7031
+ const backIn = /*@__PURE__*/ reverseEasing(backOut);
7032
+ const backInOut = /*@__PURE__*/ mirrorEasing(backIn);
7033
+
7034
+ const anticipate = (p) => (p *= 2) < 1 ? 0.5 * backIn(p) : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
7035
+
7036
+ const circIn = (p) => 1 - Math.sin(Math.acos(p));
7037
+ const circOut = reverseEasing(circIn);
7038
+ const circInOut = mirrorEasing(circIn);
7039
+
7040
+ const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number";
7041
+
7042
+ const easingLookup = {
7043
+ linear: noop,
7044
+ easeIn,
7045
+ easeInOut,
7046
+ easeOut,
7047
+ circIn,
7048
+ circInOut,
7049
+ circOut,
7050
+ backIn,
7051
+ backInOut,
7052
+ backOut,
7053
+ anticipate,
7054
+ };
7055
+ const isValidEasing = (easing) => {
7056
+ return typeof easing === "string";
7057
+ };
7058
+ const easingDefinitionToFunction = (definition) => {
7059
+ if (isBezierDefinition(definition)) {
7060
+ // If cubic bezier definition, create bezier curve
7061
+ invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`);
7062
+ const [x1, y1, x2, y2] = definition;
7063
+ return cubicBezier(x1, y1, x2, y2);
7064
+ }
7065
+ else if (isValidEasing(definition)) {
7066
+ // Else lookup from table
7067
+ invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`);
7068
+ return easingLookup[definition];
7069
+ }
7070
+ return definition;
7071
+ };
7792
7072
 
7793
7073
  function defaultEasing(values, easing) {
7794
7074
  return values.map(() => easing || easeInOut).splice(0, values.length - 1);
@@ -7833,68 +7113,84 @@ function keyframes({ duration = 300, keyframes: keyframeValues, times, ease = "e
7833
7113
  };
7834
7114
  }
7835
7115
 
7836
- const frameloopDriver = (update) => {
7837
- const passTimestamp = ({ timestamp }) => update(timestamp);
7838
- return {
7839
- start: () => frame.update(passTimestamp, true),
7840
- stop: () => cancelFrame(passTimestamp),
7841
- /**
7842
- * If we're processing this frame we can use the
7843
- * framelocked timestamp to keep things in sync.
7844
- */
7845
- now: () => (frameData.isProcessing ? frameData.timestamp : time.now()),
7846
- };
7847
- };
7116
+ const isNotNull = (value) => value !== null;
7117
+ function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe, speed = 1) {
7118
+ const resolvedKeyframes = keyframes.filter(isNotNull);
7119
+ const useFirstKeyframe = speed < 0 || (repeat && repeatType !== "loop" && repeat % 2 === 1);
7120
+ const index = useFirstKeyframe ? 0 : resolvedKeyframes.length - 1;
7121
+ return !index || finalKeyframe === undefined
7122
+ ? resolvedKeyframes[index]
7123
+ : finalKeyframe;
7124
+ }
7848
7125
 
7849
- const generators = {
7126
+ const transitionTypeMap = {
7850
7127
  decay: inertia,
7851
7128
  inertia,
7852
7129
  tween: keyframes,
7853
7130
  keyframes: keyframes,
7854
7131
  spring,
7855
7132
  };
7133
+ function replaceTransitionType(transition) {
7134
+ if (typeof transition.type === "string") {
7135
+ transition.type = transitionTypeMap[transition.type];
7136
+ }
7137
+ }
7138
+
7139
+ class WithPromise {
7140
+ constructor() {
7141
+ this.count = 0;
7142
+ this.updateFinished();
7143
+ }
7144
+ get finished() {
7145
+ return this._finished;
7146
+ }
7147
+ updateFinished() {
7148
+ this.count++;
7149
+ this._finished = new Promise((resolve) => {
7150
+ this.resolve = resolve;
7151
+ });
7152
+ }
7153
+ notifyFinished() {
7154
+ this.resolve();
7155
+ }
7156
+ /**
7157
+ * Allows the animation to be awaited.
7158
+ *
7159
+ * @deprecated Use `finished` instead.
7160
+ */
7161
+ then(onResolve, onReject) {
7162
+ return this.finished.then(onResolve, onReject);
7163
+ }
7164
+ }
7165
+
7856
7166
  const percentToProgress = (percent) => percent / 100;
7857
- /**
7858
- * Animation that runs on the main thread. Designed to be WAAPI-spec in the subset of
7859
- * features we expose publically. Mostly the compatibility is to ensure visual identity
7860
- * between both WAAPI and main thread animations.
7861
- */
7862
- class MainThreadAnimation extends BaseAnimation {
7167
+ class JSAnimation extends WithPromise {
7863
7168
  constructor(options) {
7864
- super(options);
7865
- /**
7866
- * The time at which the animation was paused.
7867
- */
7868
- this.holdTime = null;
7869
- /**
7870
- * The time at which the animation was cancelled.
7871
- */
7872
- this.cancelTime = null;
7169
+ super();
7170
+ this.state = "idle";
7171
+ this.startTime = null;
7172
+ this.isStopped = false;
7873
7173
  /**
7874
7174
  * The current time of the animation.
7875
7175
  */
7876
7176
  this.currentTime = 0;
7877
7177
  /**
7878
- * Playback speed as a factor. 0 would be stopped, -1 reverse and 2 double speed.
7879
- */
7880
- this.playbackSpeed = 1;
7881
- /**
7882
- * The state of the animation to apply when the animation is resolved. This
7883
- * allows calls to the public API to control the animation before it is resolved,
7884
- * without us having to resolve it first.
7178
+ * The time at which the animation was paused.
7885
7179
  */
7886
- this.pendingPlayState = "running";
7180
+ this.holdTime = null;
7887
7181
  /**
7888
- * The time at which the animation was started.
7182
+ * Playback speed as a factor. 0 would be stopped, -1 reverse and 2 double speed.
7889
7183
  */
7890
- this.startTime = null;
7891
- this.state = "idle";
7184
+ this.playbackSpeed = 1;
7892
7185
  /**
7893
7186
  * This method is bound to the instance to fix a pattern where
7894
7187
  * animation.stop is returned as a reference from a useEffect.
7895
7188
  */
7896
7189
  this.stop = () => {
7897
- this.resolver.cancel();
7190
+ const { motionValue } = this.options;
7191
+ if (motionValue && motionValue.updatedAt !== time.now()) {
7192
+ this.tick(time.now());
7193
+ }
7898
7194
  this.isStopped = true;
7899
7195
  if (this.state === "idle")
7900
7196
  return;
@@ -7902,49 +7198,35 @@ class MainThreadAnimation extends BaseAnimation {
7902
7198
  const { onStop } = this.options;
7903
7199
  onStop && onStop();
7904
7200
  };
7905
- const { name, motionValue, element, keyframes } = this.options;
7906
- const KeyframeResolver$1 = element?.KeyframeResolver || KeyframeResolver;
7907
- const onResolved = (resolvedKeyframes, finalKeyframe) => this.onKeyframesResolved(resolvedKeyframes, finalKeyframe);
7908
- this.resolver = new KeyframeResolver$1(keyframes, onResolved, name, motionValue, element);
7909
- this.resolver.scheduleResolve();
7910
- }
7911
- flatten() {
7912
- super.flatten();
7913
- // If we've already resolved the animation, re-initialise it
7914
- if (this._resolved) {
7915
- Object.assign(this._resolved, this.initPlayback(this._resolved.keyframes));
7916
- }
7917
- }
7918
- initPlayback(keyframes$1) {
7919
- const { type = "keyframes", repeat = 0, repeatDelay = 0, repeatType, velocity = 0, } = this.options;
7920
- const generatorFactory = isGenerator(type)
7921
- ? type
7922
- : generators[type] || keyframes;
7923
- /**
7924
- * If our generator doesn't support mixing numbers, we need to replace keyframes with
7925
- * [0, 100] and then make a function that maps that to the actual keyframes.
7926
- *
7927
- * 100 is chosen instead of 1 as it works nicer with spring animations.
7928
- */
7929
- let mapPercentToKeyframes;
7930
- let mirroredGenerator;
7201
+ this.options = options;
7202
+ this.initAnimation();
7203
+ this.play();
7204
+ if (options.autoplay === false)
7205
+ this.pause();
7206
+ }
7207
+ initAnimation() {
7208
+ const { options } = this;
7209
+ replaceTransitionType(options);
7210
+ const { type = keyframes, repeat = 0, repeatDelay = 0, repeatType, velocity = 0, } = options;
7211
+ let { keyframes: keyframes$1 } = options;
7212
+ const generatorFactory = type || keyframes;
7931
7213
  if (process.env.NODE_ENV !== "production" &&
7932
7214
  generatorFactory !== keyframes) {
7933
7215
  invariant(keyframes$1.length <= 2, `Only two keyframes currently supported with spring and inertia animations. Trying to animate ${keyframes$1}`);
7934
7216
  }
7935
7217
  if (generatorFactory !== keyframes &&
7936
7218
  typeof keyframes$1[0] !== "number") {
7937
- mapPercentToKeyframes = pipe(percentToProgress, mix(keyframes$1[0], keyframes$1[1]));
7219
+ this.mixKeyframes = pipe(percentToProgress, mix(keyframes$1[0], keyframes$1[1]));
7938
7220
  keyframes$1 = [0, 100];
7939
7221
  }
7940
- const generator = generatorFactory({ ...this.options, keyframes: keyframes$1 });
7222
+ const generator = generatorFactory({ ...options, keyframes: keyframes$1 });
7941
7223
  /**
7942
7224
  * If we have a mirror repeat type we need to create a second generator that outputs the
7943
7225
  * mirrored (not reversed) animation and later ping pong between the two generators.
7944
7226
  */
7945
7227
  if (repeatType === "mirror") {
7946
- mirroredGenerator = generatorFactory({
7947
- ...this.options,
7228
+ this.mirroredGenerator = generatorFactory({
7229
+ ...options,
7948
7230
  keyframes: [...keyframes$1].reverse(),
7949
7231
  velocity: -velocity,
7950
7232
  });
@@ -7961,38 +7243,29 @@ class MainThreadAnimation extends BaseAnimation {
7961
7243
  generator.calculatedDuration = calcGeneratorDuration(generator);
7962
7244
  }
7963
7245
  const { calculatedDuration } = generator;
7964
- const resolvedDuration = calculatedDuration + repeatDelay;
7965
- const totalDuration = resolvedDuration * (repeat + 1) - repeatDelay;
7966
- return {
7967
- generator,
7968
- mirroredGenerator,
7969
- mapPercentToKeyframes,
7970
- calculatedDuration,
7971
- resolvedDuration,
7972
- totalDuration,
7973
- };
7246
+ this.calculatedDuration = calculatedDuration;
7247
+ this.resolvedDuration = calculatedDuration + repeatDelay;
7248
+ this.totalDuration = this.resolvedDuration * (repeat + 1) - repeatDelay;
7249
+ this.generator = generator;
7974
7250
  }
7975
- onPostResolved() {
7976
- const { autoplay = true } = this.options;
7977
- this.play();
7978
- if (this.pendingPlayState === "paused" || !autoplay) {
7979
- this.pause();
7251
+ updateTime(timestamp) {
7252
+ const animationTime = Math.round(timestamp - this.startTime) * this.playbackSpeed;
7253
+ // Update currentTime
7254
+ if (this.holdTime !== null) {
7255
+ this.currentTime = this.holdTime;
7980
7256
  }
7981
7257
  else {
7982
- this.state = this.pendingPlayState;
7258
+ // Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 =
7259
+ // 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for
7260
+ // example.
7261
+ this.currentTime = animationTime;
7983
7262
  }
7984
7263
  }
7985
7264
  tick(timestamp, sample = false) {
7986
- const { resolved } = this;
7987
- // If the animations has failed to resolve, return the final keyframe.
7988
- if (!resolved) {
7989
- const { keyframes } = this.options;
7990
- return { done: true, value: keyframes[keyframes.length - 1] };
7991
- }
7992
- const { finalKeyframe, generator, mirroredGenerator, mapPercentToKeyframes, keyframes, calculatedDuration, totalDuration, resolvedDuration, } = resolved;
7265
+ const { generator, totalDuration, mixKeyframes, mirroredGenerator, resolvedDuration, calculatedDuration, } = this;
7993
7266
  if (this.startTime === null)
7994
7267
  return generator.next(0);
7995
- const { delay, repeat, repeatType, repeatDelay, onUpdate } = this.options;
7268
+ const { delay = 0, keyframes, repeat, repeatType, repeatDelay, type, onUpdate, finalKeyframe, } = this.options;
7996
7269
  /**
7997
7270
  * requestAnimationFrame timestamps can come through as lower than
7998
7271
  * the startTime as set by performance.now(). Here we prevent this,
@@ -8005,23 +7278,15 @@ class MainThreadAnimation extends BaseAnimation {
8005
7278
  else if (this.speed < 0) {
8006
7279
  this.startTime = Math.min(timestamp - totalDuration / this.speed, this.startTime);
8007
7280
  }
8008
- // Update currentTime
8009
7281
  if (sample) {
8010
7282
  this.currentTime = timestamp;
8011
7283
  }
8012
- else if (this.holdTime !== null) {
8013
- this.currentTime = this.holdTime;
8014
- }
8015
7284
  else {
8016
- // Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 =
8017
- // 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for
8018
- // example.
8019
- this.currentTime =
8020
- Math.round(timestamp - this.startTime) * this.speed;
7285
+ this.updateTime(timestamp);
8021
7286
  }
8022
7287
  // Rebase on delay
8023
- const timeWithoutDelay = this.currentTime - delay * (this.speed >= 0 ? 1 : -1);
8024
- const isInDelayPhase = this.speed >= 0
7288
+ const timeWithoutDelay = this.currentTime - delay * (this.playbackSpeed >= 0 ? 1 : -1);
7289
+ const isInDelayPhase = this.playbackSpeed >= 0
8025
7290
  ? timeWithoutDelay < 0
8026
7291
  : timeWithoutDelay > totalDuration;
8027
7292
  this.currentTime = Math.max(timeWithoutDelay, 0);
@@ -8082,20 +7347,21 @@ class MainThreadAnimation extends BaseAnimation {
8082
7347
  const state = isInDelayPhase
8083
7348
  ? { done: false, value: keyframes[0] }
8084
7349
  : frameGenerator.next(elapsed);
8085
- if (mapPercentToKeyframes) {
8086
- state.value = mapPercentToKeyframes(state.value);
7350
+ if (mixKeyframes) {
7351
+ state.value = mixKeyframes(state.value);
8087
7352
  }
8088
7353
  let { done } = state;
8089
7354
  if (!isInDelayPhase && calculatedDuration !== null) {
8090
7355
  done =
8091
- this.speed >= 0
7356
+ this.playbackSpeed >= 0
8092
7357
  ? this.currentTime >= totalDuration
8093
7358
  : this.currentTime <= 0;
8094
7359
  }
8095
7360
  const isAnimationFinished = this.holdTime === null &&
8096
7361
  (this.state === "finished" || (this.state === "running" && done));
8097
- if (isAnimationFinished && finalKeyframe !== undefined) {
8098
- state.value = getFinalKeyframe(keyframes, this.options, finalKeyframe);
7362
+ // TODO: The exception for inertia could be cleaner here
7363
+ if (isAnimationFinished && type !== inertia) {
7364
+ state.value = getFinalKeyframe(keyframes, this.options, finalKeyframe, this.speed);
8099
7365
  }
8100
7366
  if (onUpdate) {
8101
7367
  onUpdate(state.value);
@@ -8105,135 +7371,434 @@ class MainThreadAnimation extends BaseAnimation {
8105
7371
  }
8106
7372
  return state;
8107
7373
  }
7374
+ /**
7375
+ * Allows the returned animation to be awaited or promise-chained. Currently
7376
+ * resolves when the animation finishes at all but in a future update could/should
7377
+ * reject if its cancels.
7378
+ */
7379
+ then(resolve, reject) {
7380
+ return this.finished.then(resolve, reject);
7381
+ }
8108
7382
  get duration() {
8109
- const { resolved } = this;
8110
- return resolved ? millisecondsToSeconds(resolved.calculatedDuration) : 0;
7383
+ return millisecondsToSeconds(this.calculatedDuration);
7384
+ }
7385
+ get time() {
7386
+ return millisecondsToSeconds(this.currentTime);
7387
+ }
7388
+ set time(newTime) {
7389
+ newTime = secondsToMilliseconds(newTime);
7390
+ this.currentTime = newTime;
7391
+ if (this.startTime === null ||
7392
+ this.holdTime !== null ||
7393
+ this.playbackSpeed === 0) {
7394
+ this.holdTime = newTime;
7395
+ }
7396
+ else if (this.driver) {
7397
+ this.startTime = this.driver.now() - newTime / this.playbackSpeed;
7398
+ }
7399
+ }
7400
+ get speed() {
7401
+ return this.playbackSpeed;
7402
+ }
7403
+ set speed(newSpeed) {
7404
+ this.updateTime(time.now());
7405
+ const hasChanged = this.playbackSpeed !== newSpeed;
7406
+ this.playbackSpeed = newSpeed;
7407
+ if (hasChanged) {
7408
+ this.time = millisecondsToSeconds(this.currentTime);
7409
+ }
7410
+ }
7411
+ play() {
7412
+ if (this.isStopped)
7413
+ return;
7414
+ const { driver = frameloopDriver, onPlay, startTime } = this.options;
7415
+ if (!this.driver) {
7416
+ this.driver = driver((timestamp) => this.tick(timestamp));
7417
+ }
7418
+ onPlay && onPlay();
7419
+ const now = this.driver.now();
7420
+ if (this.holdTime !== null) {
7421
+ this.startTime = now - this.holdTime;
7422
+ }
7423
+ else if (this.state === "finished") {
7424
+ this.updateFinished();
7425
+ this.startTime = now;
7426
+ }
7427
+ else if (!this.startTime) {
7428
+ this.startTime = startTime ?? now;
7429
+ }
7430
+ if (this.state === "finished" && this.speed < 0) {
7431
+ this.startTime += this.calculatedDuration;
7432
+ }
7433
+ this.holdTime = null;
7434
+ /**
7435
+ * Set playState to running only after we've used it in
7436
+ * the previous logic.
7437
+ */
7438
+ this.state = "running";
7439
+ this.driver.start();
7440
+ }
7441
+ pause() {
7442
+ this.state = "paused";
7443
+ this.updateTime(time.now());
7444
+ this.holdTime = this.currentTime;
7445
+ }
7446
+ complete() {
7447
+ if (this.state !== "running") {
7448
+ this.play();
7449
+ }
7450
+ this.state = "finished";
7451
+ this.holdTime = null;
7452
+ }
7453
+ finish() {
7454
+ this.notifyFinished();
7455
+ this.teardown();
7456
+ this.state = "finished";
7457
+ const { onComplete } = this.options;
7458
+ onComplete && onComplete();
7459
+ }
7460
+ cancel() {
7461
+ this.holdTime = null;
7462
+ this.startTime = 0;
7463
+ this.tick(0);
7464
+ this.teardown();
7465
+ }
7466
+ teardown() {
7467
+ this.state = "idle";
7468
+ this.stopDriver();
7469
+ this.startTime = this.holdTime = null;
7470
+ }
7471
+ stopDriver() {
7472
+ if (!this.driver)
7473
+ return;
7474
+ this.driver.stop();
7475
+ this.driver = undefined;
7476
+ }
7477
+ sample(sampleTime) {
7478
+ this.startTime = 0;
7479
+ return this.tick(sampleTime, true);
7480
+ }
7481
+ attachTimeline(timeline) {
7482
+ if (this.options.allowFlatten) {
7483
+ this.options.type = "keyframes";
7484
+ this.options.ease = "linear";
7485
+ this.initAnimation();
7486
+ }
7487
+ return timeline.observe(this);
7488
+ }
7489
+ }
7490
+
7491
+ function fillWildcards(keyframes) {
7492
+ for (let i = 1; i < keyframes.length; i++) {
7493
+ keyframes[i] ?? (keyframes[i] = keyframes[i - 1]);
7494
+ }
7495
+ }
7496
+
7497
+ const radToDeg = (rad) => (rad * 180) / Math.PI;
7498
+ const rotate = (v) => {
7499
+ const angle = radToDeg(Math.atan2(v[1], v[0]));
7500
+ return rebaseAngle(angle);
7501
+ };
7502
+ const matrix2dParsers = {
7503
+ x: 4,
7504
+ y: 5,
7505
+ translateX: 4,
7506
+ translateY: 5,
7507
+ scaleX: 0,
7508
+ scaleY: 3,
7509
+ scale: (v) => (Math.abs(v[0]) + Math.abs(v[3])) / 2,
7510
+ rotate,
7511
+ rotateZ: rotate,
7512
+ skewX: (v) => radToDeg(Math.atan(v[1])),
7513
+ skewY: (v) => radToDeg(Math.atan(v[2])),
7514
+ skew: (v) => (Math.abs(v[1]) + Math.abs(v[2])) / 2,
7515
+ };
7516
+ const rebaseAngle = (angle) => {
7517
+ angle = angle % 360;
7518
+ if (angle < 0)
7519
+ angle += 360;
7520
+ return angle;
7521
+ };
7522
+ const rotateZ = rotate;
7523
+ const scaleX = (v) => Math.sqrt(v[0] * v[0] + v[1] * v[1]);
7524
+ const scaleY = (v) => Math.sqrt(v[4] * v[4] + v[5] * v[5]);
7525
+ const matrix3dParsers = {
7526
+ x: 12,
7527
+ y: 13,
7528
+ z: 14,
7529
+ translateX: 12,
7530
+ translateY: 13,
7531
+ translateZ: 14,
7532
+ scaleX,
7533
+ scaleY,
7534
+ scale: (v) => (scaleX(v) + scaleY(v)) / 2,
7535
+ rotateX: (v) => rebaseAngle(radToDeg(Math.atan2(v[6], v[5]))),
7536
+ rotateY: (v) => rebaseAngle(radToDeg(Math.atan2(-v[2], v[0]))),
7537
+ rotateZ,
7538
+ rotate: rotateZ,
7539
+ skewX: (v) => radToDeg(Math.atan(v[4])),
7540
+ skewY: (v) => radToDeg(Math.atan(v[1])),
7541
+ skew: (v) => (Math.abs(v[1]) + Math.abs(v[4])) / 2,
7542
+ };
7543
+ function defaultTransformValue(name) {
7544
+ return name.includes("scale") ? 1 : 0;
7545
+ }
7546
+ function parseValueFromTransform(transform, name) {
7547
+ if (!transform || transform === "none") {
7548
+ return defaultTransformValue(name);
8111
7549
  }
8112
- get time() {
8113
- return millisecondsToSeconds(this.currentTime);
7550
+ const matrix3dMatch = transform.match(/^matrix3d\(([-\d.e\s,]+)\)$/u);
7551
+ let parsers;
7552
+ let match;
7553
+ if (matrix3dMatch) {
7554
+ parsers = matrix3dParsers;
7555
+ match = matrix3dMatch;
8114
7556
  }
8115
- set time(newTime) {
8116
- newTime = secondsToMilliseconds(newTime);
8117
- this.currentTime = newTime;
8118
- if (this.holdTime !== null || this.speed === 0) {
8119
- this.holdTime = newTime;
8120
- }
8121
- else if (this.driver) {
8122
- this.startTime = this.driver.now() - newTime / this.speed;
8123
- }
7557
+ else {
7558
+ const matrix2dMatch = transform.match(/^matrix\(([-\d.e\s,]+)\)$/u);
7559
+ parsers = matrix2dParsers;
7560
+ match = matrix2dMatch;
8124
7561
  }
8125
- get speed() {
8126
- return this.playbackSpeed;
7562
+ if (!match) {
7563
+ return defaultTransformValue(name);
8127
7564
  }
8128
- set speed(newSpeed) {
8129
- const hasChanged = this.playbackSpeed !== newSpeed;
8130
- this.playbackSpeed = newSpeed;
8131
- if (hasChanged) {
8132
- this.time = millisecondsToSeconds(this.currentTime);
7565
+ const valueParser = parsers[name];
7566
+ const values = match[1].split(",").map(convertTransformToNumber);
7567
+ return typeof valueParser === "function"
7568
+ ? valueParser(values)
7569
+ : values[valueParser];
7570
+ }
7571
+ const readTransformValue = (instance, name) => {
7572
+ const { transform = "none" } = getComputedStyle(instance);
7573
+ return parseValueFromTransform(transform, name);
7574
+ };
7575
+ function convertTransformToNumber(value) {
7576
+ return parseFloat(value.trim());
7577
+ }
7578
+
7579
+ const isNumOrPxType = (v) => v === number || v === px;
7580
+ const transformKeys = new Set(["x", "y", "z"]);
7581
+ const nonTranslationalTransformKeys = transformPropOrder.filter((key) => !transformKeys.has(key));
7582
+ function removeNonTranslationalTransform(visualElement) {
7583
+ const removedTransforms = [];
7584
+ nonTranslationalTransformKeys.forEach((key) => {
7585
+ const value = visualElement.getValue(key);
7586
+ if (value !== undefined) {
7587
+ removedTransforms.push([key, value.get()]);
7588
+ value.set(key.startsWith("scale") ? 1 : 0);
8133
7589
  }
7590
+ });
7591
+ return removedTransforms;
7592
+ }
7593
+ const positionalValues = {
7594
+ // Dimensions
7595
+ width: ({ x }, { paddingLeft = "0", paddingRight = "0" }) => x.max - x.min - parseFloat(paddingLeft) - parseFloat(paddingRight),
7596
+ height: ({ y }, { paddingTop = "0", paddingBottom = "0" }) => y.max - y.min - parseFloat(paddingTop) - parseFloat(paddingBottom),
7597
+ top: (_bbox, { top }) => parseFloat(top),
7598
+ left: (_bbox, { left }) => parseFloat(left),
7599
+ bottom: ({ y }, { top }) => parseFloat(top) + (y.max - y.min),
7600
+ right: ({ x }, { left }) => parseFloat(left) + (x.max - x.min),
7601
+ // Transform
7602
+ x: (_bbox, { transform }) => parseValueFromTransform(transform, "x"),
7603
+ y: (_bbox, { transform }) => parseValueFromTransform(transform, "y"),
7604
+ };
7605
+ // Alias translate longform names
7606
+ positionalValues.translateX = positionalValues.x;
7607
+ positionalValues.translateY = positionalValues.y;
7608
+
7609
+ const toResolve = new Set();
7610
+ let isScheduled = false;
7611
+ let anyNeedsMeasurement = false;
7612
+ let isForced = false;
7613
+ function measureAllKeyframes() {
7614
+ if (anyNeedsMeasurement) {
7615
+ const resolversToMeasure = Array.from(toResolve).filter((resolver) => resolver.needsMeasurement);
7616
+ const elementsToMeasure = new Set(resolversToMeasure.map((resolver) => resolver.element));
7617
+ const transformsToRestore = new Map();
7618
+ /**
7619
+ * Write pass
7620
+ * If we're measuring elements we want to remove bounding box-changing transforms.
7621
+ */
7622
+ elementsToMeasure.forEach((element) => {
7623
+ const removedTransforms = removeNonTranslationalTransform(element);
7624
+ if (!removedTransforms.length)
7625
+ return;
7626
+ transformsToRestore.set(element, removedTransforms);
7627
+ element.render();
7628
+ });
7629
+ // Read
7630
+ resolversToMeasure.forEach((resolver) => resolver.measureInitialState());
7631
+ // Write
7632
+ elementsToMeasure.forEach((element) => {
7633
+ element.render();
7634
+ const restore = transformsToRestore.get(element);
7635
+ if (restore) {
7636
+ restore.forEach(([key, value]) => {
7637
+ element.getValue(key)?.set(value);
7638
+ });
7639
+ }
7640
+ });
7641
+ // Read
7642
+ resolversToMeasure.forEach((resolver) => resolver.measureEndState());
7643
+ // Write
7644
+ resolversToMeasure.forEach((resolver) => {
7645
+ if (resolver.suspendedScrollY !== undefined) {
7646
+ window.scrollTo(0, resolver.suspendedScrollY);
7647
+ }
7648
+ });
8134
7649
  }
8135
- play() {
8136
- if (!this.resolver.isScheduled) {
8137
- this.resolver.resume();
8138
- }
8139
- if (!this._resolved) {
8140
- this.pendingPlayState = "running";
8141
- return;
8142
- }
8143
- if (this.isStopped)
8144
- return;
8145
- const { driver = frameloopDriver, onPlay, startTime } = this.options;
8146
- if (!this.driver) {
8147
- this.driver = driver((timestamp) => this.tick(timestamp));
8148
- }
8149
- onPlay && onPlay();
8150
- const now = this.driver.now();
8151
- if (this.holdTime !== null) {
8152
- this.startTime = now - this.holdTime;
8153
- }
8154
- else if (!this.startTime) {
8155
- this.startTime = startTime ?? this.calcStartTime();
8156
- }
8157
- else if (this.state === "finished") {
8158
- this.startTime = now;
8159
- }
8160
- if (this.state === "finished") {
8161
- this.updateFinishedPromise();
7650
+ anyNeedsMeasurement = false;
7651
+ isScheduled = false;
7652
+ toResolve.forEach((resolver) => resolver.complete(isForced));
7653
+ toResolve.clear();
7654
+ }
7655
+ function readAllKeyframes() {
7656
+ toResolve.forEach((resolver) => {
7657
+ resolver.readKeyframes();
7658
+ if (resolver.needsMeasurement) {
7659
+ anyNeedsMeasurement = true;
8162
7660
  }
8163
- this.cancelTime = this.startTime;
8164
- this.holdTime = null;
7661
+ });
7662
+ }
7663
+ function flushKeyframeResolvers() {
7664
+ isForced = true;
7665
+ readAllKeyframes();
7666
+ measureAllKeyframes();
7667
+ isForced = false;
7668
+ }
7669
+ class KeyframeResolver {
7670
+ constructor(unresolvedKeyframes, onComplete, name, motionValue, element, isAsync = false) {
8165
7671
  /**
8166
- * Set playState to running only after we've used it in
8167
- * the previous logic.
7672
+ * Track whether this resolver has completed. Once complete, it never
7673
+ * needs to attempt keyframe resolution again.
8168
7674
  */
8169
- this.state = "running";
8170
- this.driver.start();
7675
+ this.isComplete = false;
7676
+ /**
7677
+ * Track whether this resolver is async. If it is, it'll be added to the
7678
+ * resolver queue and flushed in the next frame. Resolvers that aren't going
7679
+ * to trigger read/write thrashing don't need to be async.
7680
+ */
7681
+ this.isAsync = false;
7682
+ /**
7683
+ * Track whether this resolver needs to perform a measurement
7684
+ * to resolve its keyframes.
7685
+ */
7686
+ this.needsMeasurement = false;
7687
+ /**
7688
+ * Track whether this resolver is currently scheduled to resolve
7689
+ * to allow it to be cancelled and resumed externally.
7690
+ */
7691
+ this.isScheduled = false;
7692
+ this.unresolvedKeyframes = [...unresolvedKeyframes];
7693
+ this.onComplete = onComplete;
7694
+ this.name = name;
7695
+ this.motionValue = motionValue;
7696
+ this.element = element;
7697
+ this.isAsync = isAsync;
8171
7698
  }
8172
- pause() {
8173
- if (!this._resolved) {
8174
- this.pendingPlayState = "paused";
8175
- return;
7699
+ scheduleResolve() {
7700
+ this.isScheduled = true;
7701
+ if (this.isAsync) {
7702
+ toResolve.add(this);
7703
+ if (!isScheduled) {
7704
+ isScheduled = true;
7705
+ frame.read(readAllKeyframes);
7706
+ frame.resolveKeyframes(measureAllKeyframes);
7707
+ }
7708
+ }
7709
+ else {
7710
+ this.readKeyframes();
7711
+ this.complete();
8176
7712
  }
8177
- this.state = "paused";
8178
- this.holdTime = this.currentTime ?? 0;
8179
7713
  }
8180
- complete() {
8181
- if (this.state !== "running") {
8182
- this.play();
7714
+ readKeyframes() {
7715
+ const { unresolvedKeyframes, name, element, motionValue } = this;
7716
+ // If initial keyframe is null we need to read it from the DOM
7717
+ if (unresolvedKeyframes[0] === null) {
7718
+ const currentValue = motionValue?.get();
7719
+ // TODO: This doesn't work if the final keyframe is a wildcard
7720
+ const finalKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
7721
+ if (currentValue !== undefined) {
7722
+ unresolvedKeyframes[0] = currentValue;
7723
+ }
7724
+ else if (element && name) {
7725
+ const valueAsRead = element.readValue(name, finalKeyframe);
7726
+ if (valueAsRead !== undefined && valueAsRead !== null) {
7727
+ unresolvedKeyframes[0] = valueAsRead;
7728
+ }
7729
+ }
7730
+ if (unresolvedKeyframes[0] === undefined) {
7731
+ unresolvedKeyframes[0] = finalKeyframe;
7732
+ }
7733
+ if (motionValue && currentValue === undefined) {
7734
+ motionValue.set(unresolvedKeyframes[0]);
7735
+ }
8183
7736
  }
8184
- this.pendingPlayState = this.state = "finished";
8185
- this.holdTime = null;
7737
+ fillWildcards(unresolvedKeyframes);
8186
7738
  }
8187
- finish() {
8188
- this.teardown();
8189
- this.state = "finished";
8190
- const { onComplete } = this.options;
8191
- onComplete && onComplete();
7739
+ setFinalKeyframe() { }
7740
+ measureInitialState() { }
7741
+ renderEndStyles() { }
7742
+ measureEndState() { }
7743
+ complete(isForced = false) {
7744
+ this.isComplete = true;
7745
+ this.onComplete(this.unresolvedKeyframes, this.finalKeyframe, isForced);
7746
+ toResolve.delete(this);
8192
7747
  }
8193
7748
  cancel() {
8194
- if (this.cancelTime !== null) {
8195
- this.tick(this.cancelTime);
7749
+ if (!this.isComplete) {
7750
+ this.isScheduled = false;
7751
+ toResolve.delete(this);
8196
7752
  }
8197
- this.teardown();
8198
- this.updateFinishedPromise();
8199
- }
8200
- teardown() {
8201
- this.state = "idle";
8202
- this.stopDriver();
8203
- this.resolveFinishedPromise();
8204
- this.updateFinishedPromise();
8205
- this.startTime = this.cancelTime = null;
8206
- this.resolver.cancel();
8207
- }
8208
- stopDriver() {
8209
- if (!this.driver)
8210
- return;
8211
- this.driver.stop();
8212
- this.driver = undefined;
8213
- }
8214
- sample(time) {
8215
- this.startTime = 0;
8216
- return this.tick(time, true);
8217
7753
  }
8218
- get finished() {
8219
- return this.currentFinishedPromise;
7754
+ resume() {
7755
+ if (!this.isComplete)
7756
+ this.scheduleResolve();
8220
7757
  }
8221
7758
  }
8222
7759
 
7760
+ const isCSSVar = (name) => name.startsWith("--");
7761
+
7762
+ function setStyle(element, name, value) {
7763
+ isCSSVar(name)
7764
+ ? element.style.setProperty(name, value)
7765
+ : (element.style[name] = value);
7766
+ }
7767
+
7768
+ /*#__NO_SIDE_EFFECTS__*/
7769
+ function memo(callback) {
7770
+ let result;
7771
+ return () => {
7772
+ if (result === undefined)
7773
+ result = callback();
7774
+ return result;
7775
+ };
7776
+ }
7777
+
7778
+ const supportsScrollTimeline = /* @__PURE__ */ memo(() => window.ScrollTimeline !== undefined);
7779
+
8223
7780
  /**
8224
- * A list of values that can be hardware-accelerated.
7781
+ * Add the ability for test suites to manually set support flags
7782
+ * to better test more environments.
8225
7783
  */
8226
- const acceleratedValues = new Set([
8227
- "opacity",
8228
- "clipPath",
8229
- "filter",
8230
- "transform",
8231
- // TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved
8232
- // or until we implement support for linear() easing.
8233
- // "background-color"
8234
- ]);
7784
+ const supportsFlags = {};
8235
7785
 
8236
- const supportsWaapi = /*@__PURE__*/ memo(() => Object.hasOwnProperty.call(Element.prototype, "animate"));
7786
+ function memoSupports(callback, supportsFlag) {
7787
+ const memoized = memo(callback);
7788
+ return () => supportsFlags[supportsFlag] ?? memoized();
7789
+ }
7790
+
7791
+ const supportsLinearEasing = /*@__PURE__*/ memoSupports(() => {
7792
+ try {
7793
+ document
7794
+ .createElement("div")
7795
+ .animate({ opacity: 0 }, { easing: "linear(0, 1)" });
7796
+ }
7797
+ catch (e) {
7798
+ return false;
7799
+ }
7800
+ return true;
7801
+ }, "linearEasing");
8237
7802
 
8238
7803
  const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
8239
7804
 
@@ -8253,8 +7818,10 @@ function mapEasingToNativeEasing(easing, duration) {
8253
7818
  if (!easing) {
8254
7819
  return undefined;
8255
7820
  }
8256
- else if (typeof easing === "function" && supportsLinearEasing()) {
8257
- return generateLinearEasing(easing, duration);
7821
+ else if (typeof easing === "function") {
7822
+ return supportsLinearEasing()
7823
+ ? generateLinearEasing(easing, duration)
7824
+ : "ease-out";
8258
7825
  }
8259
7826
  else if (isBezierDefinition(easing)) {
8260
7827
  return cubicBezierAsString(easing);
@@ -8268,7 +7835,7 @@ function mapEasingToNativeEasing(easing, duration) {
8268
7835
  }
8269
7836
  }
8270
7837
 
8271
- function startWaapiAnimation(element, valueName, keyframes, { delay = 0, duration = 300, repeat = 0, repeatType = "loop", ease = "easeInOut", times, } = {}, pseudoElement = undefined) {
7838
+ function startWaapiAnimation(element, valueName, keyframes, { delay = 0, duration = 300, repeat = 0, repeatType = "loop", ease = "easeOut", times, } = {}, pseudoElement = undefined) {
8272
7839
  const keyframeOptions = {
8273
7840
  [valueName]: keyframes,
8274
7841
  };
@@ -8280,85 +7847,181 @@ function startWaapiAnimation(element, valueName, keyframes, { delay = 0, duratio
8280
7847
  */
8281
7848
  if (Array.isArray(easing))
8282
7849
  keyframeOptions.easing = easing;
8283
- const animation = element.animate(keyframeOptions, {
7850
+ const options = {
8284
7851
  delay,
8285
7852
  duration,
8286
7853
  easing: !Array.isArray(easing) ? easing : "linear",
8287
7854
  fill: "both",
8288
7855
  iterations: repeat + 1,
8289
7856
  direction: repeatType === "reverse" ? "alternate" : "normal",
8290
- pseudoElement,
8291
- });
7857
+ };
7858
+ if (pseudoElement)
7859
+ options.pseudoElement = pseudoElement;
7860
+ const animation = element.animate(keyframeOptions, options);
8292
7861
  return animation;
8293
7862
  }
8294
7863
 
8295
- function attachTimeline(animation, timeline) {
8296
- animation.timeline = timeline;
8297
- animation.onfinish = null;
7864
+ function isGenerator(type) {
7865
+ return typeof type === "function" && "applyToOptions" in type;
8298
7866
  }
8299
7867
 
8300
- function isWaapiSupportedEasing(easing) {
8301
- return Boolean((typeof easing === "function" && supportsLinearEasing()) ||
8302
- !easing ||
8303
- (typeof easing === "string" &&
8304
- (easing in supportedWaapiEasing || supportsLinearEasing())) ||
8305
- isBezierDefinition(easing) ||
8306
- (Array.isArray(easing) && easing.every(isWaapiSupportedEasing)));
7868
+ function applyGeneratorOptions({ type, ...options }) {
7869
+ if (isGenerator(type) && supportsLinearEasing()) {
7870
+ return type.applyToOptions(options);
7871
+ }
7872
+ else {
7873
+ options.duration ?? (options.duration = 300);
7874
+ options.ease ?? (options.ease = "easeOut");
7875
+ }
7876
+ return options;
8307
7877
  }
8308
7878
 
8309
7879
  /**
8310
- * 10ms is chosen here as it strikes a balance between smooth
8311
- * results (more than one keyframe per frame at 60fps) and
8312
- * keyframe quantity.
8313
- */
8314
- const sampleDelta = 10; //ms
8315
- /**
8316
- * Implement a practical max duration for keyframe generation
8317
- * to prevent infinite loops
8318
- */
8319
- const maxDuration = 20000;
8320
- /**
8321
- * Check if an animation can run natively via WAAPI or requires pregenerated keyframes.
8322
- * WAAPI doesn't support spring or function easings so we run these as JS animation before
8323
- * handing off.
7880
+ * NativeAnimation implements AnimationPlaybackControls for the browser's Web Animations API.
8324
7881
  */
8325
- function requiresPregeneratedKeyframes(options) {
8326
- return (isGenerator(options.type) ||
8327
- options.type === "spring" ||
8328
- !isWaapiSupportedEasing(options.ease));
8329
- }
8330
- function pregenerateKeyframes(keyframes, options) {
7882
+ class NativeAnimation extends WithPromise {
7883
+ constructor(options) {
7884
+ super();
7885
+ this.finishedTime = null;
7886
+ this.isStopped = false;
7887
+ if (!options)
7888
+ return;
7889
+ const { element, name, keyframes, pseudoElement, allowFlatten = false, finalKeyframe, } = options;
7890
+ this.isPseudoElement = Boolean(pseudoElement);
7891
+ this.allowFlatten = allowFlatten;
7892
+ this.options = options;
7893
+ invariant(typeof options.type !== "string", `animateMini doesn't support "type" as a string. Did you mean to import { spring } from "motion"?`);
7894
+ const transition = applyGeneratorOptions(options);
7895
+ this.animation = startWaapiAnimation(element, name, keyframes, transition, pseudoElement);
7896
+ if (transition.autoplay === false) {
7897
+ this.animation.pause();
7898
+ }
7899
+ this.animation.onfinish = () => {
7900
+ this.finishedTime = this.time;
7901
+ if (!pseudoElement) {
7902
+ const keyframe = getFinalKeyframe(keyframes, this.options, finalKeyframe, this.speed);
7903
+ if (this.updateMotionValue) {
7904
+ this.updateMotionValue(keyframe);
7905
+ }
7906
+ else {
7907
+ /**
7908
+ * If we can, we want to commit the final style as set by the user,
7909
+ * rather than the computed keyframe value supplied by the animation.
7910
+ */
7911
+ setStyle(element, name, keyframe);
7912
+ }
7913
+ this.animation.cancel();
7914
+ }
7915
+ this.notifyFinished();
7916
+ };
7917
+ }
7918
+ play() {
7919
+ if (this.isStopped)
7920
+ return;
7921
+ this.animation.play();
7922
+ if (this.state === "finished") {
7923
+ this.updateFinished();
7924
+ }
7925
+ }
7926
+ pause() {
7927
+ this.animation.pause();
7928
+ }
7929
+ complete() {
7930
+ this.animation.finish?.();
7931
+ }
7932
+ cancel() {
7933
+ try {
7934
+ this.animation.cancel();
7935
+ }
7936
+ catch (e) { }
7937
+ }
7938
+ stop() {
7939
+ if (this.isStopped)
7940
+ return;
7941
+ this.isStopped = true;
7942
+ const { state } = this;
7943
+ if (state === "idle" || state === "finished") {
7944
+ return;
7945
+ }
7946
+ if (this.updateMotionValue) {
7947
+ this.updateMotionValue();
7948
+ }
7949
+ else {
7950
+ this.commitStyles();
7951
+ }
7952
+ if (!this.isPseudoElement)
7953
+ this.cancel();
7954
+ }
8331
7955
  /**
8332
- * Create a main-thread animation to pregenerate keyframes.
8333
- * We sample this at regular intervals to generate keyframes that we then
8334
- * linearly interpolate between.
7956
+ * WAAPI doesn't natively have any interruption capabilities.
7957
+ *
7958
+ * In this method, we commit styles back to the DOM before cancelling
7959
+ * the animation.
7960
+ *
7961
+ * This is designed to be overridden by NativeAnimationExtended, which
7962
+ * will create a renderless JS animation and sample it twice to calculate
7963
+ * its current value, "previous" value, and therefore allow
7964
+ * Motion to also correctly calculate velocity for any subsequent animation
7965
+ * while deferring the commit until the next animation frame.
8335
7966
  */
8336
- const sampleAnimation = new MainThreadAnimation({
8337
- ...options,
8338
- keyframes,
8339
- repeat: 0,
8340
- delay: 0,
8341
- isGenerator: true,
8342
- });
8343
- let state = { done: false, value: keyframes[0] };
8344
- const pregeneratedKeyframes = [];
7967
+ commitStyles() {
7968
+ if (!this.isPseudoElement) {
7969
+ this.animation.commitStyles?.();
7970
+ }
7971
+ }
7972
+ get duration() {
7973
+ const duration = this.animation.effect?.getComputedTiming?.().duration || 0;
7974
+ return millisecondsToSeconds(Number(duration));
7975
+ }
7976
+ get time() {
7977
+ return millisecondsToSeconds(Number(this.animation.currentTime) || 0);
7978
+ }
7979
+ set time(newTime) {
7980
+ this.finishedTime = null;
7981
+ this.animation.currentTime = secondsToMilliseconds(newTime);
7982
+ }
8345
7983
  /**
8346
- * Bail after 20 seconds of pre-generated keyframes as it's likely
8347
- * we're heading for an infinite loop.
7984
+ * The playback speed of the animation.
7985
+ * 1 = normal speed, 2 = double speed, 0.5 = half speed.
8348
7986
  */
8349
- let t = 0;
8350
- while (!state.done && t < maxDuration) {
8351
- state = sampleAnimation.sample(t);
8352
- pregeneratedKeyframes.push(state.value);
8353
- t += sampleDelta;
7987
+ get speed() {
7988
+ return this.animation.playbackRate;
7989
+ }
7990
+ set speed(newSpeed) {
7991
+ // Allow backwards playback after finishing
7992
+ if (newSpeed < 0)
7993
+ this.finishedTime = null;
7994
+ this.animation.playbackRate = newSpeed;
7995
+ }
7996
+ get state() {
7997
+ return this.finishedTime !== null
7998
+ ? "finished"
7999
+ : this.animation.playState;
8000
+ }
8001
+ get startTime() {
8002
+ return Number(this.animation.startTime);
8003
+ }
8004
+ set startTime(newStartTime) {
8005
+ this.animation.startTime = newStartTime;
8006
+ }
8007
+ /**
8008
+ * Attaches a timeline to the animation, for instance the `ScrollTimeline`.
8009
+ */
8010
+ attachTimeline({ timeline, observe }) {
8011
+ if (this.allowFlatten) {
8012
+ this.animation.effect?.updateTiming({ easing: "linear" });
8013
+ }
8014
+ this.animation.onfinish = null;
8015
+ if (timeline && supportsScrollTimeline()) {
8016
+ this.animation.timeline = timeline;
8017
+ return noop;
8018
+ }
8019
+ else {
8020
+ return observe(this);
8021
+ }
8354
8022
  }
8355
- return {
8356
- times: undefined,
8357
- keyframes: pregeneratedKeyframes,
8358
- duration: t - sampleDelta,
8359
- ease: "linear",
8360
- };
8361
8023
  }
8024
+
8362
8025
  const unsupportedEasingFunctions = {
8363
8026
  anticipate,
8364
8027
  backInOut,
@@ -8367,386 +8030,334 @@ const unsupportedEasingFunctions = {
8367
8030
  function isUnsupportedEase(key) {
8368
8031
  return key in unsupportedEasingFunctions;
8369
8032
  }
8370
- class AcceleratedAnimation extends BaseAnimation {
8033
+ function replaceStringEasing(transition) {
8034
+ if (typeof transition.ease === "string" &&
8035
+ isUnsupportedEase(transition.ease)) {
8036
+ transition.ease = unsupportedEasingFunctions[transition.ease];
8037
+ }
8038
+ }
8039
+
8040
+ /**
8041
+ * 10ms is chosen here as it strikes a balance between smooth
8042
+ * results (more than one keyframe per frame at 60fps) and
8043
+ * keyframe quantity.
8044
+ */
8045
+ const sampleDelta = 10; //ms
8046
+ class NativeAnimationExtended extends NativeAnimation {
8371
8047
  constructor(options) {
8048
+ /**
8049
+ * The base NativeAnimation function only supports a subset
8050
+ * of Motion easings, and WAAPI also only supports some
8051
+ * easing functions via string/cubic-bezier definitions.
8052
+ *
8053
+ * This function replaces those unsupported easing functions
8054
+ * with a JS easing function. This will later get compiled
8055
+ * to a linear() easing function.
8056
+ */
8057
+ replaceStringEasing(options);
8058
+ /**
8059
+ * Ensure we replace the transition type with a generator function
8060
+ * before passing to WAAPI.
8061
+ *
8062
+ * TODO: Does this have a better home? It could be shared with
8063
+ * JSAnimation.
8064
+ */
8065
+ replaceTransitionType(options);
8372
8066
  super(options);
8373
- const { name, motionValue, element, keyframes } = this.options;
8374
- this.resolver = new DOMKeyframesResolver(keyframes, (resolvedKeyframes, finalKeyframe) => this.onKeyframesResolved(resolvedKeyframes, finalKeyframe), name, motionValue, element);
8375
- this.resolver.scheduleResolve();
8067
+ if (options.startTime) {
8068
+ this.startTime = options.startTime;
8069
+ }
8070
+ this.options = options;
8071
+ }
8072
+ /**
8073
+ * WAAPI doesn't natively have any interruption capabilities.
8074
+ *
8075
+ * Rather than read commited styles back out of the DOM, we can
8076
+ * create a renderless JS animation and sample it twice to calculate
8077
+ * its current value, "previous" value, and therefore allow
8078
+ * Motion to calculate velocity for any subsequent animation.
8079
+ */
8080
+ updateMotionValue(value) {
8081
+ const { motionValue, onUpdate, onComplete, element, ...options } = this.options;
8082
+ if (!motionValue)
8083
+ return;
8084
+ if (value !== undefined) {
8085
+ motionValue.set(value);
8086
+ return;
8087
+ }
8088
+ const sampleAnimation = new JSAnimation({
8089
+ ...options,
8090
+ autoplay: false,
8091
+ });
8092
+ const sampleTime = secondsToMilliseconds(this.finishedTime ?? this.time);
8093
+ motionValue.setWithVelocity(sampleAnimation.sample(sampleTime - sampleDelta).value, sampleAnimation.sample(sampleTime).value, sampleDelta);
8094
+ sampleAnimation.stop();
8095
+ }
8096
+ }
8097
+
8098
+ /**
8099
+ * Check if a value is animatable. Examples:
8100
+ *
8101
+ * ✅: 100, "100px", "#fff"
8102
+ * ❌: "block", "url(2.jpg)"
8103
+ * @param value
8104
+ *
8105
+ * @internal
8106
+ */
8107
+ const isAnimatable = (value, name) => {
8108
+ // If the list of keys tat might be non-animatable grows, replace with Set
8109
+ if (name === "zIndex")
8110
+ return false;
8111
+ // If it's a number or a keyframes array, we can animate it. We might at some point
8112
+ // need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
8113
+ // but for now lets leave it like this for performance reasons
8114
+ if (typeof value === "number" || Array.isArray(value))
8115
+ return true;
8116
+ if (typeof value === "string" && // It's animatable if we have a string
8117
+ (complex.test(value) || value === "0") && // And it contains numbers and/or colors
8118
+ !value.startsWith("url(") // Unless it starts with "url("
8119
+ ) {
8120
+ return true;
8121
+ }
8122
+ return false;
8123
+ };
8124
+
8125
+ function hasKeyframesChanged(keyframes) {
8126
+ const current = keyframes[0];
8127
+ if (keyframes.length === 1)
8128
+ return true;
8129
+ for (let i = 0; i < keyframes.length; i++) {
8130
+ if (keyframes[i] !== current)
8131
+ return true;
8132
+ }
8133
+ }
8134
+ function canAnimate(keyframes, name, type, velocity) {
8135
+ /**
8136
+ * Check if we're able to animate between the start and end keyframes,
8137
+ * and throw a warning if we're attempting to animate between one that's
8138
+ * animatable and another that isn't.
8139
+ */
8140
+ const originKeyframe = keyframes[0];
8141
+ if (originKeyframe === null)
8142
+ return false;
8143
+ /**
8144
+ * These aren't traditionally animatable but we do support them.
8145
+ * In future we could look into making this more generic or replacing
8146
+ * this function with mix() === mixImmediate
8147
+ */
8148
+ if (name === "display" || name === "visibility")
8149
+ return true;
8150
+ const targetKeyframe = keyframes[keyframes.length - 1];
8151
+ const isOriginAnimatable = isAnimatable(originKeyframe, name);
8152
+ const isTargetAnimatable = isAnimatable(targetKeyframe, name);
8153
+ warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${name} from "${originKeyframe}" to "${targetKeyframe}". ${originKeyframe} is not an animatable value - to enable this animation set ${originKeyframe} to a value animatable to ${targetKeyframe} via the \`style\` property.`);
8154
+ // Always skip if any of these are true
8155
+ if (!isOriginAnimatable || !isTargetAnimatable) {
8156
+ return false;
8157
+ }
8158
+ return (hasKeyframesChanged(keyframes) ||
8159
+ ((type === "spring" || isGenerator(type)) && velocity));
8160
+ }
8161
+
8162
+ /**
8163
+ * A list of values that can be hardware-accelerated.
8164
+ */
8165
+ const acceleratedValues = new Set([
8166
+ "opacity",
8167
+ "clipPath",
8168
+ "filter",
8169
+ "transform",
8170
+ // TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved
8171
+ // or until we implement support for linear() easing.
8172
+ // "background-color"
8173
+ ]);
8174
+ const supportsWaapi = /*@__PURE__*/ memo(() => Object.hasOwnProperty.call(Element.prototype, "animate"));
8175
+ function supportsBrowserAnimation(options) {
8176
+ const { motionValue, name, repeatDelay, repeatType, damping, type } = options;
8177
+ if (!motionValue ||
8178
+ !motionValue.owner ||
8179
+ !(motionValue.owner.current instanceof HTMLElement)) {
8180
+ return false;
8181
+ }
8182
+ const { onUpdate, transformTemplate } = motionValue.owner.getProps();
8183
+ return (supportsWaapi() &&
8184
+ name &&
8185
+ acceleratedValues.has(name) &&
8186
+ (name !== "transform" || !transformTemplate) &&
8187
+ /**
8188
+ * If we're outputting values to onUpdate then we can't use WAAPI as there's
8189
+ * no way to read the value from WAAPI every frame.
8190
+ */
8191
+ !onUpdate &&
8192
+ !repeatDelay &&
8193
+ repeatType !== "mirror" &&
8194
+ damping !== 0 &&
8195
+ type !== "inertia");
8196
+ }
8197
+
8198
+ /**
8199
+ * Maximum time allowed between an animation being created and it being
8200
+ * resolved for us to use the latter as the start time.
8201
+ *
8202
+ * This is to ensure that while we prefer to "start" an animation as soon
8203
+ * as it's triggered, we also want to avoid a visual jump if there's a big delay
8204
+ * between these two moments.
8205
+ */
8206
+ const MAX_RESOLVE_DELAY = 40;
8207
+ class AsyncMotionValueAnimation extends WithPromise {
8208
+ constructor({ autoplay = true, delay = 0, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", keyframes, name, motionValue, element, ...options }) {
8209
+ super();
8210
+ /**
8211
+ * Bound to support return animation.stop pattern
8212
+ */
8213
+ this.stop = () => {
8214
+ if (this._animation) {
8215
+ this._animation.stop();
8216
+ this.stopTimeline?.();
8217
+ }
8218
+ else {
8219
+ this.keyframeResolver?.cancel();
8220
+ }
8221
+ };
8222
+ this.createdAt = time.now();
8223
+ const optionsWithDefaults = {
8224
+ autoplay,
8225
+ delay,
8226
+ type,
8227
+ repeat,
8228
+ repeatDelay,
8229
+ repeatType,
8230
+ name,
8231
+ motionValue,
8232
+ element,
8233
+ ...options,
8234
+ };
8235
+ const KeyframeResolver$1 = element?.KeyframeResolver || KeyframeResolver;
8236
+ this.keyframeResolver = new KeyframeResolver$1(keyframes, (resolvedKeyframes, finalKeyframe, forced) => this.onKeyframesResolved(resolvedKeyframes, finalKeyframe, optionsWithDefaults, !forced), name, motionValue, element);
8237
+ this.keyframeResolver?.scheduleResolve();
8376
8238
  }
8377
- initPlayback(keyframes, finalKeyframe) {
8378
- let { duration = 300, times, ease, type, motionValue, name, startTime, } = this.options;
8239
+ onKeyframesResolved(keyframes, finalKeyframe, options, sync) {
8240
+ this.keyframeResolver = undefined;
8241
+ const { name, type, velocity, delay, isHandoff, onUpdate, onComplete } = options;
8242
+ this.resolvedAt = time.now();
8379
8243
  /**
8380
- * If element has since been unmounted, return false to indicate
8381
- * the animation failed to initialised.
8244
+ * If we can't animate this value with the resolved keyframes
8245
+ * then we should complete it immediately.
8382
8246
  */
8383
- if (!motionValue.owner || !motionValue.owner.current) {
8384
- return false;
8247
+ if (!canAnimate(keyframes, name, type, velocity)) {
8248
+ if (MotionGlobalConfig.instantAnimations || !delay) {
8249
+ onUpdate?.(getFinalKeyframe(keyframes, options, finalKeyframe));
8250
+ }
8251
+ keyframes[0] = keyframes[keyframes.length - 1];
8252
+ options.duration = 0;
8253
+ options.repeat = 0;
8385
8254
  }
8386
8255
  /**
8387
- * If the user has provided an easing function name that isn't supported
8388
- * by WAAPI (like "anticipate"), we need to provide the corressponding
8389
- * function. This will later get converted to a linear() easing function.
8256
+ * Resolve startTime for the animation.
8257
+ *
8258
+ * This method uses the createdAt and resolvedAt to calculate the
8259
+ * animation startTime. *Ideally*, we would use the createdAt time as t=0
8260
+ * as the following frame would then be the first frame of the animation in
8261
+ * progress, which would feel snappier.
8262
+ *
8263
+ * However, if there's a delay (main thread work) between the creation of
8264
+ * the animation and the first commited frame, we prefer to use resolvedAt
8265
+ * to avoid a sudden jump into the animation.
8390
8266
  */
8391
- if (typeof ease === "string" &&
8392
- supportsLinearEasing() &&
8393
- isUnsupportedEase(ease)) {
8394
- ease = unsupportedEasingFunctions[ease];
8395
- }
8267
+ const startTime = sync
8268
+ ? !this.resolvedAt
8269
+ ? this.createdAt
8270
+ : this.resolvedAt - this.createdAt > MAX_RESOLVE_DELAY
8271
+ ? this.resolvedAt
8272
+ : this.createdAt
8273
+ : undefined;
8274
+ const resolvedOptions = {
8275
+ startTime,
8276
+ finalKeyframe,
8277
+ ...options,
8278
+ keyframes,
8279
+ };
8396
8280
  /**
8397
- * If this animation needs pre-generated keyframes then generate.
8281
+ * Animate via WAAPI if possible. If this is a handoff animation, the optimised animation will be running via
8282
+ * WAAPI. Therefore, this animation must be JS to ensure it runs "under" the
8283
+ * optimised animation.
8398
8284
  */
8399
- if (requiresPregeneratedKeyframes(this.options)) {
8400
- const { onComplete, onUpdate, motionValue, element, ...options } = this.options;
8401
- const pregeneratedAnimation = pregenerateKeyframes(keyframes, options);
8402
- keyframes = pregeneratedAnimation.keyframes;
8403
- // If this is a very short animation, ensure we have
8404
- // at least two keyframes to animate between as older browsers
8405
- // can't animate between a single keyframe.
8406
- if (keyframes.length === 1) {
8407
- keyframes[1] = keyframes[0];
8408
- }
8409
- duration = pregeneratedAnimation.duration;
8410
- times = pregeneratedAnimation.times;
8411
- ease = pregeneratedAnimation.ease;
8412
- type = "keyframes";
8413
- }
8414
- const animation = startWaapiAnimation(motionValue.owner.current, name, keyframes, { ...this.options, duration, times, ease });
8415
- // Override the browser calculated startTime with one synchronised to other JS
8416
- // and WAAPI animations starting this event loop.
8417
- animation.startTime = startTime ?? this.calcStartTime();
8285
+ const animation = !isHandoff && supportsBrowserAnimation(resolvedOptions)
8286
+ ? new NativeAnimationExtended({
8287
+ ...resolvedOptions,
8288
+ element: resolvedOptions.motionValue.owner.current,
8289
+ })
8290
+ : new JSAnimation(resolvedOptions);
8291
+ animation.finished
8292
+ .then(() => {
8293
+ onComplete?.();
8294
+ this.notifyFinished();
8295
+ })
8296
+ .catch(noop);
8418
8297
  if (this.pendingTimeline) {
8419
- attachTimeline(animation, this.pendingTimeline);
8298
+ this.stopTimeline = animation.attachTimeline(this.pendingTimeline);
8420
8299
  this.pendingTimeline = undefined;
8421
8300
  }
8301
+ this._animation = animation;
8302
+ }
8303
+ get finished() {
8304
+ if (!this._animation) {
8305
+ return this._finished;
8306
+ }
8422
8307
  else {
8423
- /**
8424
- * Prefer the `onfinish` prop as it's more widely supported than
8425
- * the `finished` promise.
8426
- *
8427
- * Here, we synchronously set the provided MotionValue to the end
8428
- * keyframe. If we didn't, when the WAAPI animation is finished it would
8429
- * be removed from the element which would then revert to its old styles.
8430
- */
8431
- animation.onfinish = () => {
8432
- const { onComplete } = this.options;
8433
- motionValue.set(getFinalKeyframe(keyframes, this.options, finalKeyframe));
8434
- onComplete && onComplete();
8435
- this.cancel();
8436
- this.resolveFinishedPromise();
8437
- };
8308
+ return this.animation.finished;
8438
8309
  }
8439
- return {
8440
- animation,
8441
- duration,
8442
- times,
8443
- type,
8444
- ease,
8445
- keyframes: keyframes,
8446
- };
8310
+ }
8311
+ then(onResolve, _onReject) {
8312
+ return this.finished.finally(onResolve).then(() => { });
8313
+ }
8314
+ get animation() {
8315
+ if (!this._animation) {
8316
+ flushKeyframeResolvers();
8317
+ }
8318
+ return this._animation;
8447
8319
  }
8448
8320
  get duration() {
8449
- const { resolved } = this;
8450
- if (!resolved)
8451
- return 0;
8452
- const { duration } = resolved;
8453
- return millisecondsToSeconds(duration);
8321
+ return this.animation.duration;
8454
8322
  }
8455
8323
  get time() {
8456
- const { resolved } = this;
8457
- if (!resolved)
8458
- return 0;
8459
- const { animation } = resolved;
8460
- return millisecondsToSeconds(animation.currentTime || 0);
8324
+ return this.animation.time;
8461
8325
  }
8462
8326
  set time(newTime) {
8463
- const { resolved } = this;
8464
- if (!resolved)
8465
- return;
8466
- const { animation } = resolved;
8467
- animation.currentTime = secondsToMilliseconds(newTime);
8327
+ this.animation.time = newTime;
8468
8328
  }
8469
8329
  get speed() {
8470
- const { resolved } = this;
8471
- if (!resolved)
8472
- return 1;
8473
- const { animation } = resolved;
8474
- return animation.playbackRate;
8330
+ return this.animation.speed;
8475
8331
  }
8476
- get finished() {
8477
- return this.resolved.animation.finished;
8332
+ get state() {
8333
+ return this.animation.state;
8478
8334
  }
8479
8335
  set speed(newSpeed) {
8480
- const { resolved } = this;
8481
- if (!resolved)
8482
- return;
8483
- const { animation } = resolved;
8484
- animation.playbackRate = newSpeed;
8485
- }
8486
- get state() {
8487
- const { resolved } = this;
8488
- if (!resolved)
8489
- return "idle";
8490
- const { animation } = resolved;
8491
- return animation.playState;
8336
+ this.animation.speed = newSpeed;
8492
8337
  }
8493
8338
  get startTime() {
8494
- const { resolved } = this;
8495
- if (!resolved)
8496
- return null;
8497
- const { animation } = resolved;
8498
- // Coerce to number as TypeScript incorrectly types this
8499
- // as CSSNumberish
8500
- return animation.startTime;
8339
+ return this.animation.startTime;
8501
8340
  }
8502
- /**
8503
- * Replace the default DocumentTimeline with another AnimationTimeline.
8504
- * Currently used for scroll animations.
8505
- */
8506
8341
  attachTimeline(timeline) {
8507
- if (!this._resolved) {
8508
- this.pendingTimeline = timeline;
8342
+ if (this._animation) {
8343
+ this.stopTimeline = this.animation.attachTimeline(timeline);
8509
8344
  }
8510
8345
  else {
8511
- const { resolved } = this;
8512
- if (!resolved)
8513
- return noop;
8514
- const { animation } = resolved;
8515
- attachTimeline(animation, timeline);
8346
+ this.pendingTimeline = timeline;
8516
8347
  }
8517
- return noop;
8348
+ return () => this.stop();
8518
8349
  }
8519
8350
  play() {
8520
- if (this.isStopped)
8521
- return;
8522
- const { resolved } = this;
8523
- if (!resolved)
8524
- return;
8525
- const { animation } = resolved;
8526
- if (animation.playState === "finished") {
8527
- this.updateFinishedPromise();
8528
- }
8529
- animation.play();
8351
+ this.animation.play();
8530
8352
  }
8531
8353
  pause() {
8532
- const { resolved } = this;
8533
- if (!resolved)
8534
- return;
8535
- const { animation } = resolved;
8536
- animation.pause();
8537
- }
8538
- stop() {
8539
- this.resolver.cancel();
8540
- this.isStopped = true;
8541
- if (this.state === "idle")
8542
- return;
8543
- this.resolveFinishedPromise();
8544
- this.updateFinishedPromise();
8545
- const { resolved } = this;
8546
- if (!resolved)
8547
- return;
8548
- const { animation, keyframes, duration, type, ease, times } = resolved;
8549
- if (animation.playState === "idle" ||
8550
- animation.playState === "finished") {
8551
- return;
8552
- }
8553
- /**
8554
- * WAAPI doesn't natively have any interruption capabilities.
8555
- *
8556
- * Rather than read commited styles back out of the DOM, we can
8557
- * create a renderless JS animation and sample it twice to calculate
8558
- * its current value, "previous" value, and therefore allow
8559
- * Motion to calculate velocity for any subsequent animation.
8560
- */
8561
- if (this.time) {
8562
- const { motionValue, onUpdate, onComplete, element, ...options } = this.options;
8563
- const sampleAnimation = new MainThreadAnimation({
8564
- ...options,
8565
- keyframes,
8566
- duration,
8567
- type,
8568
- ease,
8569
- times,
8570
- isGenerator: true,
8571
- });
8572
- const sampleTime = secondsToMilliseconds(this.time);
8573
- motionValue.setWithVelocity(sampleAnimation.sample(sampleTime - sampleDelta).value, sampleAnimation.sample(sampleTime).value, sampleDelta);
8574
- }
8575
- const { onStop } = this.options;
8576
- onStop && onStop();
8577
- this.cancel();
8354
+ this.animation.pause();
8578
8355
  }
8579
8356
  complete() {
8580
- const { resolved } = this;
8581
- if (!resolved)
8582
- return;
8583
- resolved.animation.finish();
8584
- }
8585
- cancel() {
8586
- const { resolved } = this;
8587
- if (!resolved)
8588
- return;
8589
- resolved.animation.cancel();
8590
- }
8591
- static supports(options) {
8592
- const { motionValue, name, repeatDelay, repeatType, damping, type } = options;
8593
- if (!motionValue ||
8594
- !motionValue.owner ||
8595
- !(motionValue.owner.current instanceof HTMLElement)) {
8596
- return false;
8597
- }
8598
- const { onUpdate, transformTemplate } = motionValue.owner.getProps();
8599
- return (supportsWaapi() &&
8600
- name &&
8601
- acceleratedValues.has(name) &&
8602
- (name !== "transform" || !transformTemplate) &&
8603
- /**
8604
- * If we're outputting values to onUpdate then we can't use WAAPI as there's
8605
- * no way to read the value from WAAPI every frame.
8606
- */
8607
- !onUpdate &&
8608
- !repeatDelay &&
8609
- repeatType !== "mirror" &&
8610
- damping !== 0 &&
8611
- type !== "inertia");
8612
- }
8613
- }
8614
-
8615
- const underDampedSpring = {
8616
- type: "spring",
8617
- stiffness: 500,
8618
- damping: 25,
8619
- restSpeed: 10,
8620
- };
8621
- const criticallyDampedSpring = (target) => ({
8622
- type: "spring",
8623
- stiffness: 550,
8624
- damping: target === 0 ? 2 * Math.sqrt(550) : 30,
8625
- restSpeed: 10,
8626
- });
8627
- const keyframesTransition = {
8628
- type: "keyframes",
8629
- duration: 0.8,
8630
- };
8631
- /**
8632
- * Default easing curve is a slightly shallower version of
8633
- * the default browser easing curve.
8634
- */
8635
- const ease = {
8636
- type: "keyframes",
8637
- ease: [0.25, 0.1, 0.35, 1],
8638
- duration: 0.3,
8639
- };
8640
- const getDefaultTransition = (valueKey, { keyframes }) => {
8641
- if (keyframes.length > 2) {
8642
- return keyframesTransition;
8643
- }
8644
- else if (transformProps.has(valueKey)) {
8645
- return valueKey.startsWith("scale")
8646
- ? criticallyDampedSpring(keyframes[1])
8647
- : underDampedSpring;
8648
- }
8649
- return ease;
8650
- };
8651
-
8652
- /**
8653
- * Decide whether a transition is defined on a given Transition.
8654
- * This filters out orchestration options and returns true
8655
- * if any options are left.
8656
- */
8657
- function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, elapsed, ...transition }) {
8658
- return !!Object.keys(transition).length;
8659
- }
8660
-
8661
- function getValueTransition(transition, key) {
8662
- return (transition?.[key] ??
8663
- transition?.["default"] ??
8664
- transition);
8665
- }
8666
-
8667
- const supportsScrollTimeline = /* @__PURE__ */ memo(() => window.ScrollTimeline !== undefined);
8668
-
8669
- class GroupAnimation {
8670
- constructor(animations) {
8671
- // Bound to accomodate common `return animation.stop` pattern
8672
- this.stop = () => this.runAll("stop");
8673
- this.animations = animations.filter(Boolean);
8674
- }
8675
- get finished() {
8676
- return Promise.all(this.animations.map((animation) => animation.finished));
8677
- }
8678
- /**
8679
- * TODO: Filter out cancelled or stopped animations before returning
8680
- */
8681
- getAll(propName) {
8682
- return this.animations[0][propName];
8683
- }
8684
- setAll(propName, newValue) {
8685
- for (let i = 0; i < this.animations.length; i++) {
8686
- this.animations[i][propName] = newValue;
8687
- }
8688
- }
8689
- attachTimeline(timeline, fallback) {
8690
- const subscriptions = this.animations.map((animation) => {
8691
- if (supportsScrollTimeline() && animation.attachTimeline) {
8692
- return animation.attachTimeline(timeline);
8693
- }
8694
- else if (typeof fallback === "function") {
8695
- return fallback(animation);
8696
- }
8697
- });
8698
- return () => {
8699
- subscriptions.forEach((cancel, i) => {
8700
- cancel && cancel();
8701
- this.animations[i].stop();
8702
- });
8703
- };
8704
- }
8705
- get time() {
8706
- return this.getAll("time");
8707
- }
8708
- set time(time) {
8709
- this.setAll("time", time);
8710
- }
8711
- get speed() {
8712
- return this.getAll("speed");
8713
- }
8714
- set speed(speed) {
8715
- this.setAll("speed", speed);
8716
- }
8717
- get startTime() {
8718
- return this.getAll("startTime");
8719
- }
8720
- get duration() {
8721
- let max = 0;
8722
- for (let i = 0; i < this.animations.length; i++) {
8723
- max = Math.max(max, this.animations[i].duration);
8724
- }
8725
- return max;
8726
- }
8727
- runAll(methodName) {
8728
- this.animations.forEach((controls) => controls[methodName]());
8729
- }
8730
- flatten() {
8731
- this.runAll("flatten");
8732
- }
8733
- play() {
8734
- this.runAll("play");
8735
- }
8736
- pause() {
8737
- this.runAll("pause");
8357
+ this.animation.complete();
8738
8358
  }
8739
8359
  cancel() {
8740
- this.runAll("cancel");
8741
- }
8742
- complete() {
8743
- this.runAll("complete");
8744
- }
8745
- }
8746
-
8747
- class GroupAnimationWithThen extends GroupAnimation {
8748
- then(onResolve, _onReject) {
8749
- return this.finished.finally(onResolve).then(() => { });
8360
+ this.animation.cancel();
8750
8361
  }
8751
8362
  }
8752
8363
 
@@ -8764,7 +8375,7 @@ const animateMotionValue = (name, value, target, transition = {}, element, isHan
8764
8375
  */
8765
8376
  let { elapsed = 0 } = transition;
8766
8377
  elapsed = elapsed - secondsToMilliseconds(delay);
8767
- let options = {
8378
+ const options = {
8768
8379
  keyframes: Array.isArray(target) ? target : [null, target],
8769
8380
  ease: "easeOut",
8770
8381
  velocity: value.getVelocity(),
@@ -8787,22 +8398,18 @@ const animateMotionValue = (name, value, target, transition = {}, element, isHan
8787
8398
  * unique transition settings for this value.
8788
8399
  */
8789
8400
  if (!isTransitionDefined(valueTransition)) {
8790
- options = {
8791
- ...options,
8792
- ...getDefaultTransition(name, options),
8793
- };
8401
+ Object.assign(options, getDefaultTransition(name, options));
8794
8402
  }
8795
8403
  /**
8796
8404
  * Both WAAPI and our internal animation functions use durations
8797
8405
  * as defined by milliseconds, while our external API defines them
8798
8406
  * as seconds.
8799
8407
  */
8800
- if (options.duration) {
8801
- options.duration = secondsToMilliseconds(options.duration);
8802
- }
8803
- if (options.repeatDelay) {
8804
- options.repeatDelay = secondsToMilliseconds(options.repeatDelay);
8805
- }
8408
+ options.duration && (options.duration = secondsToMilliseconds(options.duration));
8409
+ options.repeatDelay && (options.repeatDelay = secondsToMilliseconds(options.repeatDelay));
8410
+ /**
8411
+ * Support deprecated way to set initial value. Prefer keyframe syntax.
8412
+ */
8806
8413
  if (options.from !== undefined) {
8807
8414
  options.keyframes[0] = options.from;
8808
8415
  }
@@ -8814,6 +8421,12 @@ const animateMotionValue = (name, value, target, transition = {}, element, isHan
8814
8421
  shouldSkip = true;
8815
8422
  }
8816
8423
  }
8424
+ if (MotionGlobalConfig.instantAnimations ||
8425
+ MotionGlobalConfig.skipAnimations) {
8426
+ shouldSkip = true;
8427
+ options.duration = 0;
8428
+ options.delay = 0;
8429
+ }
8817
8430
  /**
8818
8431
  * If the transition type or easing has been explicitly set by the user
8819
8432
  * then we don't want to allow flattening the animation.
@@ -8825,30 +8438,28 @@ const animateMotionValue = (name, value, target, transition = {}, element, isHan
8825
8438
  * this early check prevents the need to create an animation at all.
8826
8439
  */
8827
8440
  if (shouldSkip && !isHandoff && value.get() !== undefined) {
8828
- const finalKeyframe = getFinalKeyframe(options.keyframes, valueTransition);
8441
+ const finalKeyframe = getFinalKeyframe$1(options.keyframes, valueTransition);
8829
8442
  if (finalKeyframe !== undefined) {
8830
8443
  frame.update(() => {
8831
8444
  options.onUpdate(finalKeyframe);
8832
8445
  options.onComplete();
8833
8446
  });
8834
- // We still want to return some animation controls here rather
8835
- // than returning undefined
8836
- return new GroupAnimationWithThen([]);
8447
+ return;
8837
8448
  }
8838
8449
  }
8839
- /**
8840
- * Animate via WAAPI if possible. If this is a handoff animation, the optimised animation will be running via
8841
- * WAAPI. Therefore, this animation must be JS to ensure it runs "under" the
8842
- * optimised animation.
8843
- */
8844
- if (!isHandoff && AcceleratedAnimation.supports(options)) {
8845
- return new AcceleratedAnimation(options);
8846
- }
8847
- else {
8848
- return new MainThreadAnimation(options);
8849
- }
8450
+ return new AsyncMotionValueAnimation(options);
8850
8451
  };
8851
8452
 
8453
+ const positionalKeys = new Set([
8454
+ "width",
8455
+ "height",
8456
+ "top",
8457
+ "left",
8458
+ "right",
8459
+ "bottom",
8460
+ ...transformPropOrder,
8461
+ ]);
8462
+
8852
8463
  /**
8853
8464
  * Decide whether we should block this animation. Previously, we achieved this
8854
8465
  * just by checking whether the key was listed in protectedKeys, but this
@@ -10832,7 +10443,7 @@ function delay(callback, timeout) {
10832
10443
  callback(elapsed - timeout);
10833
10444
  }
10834
10445
  };
10835
- frame.read(checkElapsed, true);
10446
+ frame.setup(checkElapsed, true);
10836
10447
  return () => cancelFrame(checkElapsed);
10837
10448
  }
10838
10449
 
@@ -12126,9 +11737,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
12126
11737
  }
12127
11738
  setAnimationOrigin(delta, hasOnlyRelativeTargetChanged = false) {
12128
11739
  const snapshot = this.snapshot;
12129
- const snapshotLatestValues = snapshot
12130
- ? snapshot.latestValues
12131
- : {};
11740
+ const snapshotLatestValues = snapshot ? snapshot.latestValues : {};
12132
11741
  const mixedValues = { ...this.latestValues };
12133
11742
  const targetDelta = createDelta();
12134
11743
  if (!this.relativeParent ||
@@ -13196,15 +12805,6 @@ function initPrefersReducedMotion() {
13196
12805
  }
13197
12806
  }
13198
12807
 
13199
- /**
13200
- * A list of all ValueTypes
13201
- */
13202
- const valueTypes = [...dimensionValueTypes, color, complex];
13203
- /**
13204
- * Tests a value against the list of ValueTypes
13205
- */
13206
- const findValueType = (v) => valueTypes.find(testValueType(v));
13207
-
13208
12808
  const visualElementStore = new WeakMap();
13209
12809
 
13210
12810
  function updateMotionValuesFromProps(element, next, prev) {
@@ -13222,7 +12822,7 @@ function updateMotionValuesFromProps(element, next, prev) {
13222
12822
  * and warn against mismatches.
13223
12823
  */
13224
12824
  if (process.env.NODE_ENV === "development") {
13225
- warnOnce(nextValue.version === "12.7.4", `Attempting to mix Motion versions ${nextValue.version} with 12.7.4 may not work as expected.`);
12825
+ warnOnce(nextValue.version === "12.8.0", `Attempting to mix Motion versions ${nextValue.version} with 12.8.0 may not work as expected.`);
13226
12826
  }
13227
12827
  }
13228
12828
  else if (isMotionValue(prevValue)) {
@@ -13261,6 +12861,108 @@ function updateMotionValuesFromProps(element, next, prev) {
13261
12861
  return next;
13262
12862
  }
13263
12863
 
12864
+ /**
12865
+ * Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1"
12866
+ */
12867
+ const isNumericalString = (v) => /^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(v);
12868
+
12869
+ /**
12870
+ * Check if the value is a zero value string like "0px" or "0%"
12871
+ */
12872
+ const isZeroValueString = (v) => /^0[^.\s]+$/u.test(v);
12873
+
12874
+ /**
12875
+ * ValueType for "auto"
12876
+ */
12877
+ const auto = {
12878
+ test: (v) => v === "auto",
12879
+ parse: (v) => v,
12880
+ };
12881
+
12882
+ /**
12883
+ * Tests a provided value against a ValueType
12884
+ */
12885
+ const testValueType = (v) => (type) => type.test(v);
12886
+
12887
+ /**
12888
+ * A list of value types commonly used for dimensions
12889
+ */
12890
+ const dimensionValueTypes = [number, px, percent, degrees, vw, vh, auto];
12891
+ /**
12892
+ * Tests a dimensional value against the list of dimension ValueTypes
12893
+ */
12894
+ const findDimensionValueType = (v) => dimensionValueTypes.find(testValueType(v));
12895
+
12896
+ /**
12897
+ * A list of all ValueTypes
12898
+ */
12899
+ const valueTypes = [...dimensionValueTypes, color, complex];
12900
+ /**
12901
+ * Tests a value against the list of ValueTypes
12902
+ */
12903
+ const findValueType = (v) => valueTypes.find(testValueType(v));
12904
+
12905
+ /**
12906
+ * Properties that should default to 1 or 100%
12907
+ */
12908
+ const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]);
12909
+ function applyDefaultFilter(v) {
12910
+ const [name, value] = v.slice(0, -1).split("(");
12911
+ if (name === "drop-shadow")
12912
+ return v;
12913
+ const [number] = value.match(floatRegex) || [];
12914
+ if (!number)
12915
+ return v;
12916
+ const unit = value.replace(number, "");
12917
+ let defaultValue = maxDefaults.has(name) ? 1 : 0;
12918
+ if (number !== value)
12919
+ defaultValue *= 100;
12920
+ return name + "(" + defaultValue + unit + ")";
12921
+ }
12922
+ const functionRegex = /\b([a-z-]*)\(.*?\)/gu;
12923
+ const filter = {
12924
+ ...complex,
12925
+ getAnimatableNone: (v) => {
12926
+ const functions = v.match(functionRegex);
12927
+ return functions ? functions.map(applyDefaultFilter).join(" ") : v;
12928
+ },
12929
+ };
12930
+
12931
+ /**
12932
+ * A map of default value types for common values
12933
+ */
12934
+ const defaultValueTypes = {
12935
+ ...numberValueTypes,
12936
+ // Color props
12937
+ color,
12938
+ backgroundColor: color,
12939
+ outlineColor: color,
12940
+ fill: color,
12941
+ stroke: color,
12942
+ // Border props
12943
+ borderColor: color,
12944
+ borderTopColor: color,
12945
+ borderRightColor: color,
12946
+ borderBottomColor: color,
12947
+ borderLeftColor: color,
12948
+ filter,
12949
+ WebkitFilter: filter,
12950
+ };
12951
+ /**
12952
+ * Gets the default ValueType for the provided value key
12953
+ */
12954
+ const getDefaultValueType = (key) => defaultValueTypes[key];
12955
+
12956
+ function getAnimatableNone(key, value) {
12957
+ let defaultValueType = getDefaultValueType(key);
12958
+ if (defaultValueType !== filter)
12959
+ defaultValueType = complex;
12960
+ // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
12961
+ return defaultValueType.getAnimatableNone
12962
+ ? defaultValueType.getAnimatableNone(value)
12963
+ : undefined;
12964
+ }
12965
+
13264
12966
  const propEventHandlers = [
13265
12967
  "AnimationStart",
13266
12968
  "AnimationComplete",
@@ -13718,6 +13420,202 @@ class VisualElement {
13718
13420
  }
13719
13421
  }
13720
13422
 
13423
+ /**
13424
+ * Parse Framer's special CSS variable format into a CSS token and a fallback.
13425
+ *
13426
+ * ```
13427
+ * `var(--foo, #fff)` => [`--foo`, '#fff']
13428
+ * ```
13429
+ *
13430
+ * @param current
13431
+ */
13432
+ const splitCSSVariableRegex =
13433
+ // eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive, as it can match a lot of words
13434
+ /^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u;
13435
+ function parseCSSVariable(current) {
13436
+ const match = splitCSSVariableRegex.exec(current);
13437
+ if (!match)
13438
+ return [,];
13439
+ const [, token1, token2, fallback] = match;
13440
+ return [`--${token1 ?? token2}`, fallback];
13441
+ }
13442
+ const maxDepth = 4;
13443
+ function getVariableValue(current, element, depth = 1) {
13444
+ invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`);
13445
+ const [token, fallback] = parseCSSVariable(current);
13446
+ // No CSS variable detected
13447
+ if (!token)
13448
+ return;
13449
+ // Attempt to read this CSS variable off the element
13450
+ const resolved = window.getComputedStyle(element).getPropertyValue(token);
13451
+ if (resolved) {
13452
+ const trimmed = resolved.trim();
13453
+ return isNumericalString(trimmed) ? parseFloat(trimmed) : trimmed;
13454
+ }
13455
+ return isCSSVariableToken(fallback)
13456
+ ? getVariableValue(fallback, element, depth + 1)
13457
+ : fallback;
13458
+ }
13459
+
13460
+ function isNone(value) {
13461
+ if (typeof value === "number") {
13462
+ return value === 0;
13463
+ }
13464
+ else if (value !== null) {
13465
+ return value === "none" || value === "0" || isZeroValueString(value);
13466
+ }
13467
+ else {
13468
+ return true;
13469
+ }
13470
+ }
13471
+
13472
+ /**
13473
+ * If we encounter keyframes like "none" or "0" and we also have keyframes like
13474
+ * "#fff" or "200px 200px" we want to find a keyframe to serve as a template for
13475
+ * the "none" keyframes. In this case "#fff" or "200px 200px" - then these get turned into
13476
+ * zero equivalents, i.e. "#fff0" or "0px 0px".
13477
+ */
13478
+ const invalidTemplates = new Set(["auto", "none", "0"]);
13479
+ function makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name) {
13480
+ let i = 0;
13481
+ let animatableTemplate = undefined;
13482
+ while (i < unresolvedKeyframes.length && !animatableTemplate) {
13483
+ const keyframe = unresolvedKeyframes[i];
13484
+ if (typeof keyframe === "string" &&
13485
+ !invalidTemplates.has(keyframe) &&
13486
+ analyseComplexValue(keyframe).values.length) {
13487
+ animatableTemplate = unresolvedKeyframes[i];
13488
+ }
13489
+ i++;
13490
+ }
13491
+ if (animatableTemplate && name) {
13492
+ for (const noneIndex of noneKeyframeIndexes) {
13493
+ unresolvedKeyframes[noneIndex] = getAnimatableNone(name, animatableTemplate);
13494
+ }
13495
+ }
13496
+ }
13497
+
13498
+ class DOMKeyframesResolver extends KeyframeResolver {
13499
+ constructor(unresolvedKeyframes, onComplete, name, motionValue, element) {
13500
+ super(unresolvedKeyframes, onComplete, name, motionValue, element, true);
13501
+ }
13502
+ readKeyframes() {
13503
+ const { unresolvedKeyframes, element, name } = this;
13504
+ if (!element || !element.current)
13505
+ return;
13506
+ super.readKeyframes();
13507
+ /**
13508
+ * If any keyframe is a CSS variable, we need to find its value by sampling the element
13509
+ */
13510
+ for (let i = 0; i < unresolvedKeyframes.length; i++) {
13511
+ let keyframe = unresolvedKeyframes[i];
13512
+ if (typeof keyframe === "string") {
13513
+ keyframe = keyframe.trim();
13514
+ if (isCSSVariableToken(keyframe)) {
13515
+ const resolved = getVariableValue(keyframe, element.current);
13516
+ if (resolved !== undefined) {
13517
+ unresolvedKeyframes[i] = resolved;
13518
+ }
13519
+ if (i === unresolvedKeyframes.length - 1) {
13520
+ this.finalKeyframe = keyframe;
13521
+ }
13522
+ }
13523
+ }
13524
+ }
13525
+ /**
13526
+ * Resolve "none" values. We do this potentially twice - once before and once after measuring keyframes.
13527
+ * This could be seen as inefficient but it's a trade-off to avoid measurements in more situations, which
13528
+ * have a far bigger performance impact.
13529
+ */
13530
+ this.resolveNoneKeyframes();
13531
+ /**
13532
+ * Check to see if unit type has changed. If so schedule jobs that will
13533
+ * temporarily set styles to the destination keyframes.
13534
+ * Skip if we have more than two keyframes or this isn't a positional value.
13535
+ * TODO: We can throw if there are multiple keyframes and the value type changes.
13536
+ */
13537
+ if (!positionalKeys.has(name) || unresolvedKeyframes.length !== 2) {
13538
+ return;
13539
+ }
13540
+ const [origin, target] = unresolvedKeyframes;
13541
+ const originType = findDimensionValueType(origin);
13542
+ const targetType = findDimensionValueType(target);
13543
+ /**
13544
+ * Either we don't recognise these value types or we can animate between them.
13545
+ */
13546
+ if (originType === targetType)
13547
+ return;
13548
+ /**
13549
+ * If both values are numbers or pixels, we can animate between them by
13550
+ * converting them to numbers.
13551
+ */
13552
+ if (isNumOrPxType(originType) && isNumOrPxType(targetType)) {
13553
+ for (let i = 0; i < unresolvedKeyframes.length; i++) {
13554
+ const value = unresolvedKeyframes[i];
13555
+ if (typeof value === "string") {
13556
+ unresolvedKeyframes[i] = parseFloat(value);
13557
+ }
13558
+ }
13559
+ }
13560
+ else {
13561
+ /**
13562
+ * Else, the only way to resolve this is by measuring the element.
13563
+ */
13564
+ this.needsMeasurement = true;
13565
+ }
13566
+ }
13567
+ resolveNoneKeyframes() {
13568
+ const { unresolvedKeyframes, name } = this;
13569
+ const noneKeyframeIndexes = [];
13570
+ for (let i = 0; i < unresolvedKeyframes.length; i++) {
13571
+ if (unresolvedKeyframes[i] === null ||
13572
+ isNone(unresolvedKeyframes[i])) {
13573
+ noneKeyframeIndexes.push(i);
13574
+ }
13575
+ }
13576
+ if (noneKeyframeIndexes.length) {
13577
+ makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name);
13578
+ }
13579
+ }
13580
+ measureInitialState() {
13581
+ const { element, unresolvedKeyframes, name } = this;
13582
+ if (!element || !element.current)
13583
+ return;
13584
+ if (name === "height") {
13585
+ this.suspendedScrollY = window.pageYOffset;
13586
+ }
13587
+ this.measuredOrigin = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
13588
+ unresolvedKeyframes[0] = this.measuredOrigin;
13589
+ // Set final key frame to measure after next render
13590
+ const measureKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
13591
+ if (measureKeyframe !== undefined) {
13592
+ element.getValue(name, measureKeyframe).jump(measureKeyframe, false);
13593
+ }
13594
+ }
13595
+ measureEndState() {
13596
+ const { element, name, unresolvedKeyframes } = this;
13597
+ if (!element || !element.current)
13598
+ return;
13599
+ const value = element.getValue(name);
13600
+ value && value.jump(this.measuredOrigin, false);
13601
+ const finalKeyframeIndex = unresolvedKeyframes.length - 1;
13602
+ const finalKeyframe = unresolvedKeyframes[finalKeyframeIndex];
13603
+ unresolvedKeyframes[finalKeyframeIndex] = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
13604
+ if (finalKeyframe !== null && this.finalKeyframe === undefined) {
13605
+ this.finalKeyframe = finalKeyframe;
13606
+ }
13607
+ // If we removed transform values, reapply them before the next render
13608
+ if (this.removedTransforms?.length) {
13609
+ this.removedTransforms.forEach(([unsetTransformName, unsetTransformValue]) => {
13610
+ element
13611
+ .getValue(unsetTransformName)
13612
+ .set(unsetTransformValue);
13613
+ });
13614
+ }
13615
+ this.resolveNoneKeyframes();
13616
+ }
13617
+ }
13618
+
13721
13619
  class DOMVisualElement extends VisualElement {
13722
13620
  constructor() {
13723
13621
  super(...arguments);
@@ -18547,12 +18445,12 @@ function deepEqual(object1, object2) {
18547
18445
  }
18548
18446
 
18549
18447
  const useDeepEqualEffect = (effect, deps) => {
18550
- const ref = React.useRef(deps);
18448
+ const ref = React__namespace.useRef(deps);
18551
18449
  if (!deepEqual(deps, ref.current)) {
18552
18450
  ref.current = deps;
18553
18451
  }
18554
18452
  // eslint-disable-next-line react-hooks/exhaustive-deps
18555
- React.useEffect(effect, ref.current);
18453
+ React__namespace.useEffect(effect, ref.current);
18556
18454
  };
18557
18455
 
18558
18456
  /**
@@ -20537,6 +20435,7 @@ function useForm(props = {}) {
20537
20435
  ...data,
20538
20436
  isReady: true,
20539
20437
  }));
20438
+ control._formState.isReady = true;
20540
20439
  return sub;
20541
20440
  }, [control]);
20542
20441
  React.useEffect(() => control._disableForm(props.disabled), [control, props.disabled]);
@@ -34144,20 +34043,20 @@ var initStripe = function initStripe(maybeStripe, args, startTime) {
34144
34043
  return stripe;
34145
34044
  }; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
34146
34045
 
34147
- var stripePromise$2;
34046
+ var stripePromise;
34148
34047
  var loadCalled = false;
34149
34048
 
34150
34049
  var getStripePromise = function getStripePromise() {
34151
- if (stripePromise$2) {
34152
- return stripePromise$2;
34050
+ if (stripePromise) {
34051
+ return stripePromise;
34153
34052
  }
34154
34053
 
34155
- stripePromise$2 = loadScript(null)["catch"](function (error) {
34054
+ stripePromise = loadScript(null)["catch"](function (error) {
34156
34055
  // clear cache on error
34157
- stripePromise$2 = null;
34056
+ stripePromise = null;
34158
34057
  return Promise.reject(error);
34159
34058
  });
34160
- return stripePromise$2;
34059
+ return stripePromise;
34161
34060
  }; // Execute our own script injection after a tick to give users time to do their
34162
34061
  // own script injection.
34163
34062
 
@@ -34227,9 +34126,8 @@ const CheckoutForm$1 = ({ onSuccess, onError, children, setSubmitting, }) => {
34227
34126
  };
34228
34127
  var CheckoutForm$2 = React.memo(CheckoutForm$1);
34229
34128
 
34230
- const publicStripeKey = "pk_live_51QvliXK5wvEuxX36GWLFgMtUrG2cGIjpW0eXoqVzjEr8S0PdGzAp4ydQa6ssxVW9u0zaLajod93YZnQIU5C8cgqp00Bb64X60b";
34231
- const stripePromise = loadStripe(publicStripeKey);
34232
- function PaymentElement({ paymentSecret, checkoutAppearance, locale, fonts, onSuccess, onError, children, setSubmitting, }) {
34129
+ function PaymentElement({ paymentSecret, publicKey, checkoutAppearance, locale, fonts, onSuccess, onError, children, setSubmitting, }) {
34130
+ const stripePromise = loadStripe(publicKey !== null && publicKey !== void 0 ? publicKey : "");
34233
34131
  const options = {
34234
34132
  locale: locale !== null && locale !== void 0 ? locale : "en",
34235
34133
  appearance: checkoutAppearance,
@@ -34241,7 +34139,7 @@ function PaymentElement({ paymentSecret, checkoutAppearance, locale, fonts, onSu
34241
34139
  }
34242
34140
  var PaymentElement$1 = React.memo(PaymentElement);
34243
34141
 
34244
- function PaymentForm({ paymentSecret, onSuccess, onError, onBack, onDoubleBack, contactEmail, shippingAddress, shippingProvider, shippingPrice, checkoutAppearance, fonts, locale, }) {
34142
+ function PaymentForm({ paymentSecret, onSuccess, onError, onBack, onDoubleBack, contactEmail, shippingAddress, shippingName, shippingPrice, checkoutAppearance, fonts, locale, publicKey, }) {
34245
34143
  const [isSubmitting, setIsSubmitting] = React.useState(false);
34246
34144
  const { t } = useTranslation();
34247
34145
  return (React.createElement("div", { className: "space-y-6" },
@@ -34257,7 +34155,7 @@ function PaymentForm({ paymentSecret, onSuccess, onError, onBack, onDoubleBack,
34257
34155
  React.createElement(Button, { variant: "link", size: "link", onClick: onDoubleBack }, t("CheckoutEmbed.Shipping.change"))),
34258
34156
  React.createElement("div", { className: "flex items-center justify-between text-sm" },
34259
34157
  React.createElement("p", null,
34260
- React.createElement("span", { className: "font-medium" }, t("CheckoutEmbed.Shipping.shipTo")),
34158
+ React.createElement("span", { className: "font-medium" }, t("CheckoutEmbed.Shipping.address")),
34261
34159
  " ",
34262
34160
  React.createElement("span", { className: "text-muted-foreground" }, shippingAddress)),
34263
34161
  React.createElement(Button, { variant: "link", size: "link", onClick: onDoubleBack }, t("CheckoutEmbed.Shipping.change"))),
@@ -34266,11 +34164,11 @@ function PaymentForm({ paymentSecret, onSuccess, onError, onBack, onDoubleBack,
34266
34164
  React.createElement("span", { className: "font-medium" }, t("CheckoutEmbed.Shipping.shipping")),
34267
34165
  " ",
34268
34166
  React.createElement("span", { className: "text-muted-foreground" },
34269
- shippingProvider,
34167
+ shippingName,
34270
34168
  " \u00B7 ",
34271
34169
  shippingPrice)),
34272
34170
  React.createElement(Button, { variant: "link", size: "link", onClick: onBack }, t("CheckoutEmbed.Shipping.change")))),
34273
- React.createElement("div", { className: "mt-8" }, paymentSecret && (React.createElement(PaymentElement$1, { fonts: fonts, checkoutAppearance: convertCheckoutAppearanceToStripeAppearance(checkoutAppearance, fonts), locale: locale, paymentSecret: paymentSecret, onSuccess: onSuccess, onError: onError, setSubmitting: setIsSubmitting },
34171
+ React.createElement("div", { className: "mt-8" }, paymentSecret && (React.createElement(PaymentElement$1, { fonts: fonts, checkoutAppearance: convertCheckoutAppearanceToStripeAppearance(checkoutAppearance, fonts), locale: locale, paymentSecret: paymentSecret, onSuccess: onSuccess, onError: onError, setSubmitting: setIsSubmitting, publicKey: publicKey },
34274
34172
  React.createElement("div", { className: "flex justify-between items-center pt-8" },
34275
34173
  React.createElement(Button, { type: "button", variant: "ghost", onClick: onBack },
34276
34174
  React.createElement(ChevronLeft, null),
@@ -34354,7 +34252,7 @@ function ShippingMethodForm({ shippingRates, initialData, onSubmit, onBack, cont
34354
34252
  React.createElement(Button, { variant: "link", size: "link", onClick: onBack }, t("CheckoutEmbed.Shipping.change"))),
34355
34253
  React.createElement("div", { className: "flex items-center justify-between text-sm" },
34356
34254
  React.createElement("p", null,
34357
- React.createElement("span", { className: "font-medium" }, t("CheckoutEmbed.Shipping.shipTo")),
34255
+ React.createElement("span", { className: "font-medium" }, t("CheckoutEmbed.Shipping.address")),
34358
34256
  " ",
34359
34257
  React.createElement("span", { className: "text-muted-foreground" }, shippingAddress)),
34360
34258
  React.createElement(Button, { variant: "link", size: "link", onClick: onBack }, t("CheckoutEmbed.Shipping.change")))),
@@ -34449,6 +34347,7 @@ const motionSettings = {
34449
34347
  function CheckoutForm({ storeClient, checkoutId, onSuccess, onError, cancelUrl, clientSecret, customer, currency, checkoutAppearance, fonts, locale, setShippingCost, exchangeRate, }) {
34450
34348
  const { formData, setFormData, step, setStep } = useFormStore();
34451
34349
  const [paymentSecret, setPaymentSecret] = React.useState(null);
34350
+ const [publicKey, setPublicKey] = React.useState(null);
34452
34351
  const [shippingRates, setShippingRates] = React.useState([]);
34453
34352
  const validateStep = React.useCallback(() => {
34454
34353
  if (step === "customer")
@@ -34472,6 +34371,7 @@ function CheckoutForm({ storeClient, checkoutId, onSuccess, onError, cancelUrl,
34472
34371
  React.useEffect(() => {
34473
34372
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
34474
34373
  if (customer && !((_a = formData.customer) === null || _a === void 0 ? void 0 : _a.email)) {
34374
+ const step = customer.id ? "shipping" : "customer";
34475
34375
  setFormData(Object.assign(Object.assign({}, formData), { customerId: customer.id, customer: {
34476
34376
  firstName: (_d = (_c = (_b = customer.address) === null || _b === void 0 ? void 0 : _b.name) === null || _c === void 0 ? void 0 : _c.split(" ")[0]) !== null && _d !== void 0 ? _d : "",
34477
34377
  lastName: (_g = (_f = (_e = customer.address) === null || _e === void 0 ? void 0 : _e.name) === null || _f === void 0 ? void 0 : _f.split(" ")[1]) !== null && _g !== void 0 ? _g : "",
@@ -34486,6 +34386,7 @@ function CheckoutForm({ storeClient, checkoutId, onSuccess, onError, cancelUrl,
34486
34386
  countryCode: (_x = (_w = customer.address) === null || _w === void 0 ? void 0 : _w.countryCode) !== null && _x !== void 0 ? _x : "",
34487
34387
  },
34488
34388
  } }));
34389
+ setStep(step);
34489
34390
  }
34490
34391
  }, [customer]);
34491
34392
  React.useEffect(() => {
@@ -34546,8 +34447,9 @@ function CheckoutForm({ storeClient, checkoutId, onSuccess, onError, cancelUrl,
34546
34447
  pickupPointId: data.pickupPointId,
34547
34448
  },
34548
34449
  });
34549
- const paymentSecret = yield storeClient.generateCheckoutsPaymentSecret(clientSecret, checkoutId);
34450
+ const { paymentSecret, publicKey } = yield storeClient.generateCheckoutsPaymentSecret(clientSecret, checkoutId);
34550
34451
  setPaymentSecret(paymentSecret);
34452
+ setPublicKey(publicKey);
34551
34453
  setShippingCost(data.price);
34552
34454
  setStep("payment");
34553
34455
  });
@@ -34566,8 +34468,9 @@ function CheckoutForm({ storeClient, checkoutId, onSuccess, onError, cancelUrl,
34566
34468
  };
34567
34469
  React.useEffect(() => {
34568
34470
  const asyncFunc = () => __awaiter(this, void 0, void 0, function* () {
34569
- const paymentSecret = yield storeClient.generateCheckoutsPaymentSecret(clientSecret, checkoutId);
34471
+ const { paymentSecret, publicKey } = yield storeClient.generateCheckoutsPaymentSecret(clientSecret, checkoutId);
34570
34472
  setPaymentSecret(paymentSecret);
34473
+ setPublicKey(publicKey);
34571
34474
  });
34572
34475
  if (!paymentSecret && step === "payment") {
34573
34476
  asyncFunc();
@@ -34580,7 +34483,7 @@ function CheckoutForm({ storeClient, checkoutId, onSuccess, onError, cancelUrl,
34580
34483
  step === "shipping" && formData.customer && (React.createElement(motion.div, Object.assign({ key: "shipping" }, motionSettings, { className: "absolute w-full" }),
34581
34484
  React.createElement(ShippingMethodForm, { setFormData: setFormData, formData: formData, shippingRates: shippingRates, initialData: formData.shipping, onSubmit: handleShippingSubmit, onBack: handleBack, contactEmail: formData.customer.email, shippingAddress: formatAddress(formData.customer.address), currency: currency, exchangeRate: exchangeRate, locale: locale, countryCode: formData.customer.address.countryCode }))),
34582
34485
  step === "payment" && formData.customer && formData.shipping && (React.createElement(motion.div, Object.assign({ key: "payment" }, motionSettings, { className: "absolute w-full" }),
34583
- React.createElement(PaymentForm, { locale: locale, fonts: fonts, checkoutAppearance: checkoutAppearance, paymentSecret: paymentSecret, onSuccess: onSuccess, onError: onError, onBack: handleBack, onDoubleBack: handleDoubleBack, contactEmail: formData.customer.email, shippingAddress: formatAddress(formData.customer.address), shippingProvider: formData.shipping.provider, shippingPrice: storeHelpers.formatPrice(formData.shipping.price, currency, exchangeRate) }))))));
34486
+ React.createElement(PaymentForm, { locale: locale, fonts: fonts, checkoutAppearance: checkoutAppearance, paymentSecret: paymentSecret, onSuccess: onSuccess, onError: onError, onBack: handleBack, onDoubleBack: handleDoubleBack, contactEmail: formData.customer.email, shippingAddress: formatAddress(formData.customer.address), shippingName: formData.shipping.name, shippingPrice: storeHelpers.formatPrice(formData.shipping.price, currency, exchangeRate), publicKey: publicKey }))))));
34584
34487
  }
34585
34488
 
34586
34489
  function CheckoutFormLoading() {
@@ -34609,7 +34512,13 @@ function CheckoutSummary({ lineItems, shipping, tax, currency, cancelUrl, exchan
34609
34512
  const { t } = useTranslation();
34610
34513
  const subtotal = lineItems.reduce((acc, item) => {
34611
34514
  var _a, _b;
34612
- const variant = (_a = item.product) === null || _a === void 0 ? void 0 : _a.productVariants.find((variant) => variant.variantOptions === item.variantOptions);
34515
+ const variant = (_a = item.product) === null || _a === void 0 ? void 0 : _a.productVariants.find((variant) => {
34516
+ if (!variant.variantOptions || !item.variantOptions)
34517
+ return false;
34518
+ if (variant.variantOptions.length !== item.variantOptions.length)
34519
+ return false;
34520
+ return variant.variantOptions.every((vOpt) => item.variantOptions.some((iOpt) => vOpt.name === iOpt.name && vOpt.value === iOpt.value));
34521
+ });
34613
34522
  const productItem = variant || item.product;
34614
34523
  return acc + ((_b = productItem === null || productItem === void 0 ? void 0 : productItem.priceInCents) !== null && _b !== void 0 ? _b : 0) * item.quantity;
34615
34524
  }, 0);
@@ -34653,7 +34562,13 @@ function CheckoutSummary({ lineItems, shipping, tax, currency, cancelUrl, exchan
34653
34562
  grid: isOpen,
34654
34563
  }) }, lineItems.map((item, index) => {
34655
34564
  var _a, _b, _c, _d, _e;
34656
- const variant = (_a = item.product) === null || _a === void 0 ? void 0 : _a.productVariants.find((variant) => variant.variantOptions === item.variantOptions);
34565
+ const variant = (_a = item.product) === null || _a === void 0 ? void 0 : _a.productVariants.find((variant) => {
34566
+ if (!variant.variantOptions || !item.variantOptions)
34567
+ return false;
34568
+ if (variant.variantOptions.length !== item.variantOptions.length)
34569
+ return false;
34570
+ return variant.variantOptions.every((vOpt) => item.variantOptions.some((iOpt) => vOpt.name === iOpt.name && vOpt.value === iOpt.value));
34571
+ });
34657
34572
  const productItem = variant || item.product;
34658
34573
  return (React.createElement("div", { key: index, className: "flex items-center" },
34659
34574
  React.createElement("div", { className: "relative" },