framer-motion 10.5.0 → 10.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var wrap = require('./wrap-bf172f81.js');
5
+ var wrap = require('./wrap-992dcd37.js');
6
6
 
7
7
 
8
8
 
package/dist/cjs/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var React = require('react');
6
- var wrap = require('./wrap-bf172f81.js');
6
+ var wrap = require('./wrap-992dcd37.js');
7
7
 
8
8
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
9
9
 
@@ -5295,6 +5295,16 @@ function useSpring(source, config = {}) {
5295
5295
  ...config,
5296
5296
  onUpdate: set,
5297
5297
  });
5298
+ /**
5299
+ * If we're between frames, resync the animation to the frameloop.
5300
+ */
5301
+ if (!wrap.frameData.isProcessing) {
5302
+ const delta = performance.now() - wrap.frameData.timestamp;
5303
+ if (delta < 30) {
5304
+ activeSpringAnimation.current.time =
5305
+ wrap.millisecondsToSeconds(delta);
5306
+ }
5307
+ }
5298
5308
  return value.get();
5299
5309
  }, stopAnimation);
5300
5310
  }, [JSON.stringify(config)]);
@@ -818,17 +818,57 @@ function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }) {
818
818
  return keyframes[index];
819
819
  }
820
820
 
821
- // Accepts an easing function and returns a new one that outputs mirrored values for
822
- // the second half of the animation. Turns easeIn into easeInOut.
823
- const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
824
-
825
- // Accepts an easing function and returns a new one that outputs reversed values.
826
- // Turns easeIn into easeOut.
827
- const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
821
+ /*
822
+ Bezier function generator
823
+ This has been modified from Gaëtan Renaudeau's BezierEasing
824
+ https://github.com/gre/bezier-easing/blob/master/src/index.js
825
+ https://github.com/gre/bezier-easing/blob/master/LICENSE
826
+
827
+ I've removed the newtonRaphsonIterate algo because in benchmarking it
828
+ wasn't noticiably faster than binarySubdivision, indeed removing it
829
+ usually improved times, depending on the curve.
830
+ I also removed the lookup table, as for the added bundle size and loop we're
831
+ only cutting ~4 or so subdivision iterations. I bumped the max iterations up
832
+ to 12 to compensate and this still tended to be faster for no perceivable
833
+ loss in accuracy.
834
+ Usage
835
+ const easeOut = cubicBezier(.17,.67,.83,.67);
836
+ const x = easeOut(0.5); // returns 0.627...
837
+ */
838
+ // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
839
+ const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) *
840
+ t;
841
+ const subdivisionPrecision = 0.0000001;
842
+ const subdivisionMaxIterations = 12;
843
+ function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
844
+ let currentX;
845
+ let currentT;
846
+ let i = 0;
847
+ do {
848
+ currentT = lowerBound + (upperBound - lowerBound) / 2.0;
849
+ currentX = calcBezier(currentT, mX1, mX2) - x;
850
+ if (currentX > 0.0) {
851
+ upperBound = currentT;
852
+ }
853
+ else {
854
+ lowerBound = currentT;
855
+ }
856
+ } while (Math.abs(currentX) > subdivisionPrecision &&
857
+ ++i < subdivisionMaxIterations);
858
+ return currentT;
859
+ }
860
+ function cubicBezier(mX1, mY1, mX2, mY2) {
861
+ // If this is a linear gradient, return linear easing
862
+ if (mX1 === mY1 && mX2 === mY2)
863
+ return noop;
864
+ const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
865
+ // If animation is at start/end, return t without easing
866
+ return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
867
+ }
828
868
 
829
- const easeIn = (p) => p * p;
830
- const easeOut = reverseEasing(easeIn);
831
- const easeInOut = mirrorEasing(easeIn);
869
+ const easeIn = cubicBezier(0.42, 0, 1, 1);
870
+ const easeOut = cubicBezier(0, 0, 0.58, 1);
871
+ const easeInOut = cubicBezier(0.42, 0, 0.58, 1);
832
872
 
833
873
  /**
834
874
  * Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000,
@@ -1269,53 +1309,13 @@ function convertOffsetToTimes(offset, duration) {
1269
1309
  return offset.map((o) => o * duration);
1270
1310
  }
1271
1311
 
1272
- /*
1273
- Bezier function generator
1274
- This has been modified from Gaëtan Renaudeau's BezierEasing
1275
- https://github.com/gre/bezier-easing/blob/master/src/index.js
1276
- https://github.com/gre/bezier-easing/blob/master/LICENSE
1277
-
1278
- I've removed the newtonRaphsonIterate algo because in benchmarking it
1279
- wasn't noticiably faster than binarySubdivision, indeed removing it
1280
- usually improved times, depending on the curve.
1281
- I also removed the lookup table, as for the added bundle size and loop we're
1282
- only cutting ~4 or so subdivision iterations. I bumped the max iterations up
1283
- to 12 to compensate and this still tended to be faster for no perceivable
1284
- loss in accuracy.
1285
- Usage
1286
- const easeOut = cubicBezier(.17,.67,.83,.67);
1287
- const x = easeOut(0.5); // returns 0.627...
1288
- */
1289
- // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
1290
- const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) *
1291
- t;
1292
- const subdivisionPrecision = 0.0000001;
1293
- const subdivisionMaxIterations = 12;
1294
- function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
1295
- let currentX;
1296
- let currentT;
1297
- let i = 0;
1298
- do {
1299
- currentT = lowerBound + (upperBound - lowerBound) / 2.0;
1300
- currentX = calcBezier(currentT, mX1, mX2) - x;
1301
- if (currentX > 0.0) {
1302
- upperBound = currentT;
1303
- }
1304
- else {
1305
- lowerBound = currentT;
1306
- }
1307
- } while (Math.abs(currentX) > subdivisionPrecision &&
1308
- ++i < subdivisionMaxIterations);
1309
- return currentT;
1310
- }
1311
- function cubicBezier(mX1, mY1, mX2, mY2) {
1312
- // If this is a linear gradient, return linear easing
1313
- if (mX1 === mY1 && mX2 === mY2)
1314
- return noop;
1315
- const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
1316
- // If animation is at start/end, return t without easing
1317
- return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
1318
- }
1312
+ // Accepts an easing function and returns a new one that outputs mirrored values for
1313
+ // the second half of the animation. Turns easeIn into easeInOut.
1314
+ const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
1315
+
1316
+ // Accepts an easing function and returns a new one that outputs reversed values.
1317
+ // Turns easeIn into easeOut.
1318
+ const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
1319
1319
 
1320
1320
  const circIn = (p) => 1 - Math.sin(Math.acos(p));
1321
1321
  const circOut = reverseEasing(circIn);
@@ -1743,7 +1743,16 @@ function calculateDuration(generator) {
1743
1743
  }
1744
1744
  return duration;
1745
1745
  }
1746
+ /**
1747
+ * Animate a single value on the main thread.
1748
+ *
1749
+ * This function is written, where functionality overlaps,
1750
+ * to be largely spec-compliant with WAAPI to allow fungibility
1751
+ * between the two.
1752
+ */
1746
1753
  function animateValue({ autoplay = true, delay = 0, driver = frameloopDriver, keyframes: keyframes$1, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", onPlay, onStop, onComplete, onUpdate, ...options }) {
1754
+ let speed = 1;
1755
+ let hasStopped = false;
1747
1756
  let resolveFinishedPromise;
1748
1757
  let currentFinishedPromise;
1749
1758
  /**
@@ -1752,6 +1761,7 @@ function animateValue({ autoplay = true, delay = 0, driver = frameloopDriver, ke
1752
1761
  * WAAPI-compatible behaviour.
1753
1762
  */
1754
1763
  const updateFinishedPromise = () => {
1764
+ resolveFinishedPromise && resolveFinishedPromise();
1755
1765
  currentFinishedPromise = new Promise((resolve) => {
1756
1766
  resolveFinishedPromise = resolve;
1757
1767
  });
@@ -1785,6 +1795,7 @@ function animateValue({ autoplay = true, delay = 0, driver = frameloopDriver, ke
1785
1795
  let playState = "idle";
1786
1796
  let holdTime = null;
1787
1797
  let startTime = null;
1798
+ let cancelTime = null;
1788
1799
  /**
1789
1800
  * If duration is undefined and we have repeat options,
1790
1801
  * we need to calculate a duration from the generator.
@@ -1811,7 +1822,7 @@ function animateValue({ autoplay = true, delay = 0, driver = frameloopDriver, ke
1811
1822
  time = holdTime;
1812
1823
  }
1813
1824
  else {
1814
- time = timestamp - startTime;
1825
+ time = (timestamp - startTime) * speed;
1815
1826
  }
1816
1827
  // Rebase on delay
1817
1828
  time = Math.max(time - delay, 0);
@@ -1849,10 +1860,11 @@ function animateValue({ autoplay = true, delay = 0, driver = frameloopDriver, ke
1849
1860
  iterationProgress = 1;
1850
1861
  }
1851
1862
  iterationProgress === 1 && currentIteration--;
1863
+ currentIteration = Math.min(currentIteration, repeat + 1);
1852
1864
  /**
1853
1865
  * Reverse progress if we're not running in "normal" direction
1854
1866
  */
1855
- const iterationIsOdd = currentIteration % 2;
1867
+ const iterationIsOdd = Boolean(currentIteration % 2);
1856
1868
  if (iterationIsOdd) {
1857
1869
  if (repeatType === "reverse") {
1858
1870
  iterationProgress = 1 - iterationProgress;
@@ -1864,36 +1876,51 @@ function animateValue({ autoplay = true, delay = 0, driver = frameloopDriver, ke
1864
1876
  frameGenerator = mirroredGenerator;
1865
1877
  }
1866
1878
  }
1867
- const p = time >= totalDuration
1868
- ? repeatType === "reverse" && iterationIsOdd
1869
- ? 0
1870
- : 1
1871
- : clamp(0, 1, iterationProgress);
1879
+ let p = clamp(0, 1, iterationProgress);
1880
+ if (time > totalDuration) {
1881
+ p = repeatType === "reverse" && iterationIsOdd ? 1 : 0;
1882
+ }
1872
1883
  elapsed = p * resolvedDuration;
1873
1884
  }
1874
1885
  const state = frameGenerator.next(elapsed);
1875
- let { value, done } = state;
1876
- if (onUpdate) {
1877
- onUpdate(mapNumbersToKeyframes ? mapNumbersToKeyframes(value) : value);
1886
+ if (mapNumbersToKeyframes) {
1887
+ state.value = mapNumbersToKeyframes(state.value);
1878
1888
  }
1889
+ let { done } = state;
1879
1890
  if (calculatedDuration !== null) {
1880
1891
  done = time >= totalDuration;
1881
1892
  }
1882
1893
  const isAnimationFinished = holdTime === null &&
1883
- (playState === "finished" || (playState === "running" && done));
1894
+ (playState === "finished" ||
1895
+ (playState === "running" && done) ||
1896
+ (speed < 0 && time <= 0));
1897
+ if (onUpdate) {
1898
+ onUpdate(state.value);
1899
+ }
1884
1900
  if (isAnimationFinished) {
1885
1901
  finish();
1886
1902
  }
1887
1903
  return state;
1888
1904
  };
1889
- const finish = () => {
1905
+ const stopAnimationDriver = () => {
1890
1906
  animationDriver && animationDriver.stop();
1907
+ animationDriver = undefined;
1908
+ };
1909
+ const cancel = () => {
1910
+ playState = "idle";
1911
+ stopAnimationDriver();
1912
+ updateFinishedPromise();
1913
+ startTime = cancelTime = null;
1914
+ };
1915
+ const finish = () => {
1891
1916
  playState = "finished";
1892
1917
  onComplete && onComplete();
1893
- resolveFinishedPromise();
1918
+ stopAnimationDriver();
1894
1919
  updateFinishedPromise();
1895
1920
  };
1896
1921
  const play = () => {
1922
+ if (hasStopped)
1923
+ return;
1897
1924
  if (!animationDriver)
1898
1925
  animationDriver = driver(tick);
1899
1926
  const now = animationDriver.now();
@@ -1907,6 +1934,7 @@ function animateValue({ autoplay = true, delay = 0, driver = frameloopDriver, ke
1907
1934
  // logic around finished animations
1908
1935
  startTime = now;
1909
1936
  }
1937
+ cancelTime = startTime;
1910
1938
  holdTime = null;
1911
1939
  animationDriver.start();
1912
1940
  };
@@ -1923,13 +1951,22 @@ function animateValue({ autoplay = true, delay = 0, driver = frameloopDriver, ke
1923
1951
  set time(newTime) {
1924
1952
  newTime = secondsToMilliseconds(newTime);
1925
1953
  time = newTime;
1926
- if (holdTime !== null || !animationDriver) {
1954
+ if (holdTime !== null || !animationDriver || speed === 0) {
1927
1955
  holdTime = newTime;
1928
1956
  }
1929
1957
  else {
1930
- startTime = animationDriver.now() - newTime;
1958
+ startTime = animationDriver.now() - newTime / speed;
1931
1959
  }
1932
1960
  },
1961
+ get speed() {
1962
+ return speed;
1963
+ },
1964
+ set speed(newSpeed) {
1965
+ if (newSpeed === speed || !animationDriver)
1966
+ return;
1967
+ speed = newSpeed;
1968
+ controls.time = millisecondsToSeconds(time);
1969
+ },
1933
1970
  get state() {
1934
1971
  return playState;
1935
1972
  },
@@ -1939,12 +1976,20 @@ function animateValue({ autoplay = true, delay = 0, driver = frameloopDriver, ke
1939
1976
  holdTime = time;
1940
1977
  },
1941
1978
  stop: () => {
1979
+ hasStopped = true;
1942
1980
  if (playState === "idle")
1943
1981
  return;
1944
1982
  playState = "idle";
1945
1983
  onStop && onStop();
1946
- animationDriver && animationDriver.stop();
1947
- animationDriver = undefined;
1984
+ cancel();
1985
+ },
1986
+ cancel: () => {
1987
+ if (cancelTime !== null)
1988
+ tick(cancelTime);
1989
+ cancel();
1990
+ },
1991
+ complete: () => {
1992
+ playState = "finished";
1948
1993
  },
1949
1994
  sample: (elapsed) => {
1950
1995
  startTime = 0;
@@ -1990,6 +2035,7 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
1990
2035
  /**
1991
2036
  * TODO: Unify with js/index
1992
2037
  */
2038
+ let hasStopped = false;
1993
2039
  let resolveFinishedPromise;
1994
2040
  let currentFinishedPromise;
1995
2041
  /**
@@ -2043,6 +2089,11 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
2043
2089
  */
2044
2090
  ease: ease,
2045
2091
  });
2092
+ const safeCancel = () => {
2093
+ sync.update(() => animation.cancel());
2094
+ resolveFinishedPromise();
2095
+ updateFinishedPromise();
2096
+ };
2046
2097
  /**
2047
2098
  * Prefer the `onfinish` prop as it's more widely supported than
2048
2099
  * the `finished` promise.
@@ -2053,10 +2104,8 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
2053
2104
  */
2054
2105
  animation.onfinish = () => {
2055
2106
  value.set(getFinalKeyframe(keyframes, options));
2056
- sync.update(() => animation.cancel());
2057
2107
  onComplete && onComplete();
2058
- resolveFinishedPromise();
2059
- updateFinishedPromise();
2108
+ safeCancel();
2060
2109
  };
2061
2110
  /**
2062
2111
  * Animation interrupt callback.
@@ -2071,9 +2120,20 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
2071
2120
  set time(newTime) {
2072
2121
  animation.currentTime = secondsToMilliseconds(newTime);
2073
2122
  },
2074
- play: () => animation.play(),
2123
+ get speed() {
2124
+ return animation.playbackRate;
2125
+ },
2126
+ set speed(newSpeed) {
2127
+ animation.playbackRate = newSpeed;
2128
+ },
2129
+ play: () => {
2130
+ if (hasStopped)
2131
+ return;
2132
+ animation.play();
2133
+ },
2075
2134
  pause: () => animation.pause(),
2076
2135
  stop: () => {
2136
+ hasStopped = true;
2077
2137
  if (animation.playState === "idle")
2078
2138
  return;
2079
2139
  /**
@@ -2092,8 +2152,10 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
2092
2152
  });
2093
2153
  value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta).value, sampleAnimation.sample(currentTime).value, sampleDelta);
2094
2154
  }
2095
- sync.update(() => animation.cancel());
2155
+ safeCancel();
2096
2156
  },
2157
+ complete: () => animation.finish(),
2158
+ cancel: safeCancel,
2097
2159
  };
2098
2160
  }
2099
2161
 
@@ -2101,8 +2163,16 @@ function createInstantAnimation({ keyframes, delay: delayBy, onUpdate, onComplet
2101
2163
  const setValue = () => {
2102
2164
  onUpdate && onUpdate(keyframes[keyframes.length - 1]);
2103
2165
  onComplete && onComplete();
2166
+ /**
2167
+ * TODO: As this API grows it could make sense to always return
2168
+ * animateValue. This will be a bigger project as animateValue
2169
+ * is frame-locked whereas this function resolves instantly.
2170
+ * This is a behavioural change and also has ramifications regarding
2171
+ * assumptions within tests.
2172
+ */
2104
2173
  return {
2105
2174
  time: 0,
2175
+ speed: 1,
2106
2176
  play: (noop),
2107
2177
  pause: (noop),
2108
2178
  stop: (noop),
@@ -2110,6 +2180,8 @@ function createInstantAnimation({ keyframes, delay: delayBy, onUpdate, onComplet
2110
2180
  resolve();
2111
2181
  return Promise.resolve();
2112
2182
  },
2183
+ cancel: (noop),
2184
+ complete: (noop),
2113
2185
  };
2114
2186
  };
2115
2187
  return delayBy
@@ -2334,6 +2406,7 @@ const animateMotionValue = (valueName, value, target, transition = {}) => {
2334
2406
  let options = {
2335
2407
  keyframes,
2336
2408
  velocity: value.getVelocity(),
2409
+ ease: "easeOut",
2337
2410
  ...valueTransition,
2338
2411
  delay: -elapsed,
2339
2412
  onUpdate: (v) => {
@@ -2496,7 +2569,7 @@ class MotionValue {
2496
2569
  * This will be replaced by the build step with the latest version number.
2497
2570
  * When MotionValues are provided to motion components, warn if versions are mixed.
2498
2571
  */
2499
- this.version = "10.5.0";
2572
+ this.version = "10.6.1";
2500
2573
  /**
2501
2574
  * Duration, in milliseconds, since last updating frame.
2502
2575
  *
@@ -3265,18 +3338,26 @@ class GroupPlaybackControls {
3265
3338
  /**
3266
3339
  * TODO: Filter out cancelled or stopped animations before returning
3267
3340
  */
3268
- get time() {
3269
- return this.animations[0].time;
3341
+ getAll(propName) {
3342
+ return this.animations[0][propName];
3270
3343
  }
3271
- /**
3272
- * time assignment could reasonably run every frame, so
3273
- * we iterate using a normal loop to avoid function creation.
3274
- */
3275
- set time(time) {
3344
+ setAll(propName, newValue) {
3276
3345
  for (let i = 0; i < this.animations.length; i++) {
3277
- this.animations[i].time = time;
3346
+ this.animations[i][propName] = newValue;
3278
3347
  }
3279
3348
  }
3349
+ get time() {
3350
+ return this.getAll("time");
3351
+ }
3352
+ set time(time) {
3353
+ this.setAll("time", time);
3354
+ }
3355
+ get speed() {
3356
+ return this.getAll("speed");
3357
+ }
3358
+ set speed(speed) {
3359
+ this.setAll("speed", speed);
3360
+ }
3280
3361
  runAll(methodName) {
3281
3362
  this.animations.forEach((controls) => controls[methodName]());
3282
3363
  }
@@ -3289,6 +3370,12 @@ class GroupPlaybackControls {
3289
3370
  stop() {
3290
3371
  this.runAll("stop");
3291
3372
  }
3373
+ cancel() {
3374
+ this.runAll("cancel");
3375
+ }
3376
+ complete() {
3377
+ this.runAll("complete");
3378
+ }
3292
3379
  }
3293
3380
 
3294
3381
  function isDOMKeyframes(keyframes) {
@@ -3641,7 +3728,7 @@ function updateMotionValuesFromProps(element, next, prev) {
3641
3728
  * and warn against mismatches.
3642
3729
  */
3643
3730
  if (process.env.NODE_ENV === "development") {
3644
- warnOnce(nextValue.version === "10.5.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 10.5.0 may not work as expected.`);
3731
+ warnOnce(nextValue.version === "10.6.1", `Attempting to mix Framer Motion versions ${nextValue.version} with 10.6.1 may not work as expected.`);
3645
3732
  }
3646
3733
  }
3647
3734
  else if (isMotionValue(prevValue)) {
@@ -818,9 +818,12 @@ declare type ElementOrSelector = Element | Element[] | NodeListOf<Element> | str
818
818
  */
819
819
  interface AnimationPlaybackControls {
820
820
  time: number;
821
+ speed: number;
821
822
  stop: () => void;
822
823
  play: () => void;
823
824
  pause: () => void;
825
+ complete: () => void;
826
+ cancel: () => void;
824
827
  then: (onResolve: VoidFunction, onReject?: VoidFunction) => Promise<void>;
825
828
  }
826
829
  interface CSSStyleDeclarationWithTransform extends Omit<CSSStyleDeclaration, "direction" | "transition"> {
@@ -1078,9 +1081,9 @@ declare const circIn: EasingFunction;
1078
1081
  declare const circOut: EasingFunction;
1079
1082
  declare const circInOut: EasingFunction;
1080
1083
 
1081
- declare const easeIn: (p: number) => number;
1082
- declare const easeOut: EasingFunction;
1083
- declare const easeInOut: EasingFunction;
1084
+ declare const easeIn: (t: number) => number;
1085
+ declare const easeOut: (t: number) => number;
1086
+ declare const easeInOut: (t: number) => number;
1084
1087
 
1085
1088
  declare function cubicBezier(mX1: number, mY1: number, mX2: number, mY2: number): (t: number) => number;
1086
1089
 
@@ -8,18 +8,26 @@ class GroupPlaybackControls {
8
8
  /**
9
9
  * TODO: Filter out cancelled or stopped animations before returning
10
10
  */
11
- get time() {
12
- return this.animations[0].time;
11
+ getAll(propName) {
12
+ return this.animations[0][propName];
13
13
  }
14
- /**
15
- * time assignment could reasonably run every frame, so
16
- * we iterate using a normal loop to avoid function creation.
17
- */
18
- set time(time) {
14
+ setAll(propName, newValue) {
19
15
  for (let i = 0; i < this.animations.length; i++) {
20
- this.animations[i].time = time;
16
+ this.animations[i][propName] = newValue;
21
17
  }
22
18
  }
19
+ get time() {
20
+ return this.getAll("time");
21
+ }
22
+ set time(time) {
23
+ this.setAll("time", time);
24
+ }
25
+ get speed() {
26
+ return this.getAll("speed");
27
+ }
28
+ set speed(speed) {
29
+ this.setAll("speed", speed);
30
+ }
23
31
  runAll(methodName) {
24
32
  this.animations.forEach((controls) => controls[methodName]());
25
33
  }
@@ -32,6 +40,12 @@ class GroupPlaybackControls {
32
40
  stop() {
33
41
  this.runAll("stop");
34
42
  }
43
+ cancel() {
44
+ this.runAll("cancel");
45
+ }
46
+ complete() {
47
+ this.runAll("complete");
48
+ }
35
49
  }
36
50
 
37
51
  export { GroupPlaybackControls };
@@ -5,8 +5,16 @@ function createInstantAnimation({ keyframes, delay: delayBy, onUpdate, onComplet
5
5
  const setValue = () => {
6
6
  onUpdate && onUpdate(keyframes[keyframes.length - 1]);
7
7
  onComplete && onComplete();
8
+ /**
9
+ * TODO: As this API grows it could make sense to always return
10
+ * animateValue. This will be a bigger project as animateValue
11
+ * is frame-locked whereas this function resolves instantly.
12
+ * This is a behavioural change and also has ramifications regarding
13
+ * assumptions within tests.
14
+ */
8
15
  return {
9
16
  time: 0,
17
+ speed: 1,
10
18
  play: (noop),
11
19
  pause: (noop),
12
20
  stop: (noop),
@@ -14,6 +22,8 @@ function createInstantAnimation({ keyframes, delay: delayBy, onUpdate, onComplet
14
22
  resolve();
15
23
  return Promise.resolve();
16
24
  },
25
+ cancel: (noop),
26
+ complete: (noop),
17
27
  };
18
28
  };
19
29
  return delayBy