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