framer-motion 7.8.0 → 7.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +6 -5
  2. package/dist/cjs/index.js +2030 -1920
  3. package/dist/es/animation/animate.mjs +2 -2
  4. package/dist/es/animation/create-instant-animation.mjs +12 -0
  5. package/dist/es/animation/{animation-controls.mjs → hooks/animation-controls.mjs} +2 -2
  6. package/dist/es/animation/{use-animated-state.mjs → hooks/use-animated-state.mjs} +6 -6
  7. package/dist/es/animation/{use-animation.mjs → hooks/use-animation.mjs} +1 -1
  8. package/dist/es/animation/index.mjs +124 -0
  9. package/dist/es/animation/legacy-popmotion/decay.mjs +11 -4
  10. package/dist/es/animation/legacy-popmotion/index.mjs +22 -11
  11. package/dist/es/animation/legacy-popmotion/inertia.mjs +14 -8
  12. package/dist/es/animation/legacy-popmotion/keyframes.mjs +21 -13
  13. package/dist/es/animation/legacy-popmotion/spring.mjs +13 -11
  14. package/dist/es/animation/utils/default-transitions.mjs +9 -14
  15. package/dist/es/animation/utils/keyframes.mjs +41 -0
  16. package/dist/es/animation/utils/transitions.mjs +1 -166
  17. package/dist/es/animation/waapi/create-accelerated-animation.mjs +82 -0
  18. package/dist/es/animation/waapi/index.mjs +4 -6
  19. package/dist/es/gestures/drag/VisualElementDragControls.mjs +2 -2
  20. package/dist/es/index.mjs +3 -3
  21. package/dist/es/render/VisualElement.mjs +1 -1
  22. package/dist/es/render/utils/animation.mjs +2 -2
  23. package/dist/es/render/utils/motion-values.mjs +2 -2
  24. package/dist/es/render/utils/setters.mjs +1 -1
  25. package/dist/es/value/index.mjs +11 -5
  26. package/dist/es/value/use-spring.mjs +1 -2
  27. package/dist/framer-motion.dev.js +2051 -1941
  28. package/dist/framer-motion.js +1 -1
  29. package/dist/index.d.ts +409 -348
  30. package/dist/projection.dev.js +1672 -1535
  31. package/dist/size-rollup-dom-animation-assets.js +1 -1
  32. package/dist/size-rollup-dom-animation.js +1 -1
  33. package/dist/size-rollup-dom-max-assets.js +1 -1
  34. package/dist/size-rollup-dom-max.js +1 -1
  35. package/dist/size-rollup-motion.js +1 -1
  36. package/dist/size-webpack-dom-animation.js +1 -1
  37. package/dist/size-webpack-dom-max.js +1 -1
  38. package/dist/three-entry.d.ts +287 -281
  39. package/package.json +8 -8
@@ -161,386 +161,514 @@
161
161
  onNextFrame(processFrame);
162
162
  };
163
163
 
164
- function addUniqueItem(arr, item) {
165
- if (arr.indexOf(item) === -1)
166
- arr.push(item);
164
+ var warning = function () { };
165
+ var invariant = function () { };
166
+ {
167
+ warning = function (check, message) {
168
+ if (!check && typeof console !== 'undefined') {
169
+ console.warn(message);
170
+ }
171
+ };
172
+ invariant = function (check, message) {
173
+ if (!check) {
174
+ throw new Error(message);
175
+ }
176
+ };
167
177
  }
168
- function removeItem(arr, item) {
169
- const index = arr.indexOf(item);
170
- if (index > -1)
171
- arr.splice(index, 1);
178
+
179
+ /**
180
+ * Converts seconds to milliseconds
181
+ *
182
+ * @param seconds - Time in seconds.
183
+ * @return milliseconds - Converted time in milliseconds.
184
+ */
185
+ const secondsToMilliseconds = (seconds) => seconds * 1000;
186
+
187
+ const instantAnimationState = {
188
+ current: false,
189
+ };
190
+
191
+ // Accepts an easing function and returns a new one that outputs mirrored values for
192
+ // the second half of the animation. Turns easeIn into easeInOut.
193
+ const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
194
+
195
+ // Accepts an easing function and returns a new one that outputs reversed values.
196
+ // Turns easeIn into easeOut.
197
+ const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
198
+
199
+ const easeIn = (p) => p * p;
200
+ const easeOut = reverseEasing(easeIn);
201
+ const easeInOut = mirrorEasing(easeIn);
202
+
203
+ /**
204
+ * TODO: When we move from string as a source of truth to data models
205
+ * everything in this folder should probably be referred to as models vs types
206
+ */
207
+ // If this number is a decimal, make it just five decimal places
208
+ // to avoid exponents
209
+ const sanitize = (v) => Math.round(v * 100000) / 100000;
210
+ const floatRegex = /(-)?([\d]*\.?[\d])+/g;
211
+ const colorRegex = /(#[0-9a-f]{6}|#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))/gi;
212
+ const singleColorRegex = /^(#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))$/i;
213
+ function isString(v) {
214
+ return typeof v === "string";
172
215
  }
173
216
 
174
- class SubscriptionManager {
175
- constructor() {
176
- this.subscriptions = [];
217
+ const clamp = (min, max, v) => Math.min(Math.max(v, min), max);
218
+
219
+ const number = {
220
+ test: (v) => typeof v === "number",
221
+ parse: parseFloat,
222
+ transform: (v) => v,
223
+ };
224
+ const alpha = {
225
+ ...number,
226
+ transform: (v) => clamp(0, 1, v),
227
+ };
228
+ const scale = {
229
+ ...number,
230
+ default: 1,
231
+ };
232
+
233
+ /**
234
+ * Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000,
235
+ * but false if a number or multiple colors
236
+ */
237
+ const isColorString = (type, testProp) => (v) => {
238
+ return Boolean((isString(v) && singleColorRegex.test(v) && v.startsWith(type)) ||
239
+ (testProp && Object.prototype.hasOwnProperty.call(v, testProp)));
240
+ };
241
+ const splitColor = (aName, bName, cName) => (v) => {
242
+ if (!isString(v))
243
+ return v;
244
+ const [a, b, c, alpha] = v.match(floatRegex);
245
+ return {
246
+ [aName]: parseFloat(a),
247
+ [bName]: parseFloat(b),
248
+ [cName]: parseFloat(c),
249
+ alpha: alpha !== undefined ? parseFloat(alpha) : 1,
250
+ };
251
+ };
252
+
253
+ const clampRgbUnit = (v) => clamp(0, 255, v);
254
+ const rgbUnit = {
255
+ ...number,
256
+ transform: (v) => Math.round(clampRgbUnit(v)),
257
+ };
258
+ const rgba = {
259
+ test: isColorString("rgb", "red"),
260
+ parse: splitColor("red", "green", "blue"),
261
+ transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => "rgba(" +
262
+ rgbUnit.transform(red) +
263
+ ", " +
264
+ rgbUnit.transform(green) +
265
+ ", " +
266
+ rgbUnit.transform(blue) +
267
+ ", " +
268
+ sanitize(alpha.transform(alpha$1)) +
269
+ ")",
270
+ };
271
+
272
+ function parseHex(v) {
273
+ let r = "";
274
+ let g = "";
275
+ let b = "";
276
+ let a = "";
277
+ // If we have 6 characters, ie #FF0000
278
+ if (v.length > 5) {
279
+ r = v.substring(1, 3);
280
+ g = v.substring(3, 5);
281
+ b = v.substring(5, 7);
282
+ a = v.substring(7, 9);
283
+ // Or we have 3 characters, ie #F00
177
284
  }
178
- add(handler) {
179
- addUniqueItem(this.subscriptions, handler);
180
- return () => removeItem(this.subscriptions, handler);
285
+ else {
286
+ r = v.substring(1, 2);
287
+ g = v.substring(2, 3);
288
+ b = v.substring(3, 4);
289
+ a = v.substring(4, 5);
290
+ r += r;
291
+ g += g;
292
+ b += b;
293
+ a += a;
181
294
  }
182
- notify(a, b, c) {
183
- const numSubscriptions = this.subscriptions.length;
184
- if (!numSubscriptions)
185
- return;
186
- if (numSubscriptions === 1) {
187
- /**
188
- * If there's only a single handler we can just call it without invoking a loop.
189
- */
190
- this.subscriptions[0](a, b, c);
295
+ return {
296
+ red: parseInt(r, 16),
297
+ green: parseInt(g, 16),
298
+ blue: parseInt(b, 16),
299
+ alpha: a ? parseInt(a, 16) / 255 : 1,
300
+ };
301
+ }
302
+ const hex = {
303
+ test: isColorString("#"),
304
+ parse: parseHex,
305
+ transform: rgba.transform,
306
+ };
307
+
308
+ const createUnitType = (unit) => ({
309
+ test: (v) => isString(v) && v.endsWith(unit) && v.split(" ").length === 1,
310
+ parse: parseFloat,
311
+ transform: (v) => `${v}${unit}`,
312
+ });
313
+ const degrees = createUnitType("deg");
314
+ const percent = createUnitType("%");
315
+ const px = createUnitType("px");
316
+ const vh = createUnitType("vh");
317
+ const vw = createUnitType("vw");
318
+ const progressPercentage = {
319
+ ...percent,
320
+ parse: (v) => percent.parse(v) / 100,
321
+ transform: (v) => percent.transform(v * 100),
322
+ };
323
+
324
+ const hsla = {
325
+ test: isColorString("hsl", "hue"),
326
+ parse: splitColor("hue", "saturation", "lightness"),
327
+ transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => {
328
+ return ("hsla(" +
329
+ Math.round(hue) +
330
+ ", " +
331
+ percent.transform(sanitize(saturation)) +
332
+ ", " +
333
+ percent.transform(sanitize(lightness)) +
334
+ ", " +
335
+ sanitize(alpha.transform(alpha$1)) +
336
+ ")");
337
+ },
338
+ };
339
+
340
+ const color = {
341
+ test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v),
342
+ parse: (v) => {
343
+ if (rgba.test(v)) {
344
+ return rgba.parse(v);
345
+ }
346
+ else if (hsla.test(v)) {
347
+ return hsla.parse(v);
191
348
  }
192
349
  else {
193
- for (let i = 0; i < numSubscriptions; i++) {
194
- /**
195
- * Check whether the handler exists before firing as it's possible
196
- * the subscriptions were modified during this loop running.
197
- */
198
- const handler = this.subscriptions[i];
199
- handler && handler(a, b, c);
200
- }
350
+ return hex.parse(v);
201
351
  }
202
- }
203
- getSize() {
204
- return this.subscriptions.length;
205
- }
206
- clear() {
207
- this.subscriptions.length = 0;
208
- }
209
- }
352
+ },
353
+ transform: (v) => {
354
+ return isString(v)
355
+ ? v
356
+ : v.hasOwnProperty("red")
357
+ ? rgba.transform(v)
358
+ : hsla.transform(v);
359
+ },
360
+ };
210
361
 
211
362
  /*
212
- Convert velocity into velocity per second
363
+ Value in range from progress
213
364
 
214
- @param [number]: Unit per frame
215
- @param [number]: Frame duration in ms
365
+ Given a lower limit and an upper limit, we return the value within
366
+ that range as expressed by progress (usually a number from 0 to 1)
367
+
368
+ So progress = 0.5 would change
369
+
370
+ from -------- to
371
+
372
+ to
373
+
374
+ from ---- to
375
+
376
+ E.g. from = 10, to = 20, progress = 0.5 => 15
377
+
378
+ @param [number]: Lower limit of range
379
+ @param [number]: Upper limit of range
380
+ @param [number]: The progress between lower and upper limits expressed 0-1
381
+ @return [number]: Value as calculated from progress within range (not limited within range)
216
382
  */
217
- function velocityPerSecond(velocity, frameDuration) {
218
- return frameDuration ? velocity * (1000 / frameDuration) : 0;
219
- }
383
+ const mix = (from, to, progress) => -progress * from + progress * to + from;
220
384
 
221
- const isFloat = (value) => {
222
- return !isNaN(parseFloat(value));
223
- };
224
- /**
225
- * `MotionValue` is used to track the state and velocity of motion values.
226
- *
227
- * @public
228
- */
229
- class MotionValue {
230
- /**
231
- * @param init - The initiating value
232
- * @param config - Optional configuration options
233
- *
234
- * - `transformer`: A function to transform incoming values with.
235
- *
236
- * @internal
237
- */
238
- constructor(init) {
239
- /**
240
- * This will be replaced by the build step with the latest version number.
241
- * When MotionValues are provided to motion components, warn if versions are mixed.
242
- */
243
- this.version = "7.8.0";
244
- /**
245
- * Duration, in milliseconds, since last updating frame.
246
- *
247
- * @internal
248
- */
249
- this.timeDelta = 0;
250
- /**
251
- * Timestamp of the last time this `MotionValue` was updated.
252
- *
253
- * @internal
254
- */
255
- this.lastUpdated = 0;
256
- /**
257
- * Functions to notify when the `MotionValue` updates.
258
- *
259
- * @internal
260
- */
261
- this.updateSubscribers = new SubscriptionManager();
262
- /**
263
- * Functions to notify when the velocity updates.
264
- *
265
- * @internal
266
- */
267
- this.velocityUpdateSubscribers = new SubscriptionManager();
268
- /**
269
- * Functions to notify when the `MotionValue` updates and `render` is set to `true`.
270
- *
271
- * @internal
272
- */
273
- this.renderSubscribers = new SubscriptionManager();
274
- /**
275
- * Tracks whether this value can output a velocity. Currently this is only true
276
- * if the value is numerical, but we might be able to widen the scope here and support
277
- * other value types.
278
- *
279
- * @internal
280
- */
281
- this.canTrackVelocity = false;
282
- this.updateAndNotify = (v, render = true) => {
283
- this.prev = this.current;
284
- this.current = v;
285
- // Update timestamp
286
- const { delta, timestamp } = frameData;
287
- if (this.lastUpdated !== timestamp) {
288
- this.timeDelta = delta;
289
- this.lastUpdated = timestamp;
290
- sync.postRender(this.scheduleVelocityCheck);
291
- }
292
- // Update update subscribers
293
- if (this.prev !== this.current) {
294
- this.updateSubscribers.notify(this.current);
295
- }
296
- // Update velocity subscribers
297
- if (this.velocityUpdateSubscribers.getSize()) {
298
- this.velocityUpdateSubscribers.notify(this.getVelocity());
299
- }
300
- // Update render subscribers
301
- if (render) {
302
- this.renderSubscribers.notify(this.current);
303
- }
304
- };
305
- /**
306
- * Schedule a velocity check for the next frame.
307
- *
308
- * This is an instanced and bound function to prevent generating a new
309
- * function once per frame.
310
- *
311
- * @internal
312
- */
313
- this.scheduleVelocityCheck = () => sync.postRender(this.velocityCheck);
314
- /**
315
- * Updates `prev` with `current` if the value hasn't been updated this frame.
316
- * This ensures velocity calculations return `0`.
317
- *
318
- * This is an instanced and bound function to prevent generating a new
319
- * function once per frame.
320
- *
321
- * @internal
322
- */
323
- this.velocityCheck = ({ timestamp }) => {
324
- if (timestamp !== this.lastUpdated) {
325
- this.prev = this.current;
326
- this.velocityUpdateSubscribers.notify(this.getVelocity());
327
- }
328
- };
329
- this.hasAnimated = false;
330
- this.prev = this.current = init;
331
- this.canTrackVelocity = isFloat(this.current);
385
+ // Adapted from https://gist.github.com/mjackson/5311256
386
+ function hueToRgb(p, q, t) {
387
+ if (t < 0)
388
+ t += 1;
389
+ if (t > 1)
390
+ t -= 1;
391
+ if (t < 1 / 6)
392
+ return p + (q - p) * 6 * t;
393
+ if (t < 1 / 2)
394
+ return q;
395
+ if (t < 2 / 3)
396
+ return p + (q - p) * (2 / 3 - t) * 6;
397
+ return p;
398
+ }
399
+ function hslaToRgba({ hue, saturation, lightness, alpha }) {
400
+ hue /= 360;
401
+ saturation /= 100;
402
+ lightness /= 100;
403
+ let red = 0;
404
+ let green = 0;
405
+ let blue = 0;
406
+ if (!saturation) {
407
+ red = green = blue = lightness;
332
408
  }
333
- /**
334
- * Adds a function that will be notified when the `MotionValue` is updated.
335
- *
336
- * It returns a function that, when called, will cancel the subscription.
337
- *
338
- * When calling `onChange` inside a React component, it should be wrapped with the
339
- * `useEffect` hook. As it returns an unsubscribe function, this should be returned
340
- * from the `useEffect` function to ensure you don't add duplicate subscribers..
341
- *
342
- * ```jsx
343
- * export const MyComponent = () => {
344
- * const x = useMotionValue(0)
345
- * const y = useMotionValue(0)
346
- * const opacity = useMotionValue(1)
347
- *
348
- * useEffect(() => {
349
- * function updateOpacity() {
350
- * const maxXY = Math.max(x.get(), y.get())
351
- * const newOpacity = transform(maxXY, [0, 100], [1, 0])
352
- * opacity.set(newOpacity)
353
- * }
354
- *
355
- * const unsubscribeX = x.onChange(updateOpacity)
356
- * const unsubscribeY = y.onChange(updateOpacity)
357
- *
358
- * return () => {
359
- * unsubscribeX()
360
- * unsubscribeY()
361
- * }
362
- * }, [])
363
- *
364
- * return <motion.div style={{ x }} />
365
- * }
366
- * ```
367
- *
368
- * @privateRemarks
369
- *
370
- * We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
371
- *
372
- * ```jsx
373
- * useOnChange(x, () => {})
374
- * ```
375
- *
376
- * @param subscriber - A function that receives the latest value.
377
- * @returns A function that, when called, will cancel this subscription.
378
- *
379
- * @public
380
- */
381
- onChange(subscription) {
382
- return this.updateSubscribers.add(subscription);
409
+ else {
410
+ const q = lightness < 0.5
411
+ ? lightness * (1 + saturation)
412
+ : lightness + saturation - lightness * saturation;
413
+ const p = 2 * lightness - q;
414
+ red = hueToRgb(p, q, hue + 1 / 3);
415
+ green = hueToRgb(p, q, hue);
416
+ blue = hueToRgb(p, q, hue - 1 / 3);
383
417
  }
384
- clearListeners() {
385
- this.updateSubscribers.clear();
418
+ return {
419
+ red: Math.round(red * 255),
420
+ green: Math.round(green * 255),
421
+ blue: Math.round(blue * 255),
422
+ alpha,
423
+ };
424
+ }
425
+
426
+ // Linear color space blending
427
+ // Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
428
+ // Demonstrated http://codepen.io/osublake/pen/xGVVaN
429
+ const mixLinearColor = (from, to, v) => {
430
+ const fromExpo = from * from;
431
+ return Math.sqrt(Math.max(0, v * (to * to - fromExpo) + fromExpo));
432
+ };
433
+ const colorTypes = [hex, rgba, hsla];
434
+ const getColorType = (v) => colorTypes.find((type) => type.test(v));
435
+ function asRGBA(color) {
436
+ const type = getColorType(color);
437
+ invariant(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`);
438
+ let model = type.parse(color);
439
+ if (type === hsla) {
440
+ // TODO Remove this cast - needed since Framer Motion's stricter typing
441
+ model = hslaToRgba(model);
386
442
  }
387
- /**
388
- * Adds a function that will be notified when the `MotionValue` requests a render.
389
- *
390
- * @param subscriber - A function that's provided the latest value.
391
- * @returns A function that, when called, will cancel this subscription.
392
- *
393
- * @internal
394
- */
395
- onRenderRequest(subscription) {
396
- // Render immediately
397
- subscription(this.get());
398
- return this.renderSubscribers.add(subscription);
443
+ return model;
444
+ }
445
+ const mixColor = (from, to) => {
446
+ const fromRGBA = asRGBA(from);
447
+ const toRGBA = asRGBA(to);
448
+ const blended = { ...fromRGBA };
449
+ return (v) => {
450
+ blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
451
+ blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
452
+ blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
453
+ blended.alpha = mix(fromRGBA.alpha, toRGBA.alpha, v);
454
+ return rgba.transform(blended);
455
+ };
456
+ };
457
+
458
+ /**
459
+ * Pipe
460
+ * Compose other transformers to run linearily
461
+ * pipe(min(20), max(40))
462
+ * @param {...functions} transformers
463
+ * @return {function}
464
+ */
465
+ const combineFunctions = (a, b) => (v) => b(a(v));
466
+ const pipe = (...transformers) => transformers.reduce(combineFunctions);
467
+
468
+ const colorToken = "${c}";
469
+ const numberToken = "${n}";
470
+ function test(v) {
471
+ var _a, _b;
472
+ return (isNaN(v) &&
473
+ isString(v) &&
474
+ (((_a = v.match(floatRegex)) === null || _a === void 0 ? void 0 : _a.length) || 0) +
475
+ (((_b = v.match(colorRegex)) === null || _b === void 0 ? void 0 : _b.length) || 0) >
476
+ 0);
477
+ }
478
+ function analyseComplexValue(v) {
479
+ if (typeof v === "number")
480
+ v = `${v}`;
481
+ const values = [];
482
+ let numColors = 0;
483
+ let numNumbers = 0;
484
+ const colors = v.match(colorRegex);
485
+ if (colors) {
486
+ numColors = colors.length;
487
+ // Strip colors from input so they're not picked up by number regex.
488
+ // There's a better way to combine these regex searches, but its beyond my regex skills
489
+ v = v.replace(colorRegex, colorToken);
490
+ values.push(...colors.map(color.parse));
399
491
  }
400
- /**
401
- * Attaches a passive effect to the `MotionValue`.
402
- *
403
- * @internal
404
- */
405
- attach(passiveEffect) {
406
- this.passiveEffect = passiveEffect;
492
+ const numbers = v.match(floatRegex);
493
+ if (numbers) {
494
+ numNumbers = numbers.length;
495
+ v = v.replace(floatRegex, numberToken);
496
+ values.push(...numbers.map(number.parse));
407
497
  }
408
- /**
409
- * Sets the state of the `MotionValue`.
410
- *
411
- * @remarks
412
- *
413
- * ```jsx
414
- * const x = useMotionValue(0)
415
- * x.set(10)
416
- * ```
417
- *
418
- * @param latest - Latest value to set.
419
- * @param render - Whether to notify render subscribers. Defaults to `true`
420
- *
421
- * @public
422
- */
423
- set(v, render = true) {
424
- if (!render || !this.passiveEffect) {
425
- this.updateAndNotify(v, render);
426
- }
427
- else {
428
- this.passiveEffect(v, this.updateAndNotify);
498
+ return { values, numColors, numNumbers, tokenised: v };
499
+ }
500
+ function parse(v) {
501
+ return analyseComplexValue(v).values;
502
+ }
503
+ function createTransformer(source) {
504
+ const { values, numColors, tokenised } = analyseComplexValue(source);
505
+ const numValues = values.length;
506
+ return (v) => {
507
+ let output = tokenised;
508
+ for (let i = 0; i < numValues; i++) {
509
+ output = output.replace(i < numColors ? colorToken : numberToken, i < numColors
510
+ ? color.transform(v[i])
511
+ : sanitize(v[i]));
429
512
  }
513
+ return output;
514
+ };
515
+ }
516
+ const convertNumbersToZero = (v) => typeof v === "number" ? 0 : v;
517
+ function getAnimatableNone$1(v) {
518
+ const parsed = parse(v);
519
+ const transformer = createTransformer(v);
520
+ return transformer(parsed.map(convertNumbersToZero));
521
+ }
522
+ const complex = { test, parse, createTransformer, getAnimatableNone: getAnimatableNone$1 };
523
+
524
+ function getMixer(origin, target) {
525
+ if (typeof origin === "number") {
526
+ return (v) => mix(origin, target, v);
430
527
  }
431
- /**
432
- * Returns the latest state of `MotionValue`
433
- *
434
- * @returns - The latest state of `MotionValue`
435
- *
436
- * @public
437
- */
438
- get() {
439
- return this.current;
528
+ else if (color.test(origin)) {
529
+ return mixColor(origin, target);
440
530
  }
441
- /**
442
- * @public
443
- */
444
- getPrevious() {
445
- return this.prev;
531
+ else {
532
+ return mixComplex(origin, target);
446
533
  }
447
- /**
448
- * Returns the latest velocity of `MotionValue`
449
- *
450
- * @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
451
- *
452
- * @public
453
- */
454
- getVelocity() {
455
- // This could be isFloat(this.prev) && isFloat(this.current), but that would be wasteful
456
- return this.canTrackVelocity
457
- ? // These casts could be avoided if parseFloat would be typed better
458
- velocityPerSecond(parseFloat(this.current) -
459
- parseFloat(this.prev), this.timeDelta)
460
- : 0;
534
+ }
535
+ const mixArray = (from, to) => {
536
+ const output = [...from];
537
+ const numValues = output.length;
538
+ const blendValue = from.map((fromThis, i) => getMixer(fromThis, to[i]));
539
+ return (v) => {
540
+ for (let i = 0; i < numValues; i++) {
541
+ output[i] = blendValue[i](v);
542
+ }
543
+ return output;
544
+ };
545
+ };
546
+ const mixObject = (origin, target) => {
547
+ const output = { ...origin, ...target };
548
+ const blendValue = {};
549
+ for (const key in output) {
550
+ if (origin[key] !== undefined && target[key] !== undefined) {
551
+ blendValue[key] = getMixer(origin[key], target[key]);
552
+ }
461
553
  }
462
- /**
463
- * Registers a new animation to control this `MotionValue`. Only one
464
- * animation can drive a `MotionValue` at one time.
465
- *
466
- * ```jsx
467
- * value.start()
468
- * ```
469
- *
470
- * @param animation - A function that starts the provided animation
471
- *
472
- * @internal
473
- */
474
- start(animation) {
475
- this.stop();
476
- return new Promise((resolve) => {
477
- this.hasAnimated = true;
478
- this.stopAnimation = animation(resolve);
479
- }).then(() => this.clearAnimation());
554
+ return (v) => {
555
+ for (const key in blendValue) {
556
+ output[key] = blendValue[key](v);
557
+ }
558
+ return output;
559
+ };
560
+ };
561
+ const mixComplex = (origin, target) => {
562
+ const template = complex.createTransformer(target);
563
+ const originStats = analyseComplexValue(origin);
564
+ const targetStats = analyseComplexValue(target);
565
+ const canInterpolate = originStats.numColors === targetStats.numColors &&
566
+ originStats.numNumbers >= targetStats.numNumbers;
567
+ if (canInterpolate) {
568
+ return pipe(mixArray(originStats.values, targetStats.values), template);
480
569
  }
481
- /**
482
- * Stop the currently active animation.
483
- *
484
- * @public
485
- */
486
- stop() {
487
- if (this.stopAnimation)
488
- this.stopAnimation();
489
- this.clearAnimation();
570
+ else {
571
+ warning(true, `Complex values '${origin}' and '${target}' too different to mix. Ensure all colors are of the same type, and that each contains the same quantity of number and color values. Falling back to instant transition.`);
572
+ return (p) => `${p > 0 ? target : origin}`;
490
573
  }
491
- /**
492
- * Returns `true` if this value is currently animating.
493
- *
494
- * @public
495
- */
496
- isAnimating() {
497
- return !!this.stopAnimation;
574
+ };
575
+
576
+ /*
577
+ Progress within given range
578
+
579
+ Given a lower limit and an upper limit, we return the progress
580
+ (expressed as a number 0-1) represented by the given value, and
581
+ limit that progress to within 0-1.
582
+
583
+ @param [number]: Lower limit
584
+ @param [number]: Upper limit
585
+ @param [number]: Value to find progress within given range
586
+ @return [number]: Progress of value within range as expressed 0-1
587
+ */
588
+ const progress = (from, to, value) => {
589
+ const toFromDifference = to - from;
590
+ return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
591
+ };
592
+
593
+ const mixNumber = (from, to) => (p) => mix(from, to, p);
594
+ function detectMixerFactory(v) {
595
+ if (typeof v === "number") {
596
+ return mixNumber;
498
597
  }
499
- clearAnimation() {
500
- this.stopAnimation = null;
598
+ else if (typeof v === "string") {
599
+ if (color.test(v)) {
600
+ return mixColor;
601
+ }
602
+ else {
603
+ return mixComplex;
604
+ }
501
605
  }
502
- /**
503
- * Destroy and clean up subscribers to this `MotionValue`.
504
- *
505
- * The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
506
- * handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
507
- * created a `MotionValue` via the `motionValue` function.
508
- *
509
- * @public
510
- */
511
- destroy() {
512
- this.updateSubscribers.clear();
513
- this.renderSubscribers.clear();
514
- this.stop();
606
+ else if (Array.isArray(v)) {
607
+ return mixArray;
515
608
  }
609
+ else if (typeof v === "object") {
610
+ return mixObject;
611
+ }
612
+ return mixNumber;
516
613
  }
517
- function motionValue(init) {
518
- return new MotionValue(init);
614
+ function createMixers(output, ease, customMixer) {
615
+ const mixers = [];
616
+ const mixerFactory = customMixer || detectMixerFactory(output[0]);
617
+ const numMixers = output.length - 1;
618
+ for (let i = 0; i < numMixers; i++) {
619
+ let mixer = mixerFactory(output[i], output[i + 1]);
620
+ if (ease) {
621
+ const easingFunction = Array.isArray(ease) ? ease[i] : ease;
622
+ mixer = pipe(easingFunction, mixer);
623
+ }
624
+ mixers.push(mixer);
625
+ }
626
+ return mixers;
519
627
  }
520
-
521
- const isMotionValue = (value) => !!(value === null || value === void 0 ? void 0 : value.getVelocity);
522
-
523
628
  /**
524
- * Converts seconds to milliseconds
629
+ * Create a function that maps from a numerical input array to a generic output array.
525
630
  *
526
- * @param seconds - Time in seconds.
527
- * @return milliseconds - Converted time in milliseconds.
631
+ * Accepts:
632
+ * - Numbers
633
+ * - Colors (hex, hsl, hsla, rgb, rgba)
634
+ * - Complex (combinations of one or more numbers or strings)
635
+ *
636
+ * ```jsx
637
+ * const mixColor = interpolate([0, 1], ['#fff', '#000'])
638
+ *
639
+ * mixColor(0.5) // 'rgba(128, 128, 128, 1)'
640
+ * ```
641
+ *
642
+ * TODO Revist this approach once we've moved to data models for values,
643
+ * probably not needed to pregenerate mixer functions.
644
+ *
645
+ * @public
528
646
  */
529
- const secondsToMilliseconds = (seconds) => seconds * 1000;
530
-
531
- var warning = function () { };
532
- var invariant = function () { };
533
- {
534
- warning = function (check, message) {
535
- if (!check && typeof console !== 'undefined') {
536
- console.warn(message);
537
- }
538
- };
539
- invariant = function (check, message) {
540
- if (!check) {
541
- throw new Error(message);
542
- }
543
- };
647
+ function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
648
+ const inputLength = input.length;
649
+ invariant(inputLength === output.length, "Both input and output ranges must be the same length");
650
+ invariant(!ease || !Array.isArray(ease) || ease.length === inputLength - 1, "Array of easing functions must be of length `input.length - 1`, as it applies to the transitions **between** the defined values.");
651
+ // If input runs highest -> lowest, reverse both arrays
652
+ if (input[0] > input[inputLength - 1]) {
653
+ input = [...input].reverse();
654
+ output = [...output].reverse();
655
+ }
656
+ const mixers = createMixers(output, ease, mixer);
657
+ const numMixers = mixers.length;
658
+ const interpolator = (v) => {
659
+ let i = 0;
660
+ if (numMixers > 1) {
661
+ for (; i < input.length - 2; i++) {
662
+ if (v < input[i + 1])
663
+ break;
664
+ }
665
+ }
666
+ const progressInRange = progress(input[i], input[i + 1], v);
667
+ return mixers[i](progressInRange);
668
+ };
669
+ return isClamp
670
+ ? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
671
+ : interpolator;
544
672
  }
545
673
 
546
674
  const noop = (any) => any;
@@ -593,311 +721,678 @@
593
721
  return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
594
722
  }
595
723
 
596
- // Accepts an easing function and returns a new one that outputs mirrored values for
597
- // the second half of the animation. Turns easeIn into easeInOut.
598
- const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
599
-
600
- // Accepts an easing function and returns a new one that outputs reversed values.
601
- // Turns easeIn into easeOut.
602
- const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
603
-
604
- const easeIn = (p) => p * p;
605
- const easeOut = reverseEasing(easeIn);
606
- const easeInOut = mirrorEasing(easeIn);
607
-
608
724
  const circIn = (p) => 1 - Math.sin(Math.acos(p));
609
725
  const circOut = reverseEasing(circIn);
610
726
  const circInOut = mirrorEasing(circOut);
611
727
 
612
- const createBackIn = (power = 1.525) => (p) => p * p * ((power + 1) * p - power);
613
- const backIn = createBackIn();
614
- const backOut = reverseEasing(backIn);
615
- const backInOut = mirrorEasing(backIn);
728
+ const createBackIn = (power = 1.525) => (p) => p * p * ((power + 1) * p - power);
729
+ const backIn = createBackIn();
730
+ const backOut = reverseEasing(backIn);
731
+ const backInOut = mirrorEasing(backIn);
732
+
733
+ const createAnticipate = (power) => {
734
+ const backEasing = createBackIn(power);
735
+ return (p) => (p *= 2) < 1
736
+ ? 0.5 * backEasing(p)
737
+ : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
738
+ };
739
+ const anticipate = createAnticipate();
740
+
741
+ const easingLookup = {
742
+ linear: noop,
743
+ easeIn,
744
+ easeInOut,
745
+ easeOut,
746
+ circIn,
747
+ circInOut,
748
+ circOut,
749
+ backIn,
750
+ backInOut,
751
+ backOut,
752
+ anticipate,
753
+ };
754
+ const easingDefinitionToFunction = (definition) => {
755
+ if (Array.isArray(definition)) {
756
+ // If cubic bezier definition, create bezier curve
757
+ invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`);
758
+ const [x1, y1, x2, y2] = definition;
759
+ return cubicBezier(x1, y1, x2, y2);
760
+ }
761
+ else if (typeof definition === "string") {
762
+ // Else lookup from table
763
+ invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`);
764
+ return easingLookup[definition];
765
+ }
766
+ return definition;
767
+ };
768
+ const isEasingArray = (ease) => {
769
+ return Array.isArray(ease) && typeof ease[0] !== "number";
770
+ };
771
+
772
+ function defaultEasing(values, easing) {
773
+ return values.map(() => easing || easeInOut).splice(0, values.length - 1);
774
+ }
775
+ function defaultOffset(values) {
776
+ const numValues = values.length;
777
+ return values.map((_value, i) => i !== 0 ? i / (numValues - 1) : 0);
778
+ }
779
+ function convertOffsetToTimes(offset, duration) {
780
+ return offset.map((o) => o * duration);
781
+ }
782
+ function keyframes({ keyframes: keyframeValues, ease = easeInOut, times, duration = 300, }) {
783
+ keyframeValues = [...keyframeValues];
784
+ const origin = keyframes[0];
785
+ /**
786
+ * Easing functions can be externally defined as strings. Here we convert them
787
+ * into actual functions.
788
+ */
789
+ const easingFunctions = isEasingArray(ease)
790
+ ? ease.map(easingDefinitionToFunction)
791
+ : easingDefinitionToFunction(ease);
792
+ /**
793
+ * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
794
+ * to reduce GC during animation.
795
+ */
796
+ const state = { done: false, value: origin };
797
+ /**
798
+ * Create a times array based on the provided 0-1 offsets
799
+ */
800
+ const absoluteTimes = convertOffsetToTimes(
801
+ // Only use the provided offsets if they're the correct length
802
+ // TODO Maybe we should warn here if there's a length mismatch
803
+ times && times.length === keyframes.length
804
+ ? times
805
+ : defaultOffset(keyframeValues), duration);
806
+ function createInterpolator() {
807
+ return interpolate(absoluteTimes, keyframeValues, {
808
+ ease: Array.isArray(easingFunctions)
809
+ ? easingFunctions
810
+ : defaultEasing(keyframeValues, easingFunctions),
811
+ });
812
+ }
813
+ let interpolator = createInterpolator();
814
+ return {
815
+ next: (t) => {
816
+ state.value = interpolator(t);
817
+ state.done = t >= duration;
818
+ return state;
819
+ },
820
+ flipTarget: () => {
821
+ keyframeValues.reverse();
822
+ interpolator = createInterpolator();
823
+ },
824
+ };
825
+ }
826
+
827
+ const safeMin = 0.001;
828
+ const minDuration = 0.01;
829
+ const maxDuration = 10.0;
830
+ const minDamping = 0.05;
831
+ const maxDamping = 1;
832
+ function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) {
833
+ let envelope;
834
+ let derivative;
835
+ warning(duration <= maxDuration * 1000, "Spring duration must be 10 seconds or less");
836
+ let dampingRatio = 1 - bounce;
837
+ /**
838
+ * Restrict dampingRatio and duration to within acceptable ranges.
839
+ */
840
+ dampingRatio = clamp(minDamping, maxDamping, dampingRatio);
841
+ duration = clamp(minDuration, maxDuration, duration / 1000);
842
+ if (dampingRatio < 1) {
843
+ /**
844
+ * Underdamped spring
845
+ */
846
+ envelope = (undampedFreq) => {
847
+ const exponentialDecay = undampedFreq * dampingRatio;
848
+ const delta = exponentialDecay * duration;
849
+ const a = exponentialDecay - velocity;
850
+ const b = calcAngularFreq(undampedFreq, dampingRatio);
851
+ const c = Math.exp(-delta);
852
+ return safeMin - (a / b) * c;
853
+ };
854
+ derivative = (undampedFreq) => {
855
+ const exponentialDecay = undampedFreq * dampingRatio;
856
+ const delta = exponentialDecay * duration;
857
+ const d = delta * velocity + velocity;
858
+ const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
859
+ const f = Math.exp(-delta);
860
+ const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
861
+ const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
862
+ return (factor * ((d - e) * f)) / g;
863
+ };
864
+ }
865
+ else {
866
+ /**
867
+ * Critically-damped spring
868
+ */
869
+ envelope = (undampedFreq) => {
870
+ const a = Math.exp(-undampedFreq * duration);
871
+ const b = (undampedFreq - velocity) * duration + 1;
872
+ return -safeMin + a * b;
873
+ };
874
+ derivative = (undampedFreq) => {
875
+ const a = Math.exp(-undampedFreq * duration);
876
+ const b = (velocity - undampedFreq) * (duration * duration);
877
+ return a * b;
878
+ };
879
+ }
880
+ const initialGuess = 5 / duration;
881
+ const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
882
+ duration = duration * 1000;
883
+ if (isNaN(undampedFreq)) {
884
+ return {
885
+ stiffness: 100,
886
+ damping: 10,
887
+ duration,
888
+ };
889
+ }
890
+ else {
891
+ const stiffness = Math.pow(undampedFreq, 2) * mass;
892
+ return {
893
+ stiffness,
894
+ damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
895
+ duration,
896
+ };
897
+ }
898
+ }
899
+ const rootIterations = 12;
900
+ function approximateRoot(envelope, derivative, initialGuess) {
901
+ let result = initialGuess;
902
+ for (let i = 1; i < rootIterations; i++) {
903
+ result = result - envelope(result) / derivative(result);
904
+ }
905
+ return result;
906
+ }
907
+ function calcAngularFreq(undampedFreq, dampingRatio) {
908
+ return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
909
+ }
910
+
911
+ /*
912
+ Convert velocity into velocity per second
616
913
 
617
- const createAnticipate = (power) => {
618
- const backEasing = createBackIn(power);
619
- return (p) => (p *= 2) < 1
620
- ? 0.5 * backEasing(p)
621
- : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
622
- };
623
- const anticipate = createAnticipate();
914
+ @param [number]: Unit per frame
915
+ @param [number]: Frame duration in ms
916
+ */
917
+ function velocityPerSecond(velocity, frameDuration) {
918
+ return frameDuration ? velocity * (1000 / frameDuration) : 0;
919
+ }
624
920
 
625
- const easingLookup = {
626
- linear: noop,
627
- easeIn,
628
- easeInOut,
629
- easeOut,
630
- circIn,
631
- circInOut,
632
- circOut,
633
- backIn,
634
- backInOut,
635
- backOut,
636
- anticipate,
637
- };
638
- const easingDefinitionToFunction = (definition) => {
639
- if (Array.isArray(definition)) {
640
- // If cubic bezier definition, create bezier curve
641
- invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`);
642
- const [x1, y1, x2, y2] = definition;
643
- return cubicBezier(x1, y1, x2, y2);
644
- }
645
- else if (typeof definition === "string") {
646
- // Else lookup from table
647
- invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`);
648
- return easingLookup[definition];
921
+ const durationKeys = ["duration", "bounce"];
922
+ const physicsKeys = ["stiffness", "damping", "mass"];
923
+ function isSpringType(options, keys) {
924
+ return keys.some((key) => options[key] !== undefined);
925
+ }
926
+ function getSpringOptions(options) {
927
+ let springOptions = {
928
+ velocity: 0.0,
929
+ stiffness: 100,
930
+ damping: 10,
931
+ mass: 1.0,
932
+ isResolvedFromDuration: false,
933
+ ...options,
934
+ };
935
+ // stiffness/damping/mass overrides duration/bounce
936
+ if (!isSpringType(options, physicsKeys) &&
937
+ isSpringType(options, durationKeys)) {
938
+ const derived = findSpring(options);
939
+ springOptions = {
940
+ ...springOptions,
941
+ ...derived,
942
+ velocity: 0.0,
943
+ mass: 1.0,
944
+ };
945
+ springOptions.isResolvedFromDuration = true;
649
946
  }
650
- return definition;
651
- };
652
- const isEasingArray = (ease) => {
653
- return Array.isArray(ease) && typeof ease[0] !== "number";
654
- };
655
-
947
+ return springOptions;
948
+ }
949
+ const velocitySampleDuration = 5;
656
950
  /**
657
- * TODO: When we move from string as a source of truth to data models
658
- * everything in this folder should probably be referred to as models vs types
951
+ * This is based on the spring implementation of Wobble https://github.com/skevy/wobble
659
952
  */
660
- // If this number is a decimal, make it just five decimal places
661
- // to avoid exponents
662
- const sanitize = (v) => Math.round(v * 100000) / 100000;
663
- const floatRegex = /(-)?([\d]*\.?[\d])+/g;
664
- const colorRegex = /(#[0-9a-f]{6}|#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))/gi;
665
- const singleColorRegex = /^(#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))$/i;
666
- function isString(v) {
667
- return typeof v === "string";
953
+ function spring({ keyframes, restSpeed = 2, restDelta = 0.01, ...options }) {
954
+ let origin = keyframes[0];
955
+ let target = keyframes[keyframes.length - 1];
956
+ /**
957
+ * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
958
+ * to reduce GC during animation.
959
+ */
960
+ const state = { done: false, value: origin };
961
+ const { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
962
+ let resolveSpring = zero;
963
+ let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
964
+ const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
965
+ function createSpring() {
966
+ const initialDelta = target - origin;
967
+ const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
968
+ /**
969
+ * If we're working within what looks like a 0-1 range, change the default restDelta
970
+ * to 0.01
971
+ */
972
+ if (restDelta === undefined) {
973
+ restDelta = Math.min(Math.abs(target - origin) / 100, 0.4);
974
+ }
975
+ if (dampingRatio < 1) {
976
+ const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
977
+ // Underdamped spring
978
+ resolveSpring = (t) => {
979
+ const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
980
+ return (target -
981
+ envelope *
982
+ (((initialVelocity +
983
+ dampingRatio * undampedAngularFreq * initialDelta) /
984
+ angularFreq) *
985
+ Math.sin(angularFreq * t) +
986
+ initialDelta * Math.cos(angularFreq * t)));
987
+ };
988
+ }
989
+ else if (dampingRatio === 1) {
990
+ // Critically damped spring
991
+ resolveSpring = (t) => target -
992
+ Math.exp(-undampedAngularFreq * t) *
993
+ (initialDelta +
994
+ (initialVelocity + undampedAngularFreq * initialDelta) *
995
+ t);
996
+ }
997
+ else {
998
+ // Overdamped spring
999
+ const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
1000
+ resolveSpring = (t) => {
1001
+ const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1002
+ // When performing sinh or cosh values can hit Infinity so we cap them here
1003
+ const freqForT = Math.min(dampedAngularFreq * t, 300);
1004
+ return (target -
1005
+ (envelope *
1006
+ ((initialVelocity +
1007
+ dampingRatio * undampedAngularFreq * initialDelta) *
1008
+ Math.sinh(freqForT) +
1009
+ dampedAngularFreq *
1010
+ initialDelta *
1011
+ Math.cosh(freqForT))) /
1012
+ dampedAngularFreq);
1013
+ };
1014
+ }
1015
+ }
1016
+ createSpring();
1017
+ return {
1018
+ next: (t) => {
1019
+ const current = resolveSpring(t);
1020
+ if (!isResolvedFromDuration) {
1021
+ let currentVelocity = initialVelocity;
1022
+ if (t !== 0) {
1023
+ /**
1024
+ * We only need to calculate velocity for under-damped springs
1025
+ * as over- and critically-damped springs can't overshoot, so
1026
+ * checking only for displacement is enough.
1027
+ */
1028
+ if (dampingRatio < 1) {
1029
+ const prevT = Math.max(0, t - velocitySampleDuration);
1030
+ currentVelocity = velocityPerSecond(current - resolveSpring(prevT), t - prevT);
1031
+ }
1032
+ else {
1033
+ currentVelocity = 0;
1034
+ }
1035
+ }
1036
+ const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
1037
+ const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
1038
+ state.done =
1039
+ isBelowVelocityThreshold && isBelowDisplacementThreshold;
1040
+ }
1041
+ else {
1042
+ state.done = t >= duration;
1043
+ }
1044
+ state.value = state.done ? target : current;
1045
+ return state;
1046
+ },
1047
+ flipTarget: () => {
1048
+ initialVelocity = -initialVelocity;
1049
+ [origin, target] = [target, origin];
1050
+ createSpring();
1051
+ },
1052
+ };
668
1053
  }
1054
+ spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string";
1055
+ const zero = (_t) => 0;
669
1056
 
670
- const clamp = (min, max, v) => Math.min(Math.max(v, min), max);
671
-
672
- const number = {
673
- test: (v) => typeof v === "number",
674
- parse: parseFloat,
675
- transform: (v) => v,
676
- };
677
- const alpha = {
678
- ...number,
679
- transform: (v) => clamp(0, 1, v),
680
- };
681
- const scale = {
682
- ...number,
683
- default: 1,
684
- };
685
-
1057
+ function decay({
686
1058
  /**
687
- * Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000,
688
- * but false if a number or multiple colors
1059
+ * The decay animation dynamically calculates an end of the animation
1060
+ * based on the initial keyframe, so we only need to define a single keyframe
1061
+ * as default.
689
1062
  */
690
- const isColorString = (type, testProp) => (v) => {
691
- return Boolean((isString(v) && singleColorRegex.test(v) && v.startsWith(type)) ||
692
- (testProp && Object.prototype.hasOwnProperty.call(v, testProp)));
693
- };
694
- const splitColor = (aName, bName, cName) => (v) => {
695
- if (!isString(v))
696
- return v;
697
- const [a, b, c, alpha] = v.match(floatRegex);
1063
+ keyframes = [0], velocity = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) {
1064
+ const origin = keyframes[0];
1065
+ /**
1066
+ * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
1067
+ * to reduce GC during animation.
1068
+ */
1069
+ const state = { done: false, value: origin };
1070
+ let amplitude = power * velocity;
1071
+ const ideal = origin + amplitude;
1072
+ const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
1073
+ /**
1074
+ * If the target has changed we need to re-calculate the amplitude, otherwise
1075
+ * the animation will start from the wrong position.
1076
+ */
1077
+ if (target !== ideal)
1078
+ amplitude = target - origin;
698
1079
  return {
699
- [aName]: parseFloat(a),
700
- [bName]: parseFloat(b),
701
- [cName]: parseFloat(c),
702
- alpha: alpha !== undefined ? parseFloat(alpha) : 1,
1080
+ next: (t) => {
1081
+ const delta = -amplitude * Math.exp(-t / timeConstant);
1082
+ state.done = !(delta > restDelta || delta < -restDelta);
1083
+ state.value = state.done ? target : target + delta;
1084
+ return state;
1085
+ },
1086
+ flipTarget: () => { },
703
1087
  };
704
- };
1088
+ }
705
1089
 
706
- const clampRgbUnit = (v) => clamp(0, 255, v);
707
- const rgbUnit = {
708
- ...number,
709
- transform: (v) => Math.round(clampRgbUnit(v)),
1090
+ const types = {
1091
+ decay,
1092
+ keyframes: keyframes,
1093
+ tween: keyframes,
1094
+ spring,
710
1095
  };
711
- const rgba = {
712
- test: isColorString("rgb", "red"),
713
- parse: splitColor("red", "green", "blue"),
714
- transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => "rgba(" +
715
- rgbUnit.transform(red) +
716
- ", " +
717
- rgbUnit.transform(green) +
718
- ", " +
719
- rgbUnit.transform(blue) +
720
- ", " +
721
- sanitize(alpha.transform(alpha$1)) +
722
- ")",
1096
+ function loopElapsed(elapsed, duration, delay = 0) {
1097
+ return elapsed - duration - delay;
1098
+ }
1099
+ function reverseElapsed(elapsed, duration = 0, delay = 0, isForwardPlayback = true) {
1100
+ return isForwardPlayback
1101
+ ? loopElapsed(duration + -elapsed, duration, delay)
1102
+ : duration - (elapsed - duration) + delay;
1103
+ }
1104
+ function hasRepeatDelayElapsed(elapsed, duration, delay, isForwardPlayback) {
1105
+ return isForwardPlayback ? elapsed >= duration + delay : elapsed <= -delay;
1106
+ }
1107
+ const framesync = (update) => {
1108
+ const passTimestamp = ({ delta }) => update(delta);
1109
+ return {
1110
+ start: () => sync.update(passTimestamp, true),
1111
+ stop: () => cancelSync.update(passTimestamp),
1112
+ };
723
1113
  };
724
-
725
- function parseHex(v) {
726
- let r = "";
727
- let g = "";
728
- let b = "";
729
- let a = "";
730
- // If we have 6 characters, ie #FF0000
731
- if (v.length > 5) {
732
- r = v.substring(1, 3);
733
- g = v.substring(3, 5);
734
- b = v.substring(5, 7);
735
- a = v.substring(7, 9);
736
- // Or we have 3 characters, ie #F00
1114
+ function animate$1({ duration, driver = framesync, elapsed = 0, repeat: repeatMax = 0, repeatType = "loop", repeatDelay = 0, keyframes, autoplay = true, onPlay, onStop, onComplete, onRepeat, onUpdate, type = "keyframes", ...options }) {
1115
+ var _a, _b;
1116
+ let driverControls;
1117
+ let repeatCount = 0;
1118
+ let computedDuration = duration;
1119
+ let latest;
1120
+ let isComplete = false;
1121
+ let isForwardPlayback = true;
1122
+ let interpolateFromNumber;
1123
+ const animator = types[keyframes.length > 2 ? "keyframes" : type];
1124
+ const origin = keyframes[0];
1125
+ const target = keyframes[keyframes.length - 1];
1126
+ if ((_b = (_a = animator).needsInterpolation) === null || _b === void 0 ? void 0 : _b.call(_a, origin, target)) {
1127
+ interpolateFromNumber = interpolate([0, 100], [origin, target], {
1128
+ clamp: false,
1129
+ });
1130
+ keyframes = [0, 100];
737
1131
  }
738
- else {
739
- r = v.substring(1, 2);
740
- g = v.substring(2, 3);
741
- b = v.substring(3, 4);
742
- a = v.substring(4, 5);
743
- r += r;
744
- g += g;
745
- b += b;
746
- a += a;
1132
+ const animation = animator({
1133
+ ...options,
1134
+ duration,
1135
+ keyframes,
1136
+ });
1137
+ function repeat() {
1138
+ repeatCount++;
1139
+ if (repeatType === "reverse") {
1140
+ isForwardPlayback = repeatCount % 2 === 0;
1141
+ elapsed = reverseElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback);
1142
+ }
1143
+ else {
1144
+ elapsed = loopElapsed(elapsed, computedDuration, repeatDelay);
1145
+ if (repeatType === "mirror")
1146
+ animation.flipTarget();
1147
+ }
1148
+ isComplete = false;
1149
+ onRepeat && onRepeat();
1150
+ }
1151
+ function complete() {
1152
+ driverControls.stop();
1153
+ onComplete && onComplete();
1154
+ }
1155
+ function update(delta) {
1156
+ if (!isForwardPlayback)
1157
+ delta = -delta;
1158
+ elapsed += delta;
1159
+ if (!isComplete) {
1160
+ const state = animation.next(Math.max(0, elapsed));
1161
+ latest = state.value;
1162
+ if (interpolateFromNumber)
1163
+ latest = interpolateFromNumber(latest);
1164
+ isComplete = isForwardPlayback ? state.done : elapsed <= 0;
1165
+ }
1166
+ onUpdate && onUpdate(latest);
1167
+ if (isComplete) {
1168
+ if (repeatCount === 0) {
1169
+ computedDuration =
1170
+ computedDuration !== undefined ? computedDuration : elapsed;
1171
+ }
1172
+ if (repeatCount < repeatMax) {
1173
+ hasRepeatDelayElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback) && repeat();
1174
+ }
1175
+ else {
1176
+ complete();
1177
+ }
1178
+ }
1179
+ }
1180
+ function play() {
1181
+ onPlay && onPlay();
1182
+ driverControls = driver(update);
1183
+ driverControls.start();
747
1184
  }
1185
+ autoplay && play();
748
1186
  return {
749
- red: parseInt(r, 16),
750
- green: parseInt(g, 16),
751
- blue: parseInt(b, 16),
752
- alpha: a ? parseInt(a, 16) / 255 : 1,
1187
+ stop: () => {
1188
+ onStop && onStop();
1189
+ driverControls.stop();
1190
+ },
1191
+ sample: (t) => {
1192
+ return animation.next(Math.max(0, t)).value;
1193
+ },
753
1194
  };
754
1195
  }
755
- const hex = {
756
- test: isColorString("#"),
757
- parse: parseHex,
758
- transform: rgba.transform,
759
- };
760
1196
 
761
- const createUnitType = (unit) => ({
762
- test: (v) => isString(v) && v.endsWith(unit) && v.split(" ").length === 1,
763
- parse: parseFloat,
764
- transform: (v) => `${v}${unit}`,
765
- });
766
- const degrees = createUnitType("deg");
767
- const percent = createUnitType("%");
768
- const px = createUnitType("px");
769
- const vh = createUnitType("vh");
770
- const vw = createUnitType("vw");
771
- const progressPercentage = {
772
- ...percent,
773
- parse: (v) => percent.parse(v) / 100,
774
- transform: (v) => percent.transform(v * 100),
775
- };
1197
+ const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
776
1198
 
777
- const hsla = {
778
- test: isColorString("hsl", "hue"),
779
- parse: splitColor("hue", "saturation", "lightness"),
780
- transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => {
781
- return ("hsla(" +
782
- Math.round(hue) +
783
- ", " +
784
- percent.transform(sanitize(saturation)) +
785
- ", " +
786
- percent.transform(sanitize(lightness)) +
787
- ", " +
788
- sanitize(alpha.transform(alpha$1)) +
789
- ")");
790
- },
791
- };
1199
+ function animateStyle(element, valueName, keyframes, { delay = 0, duration, repeat = 0, repeatType = "loop", ease, times, } = {}) {
1200
+ return element.animate({ [valueName]: keyframes, offset: times }, {
1201
+ delay,
1202
+ duration,
1203
+ easing: Array.isArray(ease) ? cubicBezierAsString(ease) : ease,
1204
+ fill: "both",
1205
+ iterations: repeat + 1,
1206
+ direction: repeatType === "reverse" ? "alternate" : "normal",
1207
+ });
1208
+ }
792
1209
 
793
- const color = {
794
- test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v),
795
- parse: (v) => {
796
- if (rgba.test(v)) {
797
- return rgba.parse(v);
798
- }
799
- else if (hsla.test(v)) {
800
- return hsla.parse(v);
1210
+ /**
1211
+ * 10ms is chosen here as it strikes a balance between smooth
1212
+ * results (more than one keyframe per frame at 60fps) and
1213
+ * keyframe quantity.
1214
+ */
1215
+ const sampleDelta = 10; //ms
1216
+ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ...options }) {
1217
+ let { keyframes, duration = 0.3, elapsed = 0, ease } = options;
1218
+ /**
1219
+ * If this is a spring animation, pre-generate keyframes and
1220
+ * record duration.
1221
+ *
1222
+ * TODO: When introducing support for values beyond opacity it
1223
+ * might be better to use `animate.sample()`
1224
+ */
1225
+ if (options.type === "spring") {
1226
+ const springAnimation = spring(options);
1227
+ let state = { done: false, value: keyframes[0] };
1228
+ const springKeyframes = [];
1229
+ let t = 0;
1230
+ while (!state.done) {
1231
+ state = springAnimation.next(t);
1232
+ springKeyframes.push(state.value);
1233
+ t += sampleDelta;
1234
+ }
1235
+ keyframes = springKeyframes;
1236
+ duration = t - sampleDelta;
1237
+ ease = "linear";
1238
+ }
1239
+ const animation = animateStyle(value.owner.current, valueName, keyframes, {
1240
+ ...options,
1241
+ delay: -elapsed,
1242
+ duration,
1243
+ /**
1244
+ * This function is currently not called if ease is provided
1245
+ * as a function so the cast is safe.
1246
+ *
1247
+ * However it would be possible for a future refinement to port
1248
+ * in easing pregeneration from Motion One for browsers that
1249
+ * support the upcoming `linear()` easing function.
1250
+ */
1251
+ ease: ease,
1252
+ });
1253
+ /**
1254
+ * Prefer the `onfinish` prop as it's more widely supported than
1255
+ * the `finished` promise.
1256
+ *
1257
+ * Here, we synchronously set the provided MotionValue to the end
1258
+ * keyframe. If we didn't, when the WAAPI animation is finished it would
1259
+ * be removed from the element which would then revert to its old styles.
1260
+ */
1261
+ animation.onfinish = () => {
1262
+ value.set(keyframes[keyframes.length - 1]);
1263
+ onComplete && onComplete();
1264
+ };
1265
+ /**
1266
+ * Animation interrupt callback.
1267
+ */
1268
+ return () => {
1269
+ /**
1270
+ * WAAPI doesn't natively have any interruption capabilities.
1271
+ *
1272
+ * Rather than read commited styles back out of the DOM, we can
1273
+ * create a renderless JS animation and sample it twice to calculate
1274
+ * its current value, "previous" value, and therefore allow
1275
+ * Motion to calculate velocity for any subsequent animation.
1276
+ */
1277
+ const { currentTime } = animation;
1278
+ if (currentTime) {
1279
+ const sampleAnimation = animate$1(options);
1280
+ value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta), sampleAnimation.sample(currentTime), sampleDelta);
801
1281
  }
802
- else {
803
- return hex.parse(v);
1282
+ sync.update(() => animation.cancel());
1283
+ };
1284
+ }
1285
+
1286
+ /**
1287
+ * Timeout defined in ms
1288
+ */
1289
+ function delay(callback, timeout) {
1290
+ const start = performance.now();
1291
+ const checkElapsed = ({ timestamp }) => {
1292
+ const elapsed = timestamp - start;
1293
+ if (elapsed >= timeout) {
1294
+ cancelSync.read(checkElapsed);
1295
+ callback(elapsed - timeout);
804
1296
  }
805
- },
806
- transform: (v) => {
807
- return isString(v)
808
- ? v
809
- : v.hasOwnProperty("red")
810
- ? rgba.transform(v)
811
- : hsla.transform(v);
812
- },
813
- };
1297
+ };
1298
+ sync.read(checkElapsed, true);
1299
+ return () => cancelSync.read(checkElapsed);
1300
+ }
814
1301
 
815
- const colorToken = "${c}";
816
- const numberToken = "${n}";
817
- function test(v) {
818
- var _a, _b;
819
- return (isNaN(v) &&
820
- isString(v) &&
821
- (((_a = v.match(floatRegex)) === null || _a === void 0 ? void 0 : _a.length) || 0) +
822
- (((_b = v.match(colorRegex)) === null || _b === void 0 ? void 0 : _b.length) || 0) >
823
- 0);
1302
+ function createInstantAnimation({ keyframes, elapsed, onUpdate, onComplete, }) {
1303
+ const setValue = () => {
1304
+ onUpdate && onUpdate(keyframes[keyframes.length - 1]);
1305
+ onComplete && onComplete();
1306
+ return () => { };
1307
+ };
1308
+ return elapsed ? delay(setValue, -elapsed) : setValue();
824
1309
  }
825
- function analyseComplexValue(v) {
826
- if (typeof v === "number")
827
- v = `${v}`;
828
- const values = [];
829
- let numColors = 0;
830
- let numNumbers = 0;
831
- const colors = v.match(colorRegex);
832
- if (colors) {
833
- numColors = colors.length;
834
- // Strip colors from input so they're not picked up by number regex.
835
- // There's a better way to combine these regex searches, but its beyond my regex skills
836
- v = v.replace(colorRegex, colorToken);
837
- values.push(...colors.map(color.parse));
1310
+
1311
+ function inertia({ keyframes, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
1312
+ const origin = keyframes[0];
1313
+ let currentAnimation;
1314
+ function isOutOfBounds(v) {
1315
+ return (min !== undefined && v < min) || (max !== undefined && v > max);
838
1316
  }
839
- const numbers = v.match(floatRegex);
840
- if (numbers) {
841
- numNumbers = numbers.length;
842
- v = v.replace(floatRegex, numberToken);
843
- values.push(...numbers.map(number.parse));
1317
+ function findNearestBoundary(v) {
1318
+ if (min === undefined)
1319
+ return max;
1320
+ if (max === undefined)
1321
+ return min;
1322
+ return Math.abs(min - v) < Math.abs(max - v) ? min : max;
1323
+ }
1324
+ function startAnimation(options) {
1325
+ currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop();
1326
+ currentAnimation = animate$1({
1327
+ keyframes: [0, 1],
1328
+ velocity: 0,
1329
+ ...options,
1330
+ driver,
1331
+ onUpdate: (v) => {
1332
+ var _a;
1333
+ onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(v);
1334
+ (_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, v);
1335
+ },
1336
+ onComplete,
1337
+ onStop,
1338
+ });
1339
+ }
1340
+ function startSpring(options) {
1341
+ startAnimation({
1342
+ type: "spring",
1343
+ stiffness: bounceStiffness,
1344
+ damping: bounceDamping,
1345
+ restDelta,
1346
+ ...options,
1347
+ });
1348
+ }
1349
+ if (isOutOfBounds(origin)) {
1350
+ // Start the animation with spring if outside the defined boundaries
1351
+ startSpring({
1352
+ velocity,
1353
+ keyframes: [origin, findNearestBoundary(origin)],
1354
+ });
1355
+ }
1356
+ else {
1357
+ /**
1358
+ * Or if the value is out of bounds, simulate the inertia movement
1359
+ * with the decay animation.
1360
+ *
1361
+ * Pre-calculate the target so we can detect if it's out-of-bounds.
1362
+ * If it is, we want to check per frame when to switch to a spring
1363
+ * animation
1364
+ */
1365
+ let target = power * velocity + origin;
1366
+ if (typeof modifyTarget !== "undefined")
1367
+ target = modifyTarget(target);
1368
+ const boundary = findNearestBoundary(target);
1369
+ const heading = boundary === min ? -1 : 1;
1370
+ let prev;
1371
+ let current;
1372
+ const checkBoundary = (v) => {
1373
+ prev = current;
1374
+ current = v;
1375
+ velocity = velocityPerSecond(v - prev, frameData.delta);
1376
+ if ((heading === 1 && v > boundary) ||
1377
+ (heading === -1 && v < boundary)) {
1378
+ startSpring({ keyframes: [v, boundary], velocity });
1379
+ }
1380
+ };
1381
+ startAnimation({
1382
+ type: "decay",
1383
+ keyframes: [origin, 0],
1384
+ velocity,
1385
+ timeConstant,
1386
+ power,
1387
+ restDelta,
1388
+ modifyTarget,
1389
+ onUpdate: isOutOfBounds(target) ? checkBoundary : undefined,
1390
+ });
844
1391
  }
845
- return { values, numColors, numNumbers, tokenised: v };
846
- }
847
- function parse(v) {
848
- return analyseComplexValue(v).values;
849
- }
850
- function createTransformer(source) {
851
- const { values, numColors, tokenised } = analyseComplexValue(source);
852
- const numValues = values.length;
853
- return (v) => {
854
- let output = tokenised;
855
- for (let i = 0; i < numValues; i++) {
856
- output = output.replace(i < numColors ? colorToken : numberToken, i < numColors
857
- ? color.transform(v[i])
858
- : sanitize(v[i]));
859
- }
860
- return output;
1392
+ return {
1393
+ stop: () => currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop(),
861
1394
  };
862
1395
  }
863
- const convertNumbersToZero = (v) => typeof v === "number" ? 0 : v;
864
- function getAnimatableNone$1(v) {
865
- const parsed = parse(v);
866
- const transformer = createTransformer(v);
867
- return transformer(parsed.map(convertNumbersToZero));
868
- }
869
- const complex = { test, parse, createTransformer, getAnimatableNone: getAnimatableNone$1 };
870
-
871
- /**
872
- * Check if a value is animatable. Examples:
873
- *
874
- * ✅: 100, "100px", "#fff"
875
- * ❌: "block", "url(2.jpg)"
876
- * @param value
877
- *
878
- * @internal
879
- */
880
- const isAnimatable = (key, value) => {
881
- // If the list of keys tat might be non-animatable grows, replace with Set
882
- if (key === "zIndex")
883
- return false;
884
- // If it's a number or a keyframes array, we can animate it. We might at some point
885
- // need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
886
- // but for now lets leave it like this for performance reasons
887
- if (typeof value === "number" || Array.isArray(value))
888
- return true;
889
- if (typeof value === "string" && // It's animatable if we have a string
890
- complex.test(value) && // And it contains numbers and/or colors
891
- !value.startsWith("url(") // Unless it starts with "url("
892
- ) {
893
- return true;
894
- }
895
- return false;
896
- };
897
-
898
- const isKeyframesTarget = (v) => {
899
- return Array.isArray(v);
900
- };
901
1396
 
902
1397
  const underDampedSpring = () => ({
903
1398
  type: "spring",
@@ -905,10 +1400,10 @@
905
1400
  damping: 25,
906
1401
  restSpeed: 10,
907
1402
  });
908
- const criticallyDampedSpring = (to) => ({
1403
+ const criticallyDampedSpring = (target) => ({
909
1404
  type: "spring",
910
1405
  stiffness: 550,
911
- damping: to === 0 ? 2 * Math.sqrt(550) : 30,
1406
+ damping: target === 0 ? 2 * Math.sqrt(550) : 30,
912
1407
  restSpeed: 10,
913
1408
  });
914
1409
  const linearTween = () => ({
@@ -916,11 +1411,10 @@
916
1411
  ease: "linear",
917
1412
  duration: 0.3,
918
1413
  });
919
- const keyframes$1 = (values) => ({
1414
+ const keyframesTransition = {
920
1415
  type: "keyframes",
921
1416
  duration: 0.8,
922
- values,
923
- });
1417
+ };
924
1418
  const defaultTransitions = {
925
1419
  x: underDampedSpring,
926
1420
  y: underDampedSpring,
@@ -937,16 +1431,41 @@
937
1431
  color: linearTween,
938
1432
  default: criticallyDampedSpring,
939
1433
  };
940
- const getDefaultTransition = (valueKey, to) => {
941
- let transitionFactory;
942
- if (isKeyframesTarget(to)) {
943
- transitionFactory = keyframes$1;
1434
+ const getDefaultTransition = (valueKey, { keyframes }) => {
1435
+ if (keyframes.length > 2) {
1436
+ return keyframesTransition;
944
1437
  }
945
1438
  else {
946
- transitionFactory =
947
- defaultTransitions[valueKey] || defaultTransitions.default;
1439
+ const factory = defaultTransitions[valueKey] || defaultTransitions.default;
1440
+ return factory(keyframes[1]);
1441
+ }
1442
+ };
1443
+
1444
+ /**
1445
+ * Check if a value is animatable. Examples:
1446
+ *
1447
+ * ✅: 100, "100px", "#fff"
1448
+ * ❌: "block", "url(2.jpg)"
1449
+ * @param value
1450
+ *
1451
+ * @internal
1452
+ */
1453
+ const isAnimatable = (key, value) => {
1454
+ // If the list of keys tat might be non-animatable grows, replace with Set
1455
+ if (key === "zIndex")
1456
+ return false;
1457
+ // If it's a number or a keyframes array, we can animate it. We might at some point
1458
+ // need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
1459
+ // but for now lets leave it like this for performance reasons
1460
+ if (typeof value === "number" || Array.isArray(value))
1461
+ return true;
1462
+ if (typeof value === "string" && // It's animatable if we have a string
1463
+ complex.test(value) && // And it contains numbers and/or colors
1464
+ !value.startsWith("url(") // Unless it starts with "url("
1465
+ ) {
1466
+ return true;
948
1467
  }
949
- return { to, ...transitionFactory(to) };
1468
+ return false;
950
1469
  };
951
1470
 
952
1471
  /**
@@ -1042,975 +1561,585 @@
1042
1561
  // Misc
1043
1562
  zIndex: int,
1044
1563
  // SVG
1045
- fillOpacity: alpha,
1046
- strokeOpacity: alpha,
1047
- numOctaves: int,
1048
- };
1049
-
1050
- /**
1051
- * A map of default value types for common values
1052
- */
1053
- const defaultValueTypes = {
1054
- ...numberValueTypes,
1055
- // Color props
1056
- color,
1057
- backgroundColor: color,
1058
- outlineColor: color,
1059
- fill: color,
1060
- stroke: color,
1061
- // Border props
1062
- borderColor: color,
1063
- borderTopColor: color,
1064
- borderRightColor: color,
1065
- borderBottomColor: color,
1066
- borderLeftColor: color,
1067
- filter,
1068
- WebkitFilter: filter,
1069
- };
1070
- /**
1071
- * Gets the default ValueType for the provided value key
1072
- */
1073
- const getDefaultValueType = (key) => defaultValueTypes[key];
1074
-
1075
- function getAnimatableNone(key, value) {
1076
- var _a;
1077
- let defaultValueType = getDefaultValueType(key);
1078
- if (defaultValueType !== filter)
1079
- defaultValueType = complex;
1080
- // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
1081
- return (_a = defaultValueType.getAnimatableNone) === null || _a === void 0 ? void 0 : _a.call(defaultValueType, value);
1082
- }
1083
-
1084
- const instantAnimationState = {
1085
- current: false,
1086
- };
1087
-
1088
- const isCustomValue = (v) => {
1089
- return Boolean(v && typeof v === "object" && v.mix && v.toValue);
1090
- };
1091
- const resolveFinalValueInKeyframes = (v) => {
1092
- // TODO maybe throw if v.length - 1 is placeholder token?
1093
- return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
1094
- };
1095
-
1096
- /*
1097
- Value in range from progress
1098
-
1099
- Given a lower limit and an upper limit, we return the value within
1100
- that range as expressed by progress (usually a number from 0 to 1)
1101
-
1102
- So progress = 0.5 would change
1103
-
1104
- from -------- to
1105
-
1106
- to
1107
-
1108
- from ---- to
1109
-
1110
- E.g. from = 10, to = 20, progress = 0.5 => 15
1111
-
1112
- @param [number]: Lower limit of range
1113
- @param [number]: Upper limit of range
1114
- @param [number]: The progress between lower and upper limits expressed 0-1
1115
- @return [number]: Value as calculated from progress within range (not limited within range)
1116
- */
1117
- const mix = (from, to, progress) => -progress * from + progress * to + from;
1118
-
1119
- // Adapted from https://gist.github.com/mjackson/5311256
1120
- function hueToRgb(p, q, t) {
1121
- if (t < 0)
1122
- t += 1;
1123
- if (t > 1)
1124
- t -= 1;
1125
- if (t < 1 / 6)
1126
- return p + (q - p) * 6 * t;
1127
- if (t < 1 / 2)
1128
- return q;
1129
- if (t < 2 / 3)
1130
- return p + (q - p) * (2 / 3 - t) * 6;
1131
- return p;
1132
- }
1133
- function hslaToRgba({ hue, saturation, lightness, alpha }) {
1134
- hue /= 360;
1135
- saturation /= 100;
1136
- lightness /= 100;
1137
- let red = 0;
1138
- let green = 0;
1139
- let blue = 0;
1140
- if (!saturation) {
1141
- red = green = blue = lightness;
1142
- }
1143
- else {
1144
- const q = lightness < 0.5
1145
- ? lightness * (1 + saturation)
1146
- : lightness + saturation - lightness * saturation;
1147
- const p = 2 * lightness - q;
1148
- red = hueToRgb(p, q, hue + 1 / 3);
1149
- green = hueToRgb(p, q, hue);
1150
- blue = hueToRgb(p, q, hue - 1 / 3);
1151
- }
1152
- return {
1153
- red: Math.round(red * 255),
1154
- green: Math.round(green * 255),
1155
- blue: Math.round(blue * 255),
1156
- alpha,
1157
- };
1158
- }
1159
-
1160
- // Linear color space blending
1161
- // Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
1162
- // Demonstrated http://codepen.io/osublake/pen/xGVVaN
1163
- const mixLinearColor = (from, to, v) => {
1164
- const fromExpo = from * from;
1165
- return Math.sqrt(Math.max(0, v * (to * to - fromExpo) + fromExpo));
1166
- };
1167
- const colorTypes = [hex, rgba, hsla];
1168
- const getColorType = (v) => colorTypes.find((type) => type.test(v));
1169
- function asRGBA(color) {
1170
- const type = getColorType(color);
1171
- invariant(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`);
1172
- let model = type.parse(color);
1173
- if (type === hsla) {
1174
- // TODO Remove this cast - needed since Framer Motion's stricter typing
1175
- model = hslaToRgba(model);
1176
- }
1177
- return model;
1178
- }
1179
- const mixColor = (from, to) => {
1180
- const fromRGBA = asRGBA(from);
1181
- const toRGBA = asRGBA(to);
1182
- const blended = { ...fromRGBA };
1183
- return (v) => {
1184
- blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
1185
- blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
1186
- blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
1187
- blended.alpha = mix(fromRGBA.alpha, toRGBA.alpha, v);
1188
- return rgba.transform(blended);
1189
- };
1564
+ fillOpacity: alpha,
1565
+ strokeOpacity: alpha,
1566
+ numOctaves: int,
1190
1567
  };
1191
1568
 
1192
1569
  /**
1193
- * Pipe
1194
- * Compose other transformers to run linearily
1195
- * pipe(min(20), max(40))
1196
- * @param {...functions} transformers
1197
- * @return {function}
1570
+ * A map of default value types for common values
1198
1571
  */
1199
- const combineFunctions = (a, b) => (v) => b(a(v));
1200
- const pipe = (...transformers) => transformers.reduce(combineFunctions);
1201
-
1202
- function getMixer(origin, target) {
1203
- if (typeof origin === "number") {
1204
- return (v) => mix(origin, target, v);
1205
- }
1206
- else if (color.test(origin)) {
1207
- return mixColor(origin, target);
1208
- }
1209
- else {
1210
- return mixComplex(origin, target);
1211
- }
1212
- }
1213
- const mixArray = (from, to) => {
1214
- const output = [...from];
1215
- const numValues = output.length;
1216
- const blendValue = from.map((fromThis, i) => getMixer(fromThis, to[i]));
1217
- return (v) => {
1218
- for (let i = 0; i < numValues; i++) {
1219
- output[i] = blendValue[i](v);
1220
- }
1221
- return output;
1222
- };
1223
- };
1224
- const mixObject = (origin, target) => {
1225
- const output = { ...origin, ...target };
1226
- const blendValue = {};
1227
- for (const key in output) {
1228
- if (origin[key] !== undefined && target[key] !== undefined) {
1229
- blendValue[key] = getMixer(origin[key], target[key]);
1230
- }
1231
- }
1232
- return (v) => {
1233
- for (const key in blendValue) {
1234
- output[key] = blendValue[key](v);
1235
- }
1236
- return output;
1237
- };
1238
- };
1239
- const mixComplex = (origin, target) => {
1240
- const template = complex.createTransformer(target);
1241
- const originStats = analyseComplexValue(origin);
1242
- const targetStats = analyseComplexValue(target);
1243
- const canInterpolate = originStats.numColors === targetStats.numColors &&
1244
- originStats.numNumbers >= targetStats.numNumbers;
1245
- if (canInterpolate) {
1246
- return pipe(mixArray(originStats.values, targetStats.values), template);
1247
- }
1248
- else {
1249
- warning(true, `Complex values '${origin}' and '${target}' too different to mix. Ensure all colors are of the same type, and that each contains the same quantity of number and color values. Falling back to instant transition.`);
1250
- return (p) => `${p > 0 ? target : origin}`;
1251
- }
1252
- };
1253
-
1254
- /*
1255
- Progress within given range
1256
-
1257
- Given a lower limit and an upper limit, we return the progress
1258
- (expressed as a number 0-1) represented by the given value, and
1259
- limit that progress to within 0-1.
1260
-
1261
- @param [number]: Lower limit
1262
- @param [number]: Upper limit
1263
- @param [number]: Value to find progress within given range
1264
- @return [number]: Progress of value within range as expressed 0-1
1265
- */
1266
- const progress = (from, to, value) => {
1267
- const toFromDifference = to - from;
1268
- return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
1572
+ const defaultValueTypes = {
1573
+ ...numberValueTypes,
1574
+ // Color props
1575
+ color,
1576
+ backgroundColor: color,
1577
+ outlineColor: color,
1578
+ fill: color,
1579
+ stroke: color,
1580
+ // Border props
1581
+ borderColor: color,
1582
+ borderTopColor: color,
1583
+ borderRightColor: color,
1584
+ borderBottomColor: color,
1585
+ borderLeftColor: color,
1586
+ filter,
1587
+ WebkitFilter: filter,
1269
1588
  };
1270
-
1271
- const mixNumber = (from, to) => (p) => mix(from, to, p);
1272
- function detectMixerFactory(v) {
1273
- if (typeof v === "number") {
1274
- return mixNumber;
1275
- }
1276
- else if (typeof v === "string") {
1277
- if (color.test(v)) {
1278
- return mixColor;
1279
- }
1280
- else {
1281
- return mixComplex;
1282
- }
1283
- }
1284
- else if (Array.isArray(v)) {
1285
- return mixArray;
1286
- }
1287
- else if (typeof v === "object") {
1288
- return mixObject;
1289
- }
1290
- return mixNumber;
1291
- }
1292
- function createMixers(output, ease, customMixer) {
1293
- const mixers = [];
1294
- const mixerFactory = customMixer || detectMixerFactory(output[0]);
1295
- const numMixers = output.length - 1;
1296
- for (let i = 0; i < numMixers; i++) {
1297
- let mixer = mixerFactory(output[i], output[i + 1]);
1298
- if (ease) {
1299
- const easingFunction = Array.isArray(ease) ? ease[i] : ease;
1300
- mixer = pipe(easingFunction, mixer);
1301
- }
1302
- mixers.push(mixer);
1303
- }
1304
- return mixers;
1305
- }
1306
1589
  /**
1307
- * Create a function that maps from a numerical input array to a generic output array.
1308
- *
1309
- * Accepts:
1310
- * - Numbers
1311
- * - Colors (hex, hsl, hsla, rgb, rgba)
1312
- * - Complex (combinations of one or more numbers or strings)
1313
- *
1314
- * ```jsx
1315
- * const mixColor = interpolate([0, 1], ['#fff', '#000'])
1316
- *
1317
- * mixColor(0.5) // 'rgba(128, 128, 128, 1)'
1318
- * ```
1319
- *
1320
- * TODO Revist this approach once we've moved to data models for values,
1321
- * probably not needed to pregenerate mixer functions.
1322
- *
1323
- * @public
1590
+ * Gets the default ValueType for the provided value key
1324
1591
  */
1325
- function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
1326
- const inputLength = input.length;
1327
- invariant(inputLength === output.length, "Both input and output ranges must be the same length");
1328
- invariant(!ease || !Array.isArray(ease) || ease.length === inputLength - 1, "Array of easing functions must be of length `input.length - 1`, as it applies to the transitions **between** the defined values.");
1329
- // If input runs highest -> lowest, reverse both arrays
1330
- if (input[0] > input[inputLength - 1]) {
1331
- input = [...input].reverse();
1332
- output = [...output].reverse();
1333
- }
1334
- const mixers = createMixers(output, ease, mixer);
1335
- const numMixers = mixers.length;
1336
- const interpolator = (v) => {
1337
- let i = 0;
1338
- if (numMixers > 1) {
1339
- for (; i < input.length - 2; i++) {
1340
- if (v < input[i + 1])
1341
- break;
1342
- }
1343
- }
1344
- const progressInRange = progress(input[i], input[i + 1], v);
1345
- return mixers[i](progressInRange);
1346
- };
1347
- return isClamp
1348
- ? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
1349
- : interpolator;
1592
+ const getDefaultValueType = (key) => defaultValueTypes[key];
1593
+
1594
+ function getAnimatableNone(key, value) {
1595
+ var _a;
1596
+ let defaultValueType = getDefaultValueType(key);
1597
+ if (defaultValueType !== filter)
1598
+ defaultValueType = complex;
1599
+ // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
1600
+ return (_a = defaultValueType.getAnimatableNone) === null || _a === void 0 ? void 0 : _a.call(defaultValueType, value);
1350
1601
  }
1351
1602
 
1352
- function defaultEasing(values, easing) {
1353
- return values.map(() => easing || easeInOut).splice(0, values.length - 1);
1603
+ /**
1604
+ * Decide whether a transition is defined on a given Transition.
1605
+ * This filters out orchestration options and returns true
1606
+ * if any options are left.
1607
+ */
1608
+ function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, ...transition }) {
1609
+ return !!Object.keys(transition).length;
1354
1610
  }
1355
- function defaultOffset(values) {
1356
- const numValues = values.length;
1357
- return values.map((_value, i) => i !== 0 ? i / (numValues - 1) : 0);
1611
+ function isZero(value) {
1612
+ return (value === 0 ||
1613
+ (typeof value === "string" &&
1614
+ parseFloat(value) === 0 &&
1615
+ value.indexOf(" ") === -1));
1358
1616
  }
1359
- function convertOffsetToTimes(offset, duration) {
1360
- return offset.map((o) => o * duration);
1617
+ function getZeroUnit(potentialUnitType) {
1618
+ return typeof potentialUnitType === "number"
1619
+ ? 0
1620
+ : getAnimatableNone("", potentialUnitType);
1361
1621
  }
1362
- function keyframes({ from = 0, to = 1, ease, offset, duration = 300, }) {
1363
- /**
1364
- * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
1365
- * to reduce GC during animation.
1366
- */
1367
- const state = { done: false, value: from };
1368
- /**
1369
- * Convert values to an array if they've been given as from/to options
1370
- */
1371
- const values = Array.isArray(to) ? to : [from, to];
1372
- /**
1373
- * Create a times array based on the provided 0-1 offsets
1374
- */
1375
- const times = convertOffsetToTimes(
1376
- // Only use the provided offsets if they're the correct length
1377
- // TODO Maybe we should warn here if there's a length mismatch
1378
- offset && offset.length === values.length
1379
- ? offset
1380
- : defaultOffset(values), duration);
1381
- function createInterpolator() {
1382
- return interpolate(times, values, {
1383
- ease: Array.isArray(ease) ? ease : defaultEasing(values, ease),
1384
- });
1385
- }
1386
- let interpolator = createInterpolator();
1387
- return {
1388
- next: (t) => {
1389
- state.value = interpolator(t);
1390
- state.done = t >= duration;
1391
- return state;
1392
- },
1393
- flipTarget: () => {
1394
- values.reverse();
1395
- interpolator = createInterpolator();
1396
- },
1397
- };
1622
+ function getValueTransition(transition, key) {
1623
+ return transition[key] || transition["default"] || transition;
1398
1624
  }
1399
1625
 
1400
- const safeMin = 0.001;
1401
- const minDuration = 0.01;
1402
- const maxDuration = 10.0;
1403
- const minDamping = 0.05;
1404
- const maxDamping = 1;
1405
- function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) {
1406
- let envelope;
1407
- let derivative;
1408
- warning(duration <= maxDuration * 1000, "Spring duration must be 10 seconds or less");
1409
- let dampingRatio = 1 - bounce;
1626
+ function getKeyframes(value, valueName, target, transition) {
1627
+ const isTargetAnimatable = isAnimatable(valueName, target);
1628
+ let origin = transition.from !== undefined ? transition.from : value.get();
1629
+ if (origin === "none" && isTargetAnimatable && typeof target === "string") {
1630
+ /**
1631
+ * If we're trying to animate from "none", try and get an animatable version
1632
+ * of the target. This could be improved to work both ways.
1633
+ */
1634
+ origin = getAnimatableNone(valueName, target);
1635
+ }
1636
+ else if (isZero(origin) && typeof target === "string") {
1637
+ origin = getZeroUnit(target);
1638
+ }
1639
+ else if (!Array.isArray(target) &&
1640
+ isZero(target) &&
1641
+ typeof origin === "string") {
1642
+ target = getZeroUnit(origin);
1643
+ }
1410
1644
  /**
1411
- * Restrict dampingRatio and duration to within acceptable ranges.
1645
+ * If the target has been defined as a series of keyframes
1412
1646
  */
1413
- dampingRatio = clamp(minDamping, maxDamping, dampingRatio);
1414
- duration = clamp(minDuration, maxDuration, duration / 1000);
1415
- if (dampingRatio < 1) {
1416
- /**
1417
- * Underdamped spring
1418
- */
1419
- envelope = (undampedFreq) => {
1420
- const exponentialDecay = undampedFreq * dampingRatio;
1421
- const delta = exponentialDecay * duration;
1422
- const a = exponentialDecay - velocity;
1423
- const b = calcAngularFreq(undampedFreq, dampingRatio);
1424
- const c = Math.exp(-delta);
1425
- return safeMin - (a / b) * c;
1426
- };
1427
- derivative = (undampedFreq) => {
1428
- const exponentialDecay = undampedFreq * dampingRatio;
1429
- const delta = exponentialDecay * duration;
1430
- const d = delta * velocity + velocity;
1431
- const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
1432
- const f = Math.exp(-delta);
1433
- const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
1434
- const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
1435
- return (factor * ((d - e) * f)) / g;
1436
- };
1437
- }
1438
- else {
1647
+ if (Array.isArray(target)) {
1439
1648
  /**
1440
- * Critically-damped spring
1649
+ * Ensure an initial wildcard keyframe is hydrated by the origin.
1650
+ * TODO: Support extra wildcard keyframes i.e [1, null, 0]
1441
1651
  */
1442
- envelope = (undampedFreq) => {
1443
- const a = Math.exp(-undampedFreq * duration);
1444
- const b = (undampedFreq - velocity) * duration + 1;
1445
- return -safeMin + a * b;
1446
- };
1447
- derivative = (undampedFreq) => {
1448
- const a = Math.exp(-undampedFreq * duration);
1449
- const b = (velocity - undampedFreq) * (duration * duration);
1450
- return a * b;
1451
- };
1452
- }
1453
- const initialGuess = 5 / duration;
1454
- const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
1455
- duration = duration * 1000;
1456
- if (isNaN(undampedFreq)) {
1457
- return {
1458
- stiffness: 100,
1459
- damping: 10,
1460
- duration,
1461
- };
1652
+ if (target[0] === null) {
1653
+ target[0] = origin;
1654
+ }
1655
+ return target;
1462
1656
  }
1463
1657
  else {
1464
- const stiffness = Math.pow(undampedFreq, 2) * mass;
1465
- return {
1466
- stiffness,
1467
- damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
1468
- duration,
1469
- };
1658
+ return [origin, target];
1470
1659
  }
1471
1660
  }
1472
- const rootIterations = 12;
1473
- function approximateRoot(envelope, derivative, initialGuess) {
1474
- let result = initialGuess;
1475
- for (let i = 1; i < rootIterations; i++) {
1476
- result = result - envelope(result) / derivative(result);
1477
- }
1478
- return result;
1479
- }
1480
- function calcAngularFreq(undampedFreq, dampingRatio) {
1481
- return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
1482
- }
1483
1661
 
1484
- const durationKeys = ["duration", "bounce"];
1485
- const physicsKeys = ["stiffness", "damping", "mass"];
1486
- function isSpringType(options, keys) {
1487
- return keys.some((key) => options[key] !== undefined);
1488
- }
1489
- function getSpringOptions(options) {
1490
- let springOptions = {
1491
- velocity: 0.0,
1492
- stiffness: 100,
1493
- damping: 10,
1494
- mass: 1.0,
1495
- isResolvedFromDuration: false,
1496
- ...options,
1662
+ const featureTests = {
1663
+ waapi: () => Object.hasOwnProperty.call(Element.prototype, "animate"),
1664
+ };
1665
+ const results = {};
1666
+ const supports = {};
1667
+ /**
1668
+ * Generate features tests that cache their results.
1669
+ */
1670
+ for (const key in featureTests) {
1671
+ supports[key] = () => {
1672
+ if (results[key] === undefined)
1673
+ results[key] = featureTests[key]();
1674
+ return results[key];
1497
1675
  };
1498
- // stiffness/damping/mass overrides duration/bounce
1499
- if (!isSpringType(options, physicsKeys) &&
1500
- isSpringType(options, durationKeys)) {
1501
- const derived = findSpring(options);
1502
- springOptions = {
1503
- ...springOptions,
1504
- ...derived,
1505
- velocity: 0.0,
1506
- mass: 1.0,
1507
- };
1508
- springOptions.isResolvedFromDuration = true;
1509
- }
1510
- return springOptions;
1511
1676
  }
1512
- const velocitySampleDuration = 5;
1677
+
1513
1678
  /**
1514
- * This is based on the spring implementation of Wobble https://github.com/skevy/wobble
1679
+ * A list of values that can be hardware-accelerated.
1515
1680
  */
1516
- function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...options }) {
1517
- /**
1518
- * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
1519
- * to reduce GC during animation.
1520
- */
1521
- const state = { done: false, value: from };
1522
- let { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
1523
- let resolveSpring = zero;
1524
- let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
1525
- const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
1526
- function createSpring() {
1527
- const initialDelta = to - from;
1528
- const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
1681
+ const acceleratedValues = new Set(["opacity"]);
1682
+ const createMotionValueAnimation = (valueName, value, target, transition = {}) => {
1683
+ return (onComplete) => {
1684
+ const valueTransition = getValueTransition(transition, valueName) || {};
1529
1685
  /**
1530
- * If we're working within what looks like a 0-1 range, change the default restDelta
1531
- * to 0.01
1686
+ * Most transition values are currently completely overwritten by value-specific
1687
+ * transitions. In the future it'd be nicer to blend these transitions. But for now
1688
+ * delay actually does inherit from the root transition if not value-specific.
1532
1689
  */
1533
- if (restDelta === undefined) {
1534
- restDelta = Math.min(Math.abs(to - from) / 100, 0.4);
1535
- }
1536
- if (dampingRatio < 1) {
1537
- const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
1538
- // Underdamped spring
1539
- resolveSpring = (t) => {
1540
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1541
- return (to -
1542
- envelope *
1543
- (((initialVelocity +
1544
- dampingRatio * undampedAngularFreq * initialDelta) /
1545
- angularFreq) *
1546
- Math.sin(angularFreq * t) +
1547
- initialDelta * Math.cos(angularFreq * t)));
1548
- };
1549
- }
1550
- else if (dampingRatio === 1) {
1551
- // Critically damped spring
1552
- resolveSpring = (t) => to -
1553
- Math.exp(-undampedAngularFreq * t) *
1554
- (initialDelta +
1555
- (initialVelocity + undampedAngularFreq * initialDelta) *
1556
- t);
1690
+ const delay = valueTransition.delay || transition.delay || 0;
1691
+ /**
1692
+ * Elapsed isn't a public transition option but can be passed through from
1693
+ * optimized appear effects in milliseconds.
1694
+ */
1695
+ let { elapsed = 0 } = transition;
1696
+ elapsed = elapsed - secondsToMilliseconds(delay);
1697
+ const keyframes = getKeyframes(value, valueName, target, valueTransition);
1698
+ /**
1699
+ * Check if we're able to animate between the start and end keyframes,
1700
+ * and throw a warning if we're attempting to animate between one that's
1701
+ * animatable and another that isn't.
1702
+ */
1703
+ const originKeyframe = keyframes[0];
1704
+ const targetKeyframe = keyframes[keyframes.length - 1];
1705
+ const isOriginAnimatable = isAnimatable(valueName, originKeyframe);
1706
+ const isTargetAnimatable = isAnimatable(valueName, targetKeyframe);
1707
+ warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${valueName} 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.`);
1708
+ let options = {
1709
+ keyframes,
1710
+ velocity: value.getVelocity(),
1711
+ ...valueTransition,
1712
+ elapsed,
1713
+ onUpdate: (v) => {
1714
+ value.set(v);
1715
+ valueTransition.onUpdate && valueTransition.onUpdate(v);
1716
+ },
1717
+ onComplete: () => {
1718
+ onComplete();
1719
+ valueTransition.onComplete && valueTransition.onComplete();
1720
+ },
1721
+ };
1722
+ if (!isOriginAnimatable ||
1723
+ !isTargetAnimatable ||
1724
+ instantAnimationState.current ||
1725
+ valueTransition.type === false) {
1726
+ /**
1727
+ * If we can't animate this value, or the global instant animation flag is set,
1728
+ * or this is simply defined as an instant transition, return an instant transition.
1729
+ */
1730
+ return createInstantAnimation(options);
1557
1731
  }
1558
- else {
1559
- // Overdamped spring
1560
- const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
1561
- resolveSpring = (t) => {
1562
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1563
- // When performing sinh or cosh values can hit Infinity so we cap them here
1564
- const freqForT = Math.min(dampedAngularFreq * t, 300);
1565
- return (to -
1566
- (envelope *
1567
- ((initialVelocity +
1568
- dampingRatio * undampedAngularFreq * initialDelta) *
1569
- Math.sinh(freqForT) +
1570
- dampedAngularFreq *
1571
- initialDelta *
1572
- Math.cosh(freqForT))) /
1573
- dampedAngularFreq);
1574
- };
1732
+ else if (valueTransition.type === "inertia") {
1733
+ /**
1734
+ * If this is an inertia animation, we currently don't support pre-generating
1735
+ * keyframes for this as such it must always run on the main thread.
1736
+ */
1737
+ const animation = inertia(options);
1738
+ return () => animation.stop();
1575
1739
  }
1576
- }
1577
- createSpring();
1578
- return {
1579
- next: (t) => {
1580
- const current = resolveSpring(t);
1581
- if (!isResolvedFromDuration) {
1582
- let currentVelocity = initialVelocity;
1583
- if (t !== 0) {
1584
- /**
1585
- * We only need to calculate velocity for under-damped springs
1586
- * as over- and critically-damped springs can't overshoot, so
1587
- * checking only for displacement is enough.
1588
- */
1589
- if (dampingRatio < 1) {
1590
- const prevT = Math.max(0, t - velocitySampleDuration);
1591
- currentVelocity = velocityPerSecond(current - resolveSpring(prevT), t - prevT);
1592
- }
1593
- else {
1594
- currentVelocity = 0;
1595
- }
1596
- }
1597
- const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
1598
- const isBelowDisplacementThreshold = Math.abs(to - current) <= restDelta;
1599
- state.done =
1600
- isBelowVelocityThreshold && isBelowDisplacementThreshold;
1601
- }
1602
- else {
1603
- state.done = t >= duration;
1604
- }
1605
- state.value = state.done ? to : current;
1606
- return state;
1607
- },
1608
- flipTarget: () => {
1609
- initialVelocity = -initialVelocity;
1610
- [from, to] = [to, from];
1611
- createSpring();
1612
- },
1613
- };
1614
- }
1615
- spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string";
1616
- const zero = (_t) => 0;
1617
-
1618
- function decay({ velocity = 0, from = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) {
1619
- /**
1620
- * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
1621
- * to reduce GC during animation.
1622
- */
1623
- const state = { done: false, value: from };
1624
- let amplitude = power * velocity;
1625
- const ideal = from + amplitude;
1626
- const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
1627
- /**
1628
- * If the target has changed we need to re-calculate the amplitude, otherwise
1629
- * the animation will start from the wrong position.
1630
- */
1631
- if (target !== ideal)
1632
- amplitude = target - from;
1633
- return {
1634
- next: (t) => {
1635
- const delta = -amplitude * Math.exp(-t / timeConstant);
1636
- state.done = !(delta > restDelta || delta < -restDelta);
1637
- state.value = state.done ? target : target + delta;
1638
- return state;
1639
- },
1640
- flipTarget: () => { },
1641
- };
1642
- }
1643
-
1644
- const types = { decay, keyframes, spring };
1645
- function loopElapsed(elapsed, duration, delay = 0) {
1646
- return elapsed - duration - delay;
1647
- }
1648
- function reverseElapsed(elapsed, duration = 0, delay = 0, isForwardPlayback = true) {
1649
- return isForwardPlayback
1650
- ? loopElapsed(duration + -elapsed, duration, delay)
1651
- : duration - (elapsed - duration) + delay;
1652
- }
1653
- function hasRepeatDelayElapsed(elapsed, duration, delay, isForwardPlayback) {
1654
- return isForwardPlayback ? elapsed >= duration + delay : elapsed <= -delay;
1655
- }
1656
- const framesync = (update) => {
1657
- const passTimestamp = ({ delta }) => update(delta);
1658
- return {
1659
- start: () => sync.update(passTimestamp, true),
1660
- stop: () => cancelSync.update(passTimestamp),
1661
- };
1662
- };
1663
- function animate$1({ from, autoplay = true, driver = framesync, elapsed = 0, repeat: repeatMax = 0, repeatType = "loop", repeatDelay = 0, onPlay, onStop, onComplete, onRepeat, onUpdate, type = "keyframes", ...options }) {
1664
- var _a, _b;
1665
- let { to } = options;
1666
- let driverControls;
1667
- let repeatCount = 0;
1668
- let computedDuration = options
1669
- .duration;
1670
- let latest;
1671
- let isComplete = false;
1672
- let isForwardPlayback = true;
1673
- let interpolateFromNumber;
1674
- const animator = types[Array.isArray(to) ? "keyframes" : type];
1675
- if ((_b = (_a = animator).needsInterpolation) === null || _b === void 0 ? void 0 : _b.call(_a, from, to)) {
1676
- interpolateFromNumber = interpolate([0, 100], [from, to], {
1677
- clamp: false,
1678
- });
1679
- from = 0;
1680
- to = 100;
1681
- }
1682
- const animation = animator({ ...options, from, to });
1683
- function repeat() {
1684
- repeatCount++;
1685
- if (repeatType === "reverse") {
1686
- isForwardPlayback = repeatCount % 2 === 0;
1687
- elapsed = reverseElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback);
1740
+ /**
1741
+ * If there's no transition defined for this value, we can generate
1742
+ * unqiue transition settings for this value.
1743
+ */
1744
+ if (!isTransitionDefined(valueTransition)) {
1745
+ options = {
1746
+ ...options,
1747
+ ...getDefaultTransition(valueName, options),
1748
+ };
1749
+ }
1750
+ /**
1751
+ * Both WAAPI and our internal animation functions use durations
1752
+ * as defined by milliseconds, while our external API defines them
1753
+ * as seconds.
1754
+ */
1755
+ if (options.duration) {
1756
+ options.duration = secondsToMilliseconds(options.duration);
1757
+ }
1758
+ if (options.repeatDelay) {
1759
+ options.repeatDelay = secondsToMilliseconds(options.repeatDelay);
1760
+ }
1761
+ const visualElement = value.owner;
1762
+ const element = visualElement && visualElement.current;
1763
+ const canAccelerateAnimation = supports.waapi() &&
1764
+ acceleratedValues.has(valueName) &&
1765
+ !options.repeatDelay &&
1766
+ options.repeatType !== "mirror" &&
1767
+ options.damping !== 0 &&
1768
+ typeof options.ease !== "function" &&
1769
+ visualElement &&
1770
+ element instanceof HTMLElement &&
1771
+ !visualElement.getProps().onUpdate;
1772
+ if (canAccelerateAnimation) {
1773
+ /**
1774
+ * If this animation is capable of being run via WAAPI, then do so.
1775
+ */
1776
+ return createAcceleratedAnimation(value, valueName, options);
1688
1777
  }
1689
1778
  else {
1690
- elapsed = loopElapsed(elapsed, computedDuration, repeatDelay);
1691
- if (repeatType === "mirror")
1692
- animation.flipTarget();
1779
+ /**
1780
+ * Otherwise, fall back to the main thread.
1781
+ */
1782
+ const animation = animate$1(options);
1783
+ return () => animation.stop();
1693
1784
  }
1694
- isComplete = false;
1695
- onRepeat && onRepeat();
1785
+ };
1786
+ };
1787
+
1788
+ function addUniqueItem(arr, item) {
1789
+ if (arr.indexOf(item) === -1)
1790
+ arr.push(item);
1791
+ }
1792
+ function removeItem(arr, item) {
1793
+ const index = arr.indexOf(item);
1794
+ if (index > -1)
1795
+ arr.splice(index, 1);
1796
+ }
1797
+
1798
+ class SubscriptionManager {
1799
+ constructor() {
1800
+ this.subscriptions = [];
1696
1801
  }
1697
- function complete() {
1698
- driverControls.stop();
1699
- onComplete && onComplete();
1802
+ add(handler) {
1803
+ addUniqueItem(this.subscriptions, handler);
1804
+ return () => removeItem(this.subscriptions, handler);
1700
1805
  }
1701
- function update(delta) {
1702
- if (!isForwardPlayback)
1703
- delta = -delta;
1704
- elapsed += delta;
1705
- if (!isComplete) {
1706
- const state = animation.next(Math.max(0, elapsed));
1707
- latest = state.value;
1708
- if (interpolateFromNumber)
1709
- latest = interpolateFromNumber(latest);
1710
- isComplete = isForwardPlayback ? state.done : elapsed <= 0;
1806
+ notify(a, b, c) {
1807
+ const numSubscriptions = this.subscriptions.length;
1808
+ if (!numSubscriptions)
1809
+ return;
1810
+ if (numSubscriptions === 1) {
1811
+ /**
1812
+ * If there's only a single handler we can just call it without invoking a loop.
1813
+ */
1814
+ this.subscriptions[0](a, b, c);
1711
1815
  }
1712
- onUpdate && onUpdate(latest);
1713
- if (isComplete) {
1714
- if (repeatCount === 0) {
1715
- computedDuration =
1716
- computedDuration !== undefined ? computedDuration : elapsed;
1717
- }
1718
- if (repeatCount < repeatMax) {
1719
- hasRepeatDelayElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback) && repeat();
1720
- }
1721
- else {
1722
- complete();
1816
+ else {
1817
+ for (let i = 0; i < numSubscriptions; i++) {
1818
+ /**
1819
+ * Check whether the handler exists before firing as it's possible
1820
+ * the subscriptions were modified during this loop running.
1821
+ */
1822
+ const handler = this.subscriptions[i];
1823
+ handler && handler(a, b, c);
1723
1824
  }
1724
1825
  }
1725
1826
  }
1726
- function play() {
1727
- onPlay && onPlay();
1728
- driverControls = driver(update);
1729
- driverControls.start();
1827
+ getSize() {
1828
+ return this.subscriptions.length;
1829
+ }
1830
+ clear() {
1831
+ this.subscriptions.length = 0;
1730
1832
  }
1731
- autoplay && play();
1732
- return {
1733
- stop: () => {
1734
- onStop && onStop();
1735
- driverControls.stop();
1736
- },
1737
- };
1738
1833
  }
1739
1834
 
1740
- function inertia({ from = 0, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
1741
- let currentAnimation;
1742
- function isOutOfBounds(v) {
1743
- return (min !== undefined && v < min) || (max !== undefined && v > max);
1744
- }
1745
- function boundaryNearest(v) {
1746
- if (min === undefined)
1747
- return max;
1748
- if (max === undefined)
1749
- return min;
1750
- return Math.abs(min - v) < Math.abs(max - v) ? min : max;
1835
+ const isFloat = (value) => {
1836
+ return !isNaN(parseFloat(value));
1837
+ };
1838
+ /**
1839
+ * `MotionValue` is used to track the state and velocity of motion values.
1840
+ *
1841
+ * @public
1842
+ */
1843
+ class MotionValue {
1844
+ /**
1845
+ * @param init - The initiating value
1846
+ * @param config - Optional configuration options
1847
+ *
1848
+ * - `transformer`: A function to transform incoming values with.
1849
+ *
1850
+ * @internal
1851
+ */
1852
+ constructor(init, options = {}) {
1853
+ /**
1854
+ * This will be replaced by the build step with the latest version number.
1855
+ * When MotionValues are provided to motion components, warn if versions are mixed.
1856
+ */
1857
+ this.version = "7.9.0";
1858
+ /**
1859
+ * Duration, in milliseconds, since last updating frame.
1860
+ *
1861
+ * @internal
1862
+ */
1863
+ this.timeDelta = 0;
1864
+ /**
1865
+ * Timestamp of the last time this `MotionValue` was updated.
1866
+ *
1867
+ * @internal
1868
+ */
1869
+ this.lastUpdated = 0;
1870
+ /**
1871
+ * Functions to notify when the `MotionValue` updates.
1872
+ *
1873
+ * @internal
1874
+ */
1875
+ this.updateSubscribers = new SubscriptionManager();
1876
+ /**
1877
+ * Functions to notify when the velocity updates.
1878
+ *
1879
+ * @internal
1880
+ */
1881
+ this.velocityUpdateSubscribers = new SubscriptionManager();
1882
+ /**
1883
+ * Functions to notify when the `MotionValue` updates and `render` is set to `true`.
1884
+ *
1885
+ * @internal
1886
+ */
1887
+ this.renderSubscribers = new SubscriptionManager();
1888
+ /**
1889
+ * Tracks whether this value can output a velocity. Currently this is only true
1890
+ * if the value is numerical, but we might be able to widen the scope here and support
1891
+ * other value types.
1892
+ *
1893
+ * @internal
1894
+ */
1895
+ this.canTrackVelocity = false;
1896
+ this.updateAndNotify = (v, render = true) => {
1897
+ this.prev = this.current;
1898
+ this.current = v;
1899
+ // Update timestamp
1900
+ const { delta, timestamp } = frameData;
1901
+ if (this.lastUpdated !== timestamp) {
1902
+ this.timeDelta = delta;
1903
+ this.lastUpdated = timestamp;
1904
+ sync.postRender(this.scheduleVelocityCheck);
1905
+ }
1906
+ // Update update subscribers
1907
+ if (this.prev !== this.current) {
1908
+ this.updateSubscribers.notify(this.current);
1909
+ }
1910
+ // Update velocity subscribers
1911
+ if (this.velocityUpdateSubscribers.getSize()) {
1912
+ this.velocityUpdateSubscribers.notify(this.getVelocity());
1913
+ }
1914
+ // Update render subscribers
1915
+ if (render) {
1916
+ this.renderSubscribers.notify(this.current);
1917
+ }
1918
+ };
1919
+ /**
1920
+ * Schedule a velocity check for the next frame.
1921
+ *
1922
+ * This is an instanced and bound function to prevent generating a new
1923
+ * function once per frame.
1924
+ *
1925
+ * @internal
1926
+ */
1927
+ this.scheduleVelocityCheck = () => sync.postRender(this.velocityCheck);
1928
+ /**
1929
+ * Updates `prev` with `current` if the value hasn't been updated this frame.
1930
+ * This ensures velocity calculations return `0`.
1931
+ *
1932
+ * This is an instanced and bound function to prevent generating a new
1933
+ * function once per frame.
1934
+ *
1935
+ * @internal
1936
+ */
1937
+ this.velocityCheck = ({ timestamp }) => {
1938
+ if (timestamp !== this.lastUpdated) {
1939
+ this.prev = this.current;
1940
+ this.velocityUpdateSubscribers.notify(this.getVelocity());
1941
+ }
1942
+ };
1943
+ this.hasAnimated = false;
1944
+ this.prev = this.current = init;
1945
+ this.canTrackVelocity = isFloat(this.current);
1946
+ this.owner = options.owner;
1751
1947
  }
1752
- function startAnimation(options) {
1753
- currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop();
1754
- currentAnimation = animate$1({
1755
- ...options,
1756
- driver,
1757
- onUpdate: (v) => {
1758
- var _a;
1759
- onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(v);
1760
- (_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, v);
1761
- },
1762
- onComplete,
1763
- onStop,
1764
- });
1948
+ /**
1949
+ * Adds a function that will be notified when the `MotionValue` is updated.
1950
+ *
1951
+ * It returns a function that, when called, will cancel the subscription.
1952
+ *
1953
+ * When calling `onChange` inside a React component, it should be wrapped with the
1954
+ * `useEffect` hook. As it returns an unsubscribe function, this should be returned
1955
+ * from the `useEffect` function to ensure you don't add duplicate subscribers..
1956
+ *
1957
+ * ```jsx
1958
+ * export const MyComponent = () => {
1959
+ * const x = useMotionValue(0)
1960
+ * const y = useMotionValue(0)
1961
+ * const opacity = useMotionValue(1)
1962
+ *
1963
+ * useEffect(() => {
1964
+ * function updateOpacity() {
1965
+ * const maxXY = Math.max(x.get(), y.get())
1966
+ * const newOpacity = transform(maxXY, [0, 100], [1, 0])
1967
+ * opacity.set(newOpacity)
1968
+ * }
1969
+ *
1970
+ * const unsubscribeX = x.onChange(updateOpacity)
1971
+ * const unsubscribeY = y.onChange(updateOpacity)
1972
+ *
1973
+ * return () => {
1974
+ * unsubscribeX()
1975
+ * unsubscribeY()
1976
+ * }
1977
+ * }, [])
1978
+ *
1979
+ * return <motion.div style={{ x }} />
1980
+ * }
1981
+ * ```
1982
+ *
1983
+ * @privateRemarks
1984
+ *
1985
+ * We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
1986
+ *
1987
+ * ```jsx
1988
+ * useOnChange(x, () => {})
1989
+ * ```
1990
+ *
1991
+ * @param subscriber - A function that receives the latest value.
1992
+ * @returns A function that, when called, will cancel this subscription.
1993
+ *
1994
+ * @public
1995
+ */
1996
+ onChange(subscription) {
1997
+ return this.updateSubscribers.add(subscription);
1765
1998
  }
1766
- function startSpring(options) {
1767
- startAnimation({
1768
- type: "spring",
1769
- stiffness: bounceStiffness,
1770
- damping: bounceDamping,
1771
- restDelta,
1772
- ...options,
1773
- });
1999
+ clearListeners() {
2000
+ this.updateSubscribers.clear();
1774
2001
  }
1775
- if (isOutOfBounds(from)) {
1776
- // Start the animation with spring if outside the defined boundaries
1777
- startSpring({ from, velocity, to: boundaryNearest(from) });
2002
+ /**
2003
+ * Adds a function that will be notified when the `MotionValue` requests a render.
2004
+ *
2005
+ * @param subscriber - A function that's provided the latest value.
2006
+ * @returns A function that, when called, will cancel this subscription.
2007
+ *
2008
+ * @internal
2009
+ */
2010
+ onRenderRequest(subscription) {
2011
+ // Render immediately
2012
+ subscription(this.get());
2013
+ return this.renderSubscribers.add(subscription);
1778
2014
  }
1779
- else {
1780
- /**
1781
- * Or if the value is out of bounds, simulate the inertia movement
1782
- * with the decay animation.
1783
- *
1784
- * Pre-calculate the target so we can detect if it's out-of-bounds.
1785
- * If it is, we want to check per frame when to switch to a spring
1786
- * animation
1787
- */
1788
- let target = power * velocity + from;
1789
- if (typeof modifyTarget !== "undefined")
1790
- target = modifyTarget(target);
1791
- const boundary = boundaryNearest(target);
1792
- const heading = boundary === min ? -1 : 1;
1793
- let prev;
1794
- let current;
1795
- const checkBoundary = (v) => {
1796
- prev = current;
1797
- current = v;
1798
- velocity = velocityPerSecond(v - prev, frameData.delta);
1799
- if ((heading === 1 && v > boundary) ||
1800
- (heading === -1 && v < boundary)) {
1801
- startSpring({ from: v, to: boundary, velocity });
1802
- }
1803
- };
1804
- startAnimation({
1805
- type: "decay",
1806
- from,
1807
- velocity,
1808
- timeConstant,
1809
- power,
1810
- restDelta,
1811
- modifyTarget,
1812
- onUpdate: isOutOfBounds(target) ? checkBoundary : undefined,
1813
- });
2015
+ /**
2016
+ * Attaches a passive effect to the `MotionValue`.
2017
+ *
2018
+ * @internal
2019
+ */
2020
+ attach(passiveEffect) {
2021
+ this.passiveEffect = passiveEffect;
1814
2022
  }
1815
- return {
1816
- stop: () => currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop(),
1817
- };
1818
- }
1819
-
1820
- /**
1821
- * Timeout defined in ms
1822
- */
1823
- function delay(callback, timeout) {
1824
- const start = performance.now();
1825
- const checkElapsed = ({ timestamp }) => {
1826
- const elapsed = timestamp - start;
1827
- if (elapsed >= timeout) {
1828
- cancelSync.read(checkElapsed);
1829
- callback(elapsed - timeout);
1830
- }
1831
- };
1832
- sync.read(checkElapsed, true);
1833
- return () => cancelSync.read(checkElapsed);
1834
- }
1835
-
1836
- /**
1837
- * Decide whether a transition is defined on a given Transition.
1838
- * This filters out orchestration options and returns true
1839
- * if any options are left.
1840
- */
1841
- function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, ...transition }) {
1842
- return !!Object.keys(transition).length;
1843
- }
1844
- /**
1845
- * Convert Framer Motion's Transition type into Popmotion-compatible options.
1846
- */
1847
- function convertTransitionToAnimationOptions({ ease, times, ...transition }) {
1848
- const options = { ...transition };
1849
- if (times)
1850
- options["offset"] = times;
1851
2023
  /**
1852
- * Convert any existing durations from seconds to milliseconds
2024
+ * Sets the state of the `MotionValue`.
2025
+ *
2026
+ * @remarks
2027
+ *
2028
+ * ```jsx
2029
+ * const x = useMotionValue(0)
2030
+ * x.set(10)
2031
+ * ```
2032
+ *
2033
+ * @param latest - Latest value to set.
2034
+ * @param render - Whether to notify render subscribers. Defaults to `true`
2035
+ *
2036
+ * @public
1853
2037
  */
1854
- if (transition.duration)
1855
- options["duration"] = secondsToMilliseconds(transition.duration);
1856
- if (transition.repeatDelay)
1857
- options.repeatDelay = secondsToMilliseconds(transition.repeatDelay);
2038
+ set(v, render = true) {
2039
+ if (!render || !this.passiveEffect) {
2040
+ this.updateAndNotify(v, render);
2041
+ }
2042
+ else {
2043
+ this.passiveEffect(v, this.updateAndNotify);
2044
+ }
2045
+ }
2046
+ setWithVelocity(prev, current, delta) {
2047
+ this.set(current);
2048
+ this.prev = prev;
2049
+ this.timeDelta = delta;
2050
+ }
1858
2051
  /**
1859
- * Map easing names to Popmotion's easing functions
2052
+ * Returns the latest state of `MotionValue`
2053
+ *
2054
+ * @returns - The latest state of `MotionValue`
2055
+ *
2056
+ * @public
1860
2057
  */
1861
- if (ease) {
1862
- options["ease"] = isEasingArray(ease)
1863
- ? ease.map(easingDefinitionToFunction)
1864
- : easingDefinitionToFunction(ease);
2058
+ get() {
2059
+ return this.current;
1865
2060
  }
1866
2061
  /**
1867
- * Support legacy transition API
2062
+ * @public
1868
2063
  */
1869
- if (transition.type === "tween")
1870
- options.type = "keyframes";
2064
+ getPrevious() {
2065
+ return this.prev;
2066
+ }
1871
2067
  /**
1872
- * TODO: Popmotion 9 has the ability to automatically detect whether to use
1873
- * a keyframes or spring animation, but does so by detecting velocity and other spring options.
1874
- * It'd be good to introduce a similar thing here.
2068
+ * Returns the latest velocity of `MotionValue`
2069
+ *
2070
+ * @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
2071
+ *
2072
+ * @public
1875
2073
  */
1876
- if (transition.type !== "spring")
1877
- options.type = "keyframes";
1878
- return options;
1879
- }
1880
- /**
1881
- * Get the delay for a value by checking Transition with decreasing specificity.
1882
- */
1883
- function getDelayFromTransition(transition, key) {
1884
- const valueTransition = getValueTransition(transition, key) || {};
1885
- return valueTransition.delay !== undefined
1886
- ? valueTransition.delay
1887
- : transition.delay !== undefined
1888
- ? transition.delay
2074
+ getVelocity() {
2075
+ // This could be isFloat(this.prev) && isFloat(this.current), but that would be wasteful
2076
+ return this.canTrackVelocity
2077
+ ? // These casts could be avoided if parseFloat would be typed better
2078
+ velocityPerSecond(parseFloat(this.current) -
2079
+ parseFloat(this.prev), this.timeDelta)
1889
2080
  : 0;
1890
- }
1891
- function hydrateKeyframes(options) {
1892
- if (Array.isArray(options.to) && options.to[0] === null) {
1893
- options.to = [...options.to];
1894
- options.to[0] = options.from;
1895
- }
1896
- return options;
1897
- }
1898
- function getPopmotionAnimationOptions(transition, options, key) {
1899
- if (Array.isArray(options.to) && transition.duration === undefined) {
1900
- transition.duration = 0.8;
1901
2081
  }
1902
- hydrateKeyframes(options);
1903
2082
  /**
1904
- * Get a default transition if none is determined to be defined.
2083
+ * Registers a new animation to control this `MotionValue`. Only one
2084
+ * animation can drive a `MotionValue` at one time.
2085
+ *
2086
+ * ```jsx
2087
+ * value.start()
2088
+ * ```
2089
+ *
2090
+ * @param animation - A function that starts the provided animation
2091
+ *
2092
+ * @internal
1905
2093
  */
1906
- if (!isTransitionDefined(transition)) {
1907
- transition = {
1908
- ...transition,
1909
- ...getDefaultTransition(key, options.to),
1910
- };
2094
+ start(animation) {
2095
+ this.stop();
2096
+ return new Promise((resolve) => {
2097
+ this.hasAnimated = true;
2098
+ this.stopAnimation = animation(resolve);
2099
+ }).then(() => this.clearAnimation());
1911
2100
  }
1912
- return {
1913
- ...options,
1914
- ...convertTransitionToAnimationOptions(transition),
1915
- };
1916
- }
1917
- /**
1918
- *
1919
- */
1920
- function getAnimation(key, value, target, transition, onComplete) {
1921
- const valueTransition = getValueTransition(transition, key) || {};
1922
- const { elapsed = 0 } = transition;
1923
- valueTransition.elapsed =
1924
- elapsed - secondsToMilliseconds(transition.delay || 0);
1925
- let origin = valueTransition.from !== undefined ? valueTransition.from : value.get();
1926
- const isTargetAnimatable = isAnimatable(key, target);
1927
- if (origin === "none" && isTargetAnimatable && typeof target === "string") {
1928
- /**
1929
- * If we're trying to animate from "none", try and get an animatable version
1930
- * of the target. This could be improved to work both ways.
1931
- */
1932
- origin = getAnimatableNone(key, target);
2101
+ /**
2102
+ * Stop the currently active animation.
2103
+ *
2104
+ * @public
2105
+ */
2106
+ stop() {
2107
+ if (this.stopAnimation)
2108
+ this.stopAnimation();
2109
+ this.clearAnimation();
1933
2110
  }
1934
- else if (isZero(origin) && typeof target === "string") {
1935
- origin = getZeroUnit(target);
2111
+ /**
2112
+ * Returns `true` if this value is currently animating.
2113
+ *
2114
+ * @public
2115
+ */
2116
+ isAnimating() {
2117
+ return !!this.stopAnimation;
1936
2118
  }
1937
- else if (!Array.isArray(target) &&
1938
- isZero(target) &&
1939
- typeof origin === "string") {
1940
- target = getZeroUnit(origin);
2119
+ clearAnimation() {
2120
+ this.stopAnimation = null;
1941
2121
  }
1942
- const isOriginAnimatable = isAnimatable(key, origin);
1943
- warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${key} from "${origin}" to "${target}". ${origin} is not an animatable value - to enable this animation set ${origin} to a value animatable to ${target} via the \`style\` property.`);
1944
- function start() {
1945
- const options = {
1946
- from: origin,
1947
- to: target,
1948
- velocity: value.getVelocity(),
1949
- onComplete,
1950
- onUpdate: (v) => value.set(v),
1951
- };
1952
- const animation = valueTransition.type === "inertia" ||
1953
- valueTransition.type === "decay"
1954
- ? inertia({ ...options, ...valueTransition })
1955
- : animate$1({
1956
- ...getPopmotionAnimationOptions(valueTransition, options, key),
1957
- onUpdate: (v) => {
1958
- options.onUpdate(v);
1959
- valueTransition.onUpdate &&
1960
- valueTransition.onUpdate(v);
1961
- },
1962
- onComplete: () => {
1963
- options.onComplete();
1964
- valueTransition.onComplete &&
1965
- valueTransition.onComplete();
1966
- },
1967
- });
1968
- return () => animation.stop();
1969
- }
1970
- function set() {
1971
- const finalTarget = resolveFinalValueInKeyframes(target);
1972
- value.set(finalTarget);
1973
- onComplete();
1974
- valueTransition.onUpdate && valueTransition.onUpdate(finalTarget);
1975
- valueTransition.onComplete && valueTransition.onComplete();
1976
- return () => { };
2122
+ /**
2123
+ * Destroy and clean up subscribers to this `MotionValue`.
2124
+ *
2125
+ * The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
2126
+ * handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
2127
+ * created a `MotionValue` via the `motionValue` function.
2128
+ *
2129
+ * @public
2130
+ */
2131
+ destroy() {
2132
+ this.updateSubscribers.clear();
2133
+ this.renderSubscribers.clear();
2134
+ this.stop();
1977
2135
  }
1978
- const useInstantAnimation = !isOriginAnimatable ||
1979
- !isTargetAnimatable ||
1980
- valueTransition.type === false;
1981
- return useInstantAnimation
1982
- ? valueTransition.elapsed
1983
- ? () => delay(set, -valueTransition.elapsed)
1984
- : set()
1985
- : start();
1986
- }
1987
- function isZero(value) {
1988
- return (value === 0 ||
1989
- (typeof value === "string" &&
1990
- parseFloat(value) === 0 &&
1991
- value.indexOf(" ") === -1));
1992
- }
1993
- function getZeroUnit(potentialUnitType) {
1994
- return typeof potentialUnitType === "number"
1995
- ? 0
1996
- : getAnimatableNone("", potentialUnitType);
1997
- }
1998
- function getValueTransition(transition, key) {
1999
- return transition[key] || transition["default"] || transition;
2000
2136
  }
2001
- /**
2002
- * Start animation on a MotionValue. This function is an interface between
2003
- * Framer Motion and Popmotion
2004
- */
2005
- function startAnimation(key, value, target, transition = {}) {
2006
- if (instantAnimationState.current) {
2007
- transition = { type: false };
2008
- }
2009
- return value.start((onComplete) => {
2010
- return getAnimation(key, value, target, { ...transition, delay: getDelayFromTransition(transition, key) }, onComplete);
2011
- });
2137
+ function motionValue(init, options) {
2138
+ return new MotionValue(init, options);
2012
2139
  }
2013
2140
 
2141
+ const isMotionValue = (value) => !!(value === null || value === void 0 ? void 0 : value.getVelocity);
2142
+
2014
2143
  /**
2015
2144
  * Animate a single value or a `MotionValue`.
2016
2145
  *
@@ -2040,7 +2169,7 @@
2040
2169
  */
2041
2170
  function animate(from, to, transition = {}) {
2042
2171
  const value = isMotionValue(from) ? from : motionValue(from);
2043
- startAnimation("", value, to, transition);
2172
+ value.start(createMotionValueAnimation("", value, to, transition));
2044
2173
  return {
2045
2174
  stop: () => value.stop(),
2046
2175
  isAnimating: () => value.isAnimating(),
@@ -2584,6 +2713,14 @@
2584
2713
  }
2585
2714
  }
2586
2715
 
2716
+ const isKeyframesTarget = (v) => {
2717
+ return Array.isArray(v);
2718
+ };
2719
+
2720
+ const isCustomValue = (v) => {
2721
+ return Boolean(v && typeof v === "object" && v.mix && v.toValue);
2722
+ };
2723
+
2587
2724
  /**
2588
2725
  * If the provided value is a MotionValue, this returns the actual value, otherwise just the value itself
2589
2726
  *
@@ -4453,7 +4590,7 @@
4453
4590
  else if (!findValueType(value) && complex.test(targetValue)) {
4454
4591
  value = getAnimatableNone(key, targetValue);
4455
4592
  }
4456
- visualElement.addValue(key, motionValue(value));
4593
+ visualElement.addValue(key, motionValue(value, { owner: visualElement }));
4457
4594
  if (origin[key] === undefined) {
4458
4595
  origin[key] = value;
4459
4596
  }
@@ -4866,7 +5003,7 @@
4866
5003
  * and warn against mismatches.
4867
5004
  */
4868
5005
  {
4869
- warnOnce(nextValue.version === "7.8.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.8.0 may not work as expected.`);
5006
+ warnOnce(nextValue.version === "7.9.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.9.0 may not work as expected.`);
4870
5007
  }
4871
5008
  }
4872
5009
  else if (isMotionValue(prevValue)) {
@@ -4874,7 +5011,7 @@
4874
5011
  * If we're swapping from a motion value to a static value,
4875
5012
  * create a new motion value from that
4876
5013
  */
4877
- element.addValue(key, motionValue(nextValue));
5014
+ element.addValue(key, motionValue(nextValue, { owner: element }));
4878
5015
  if (isWillChangeMotionValue(willChange)) {
4879
5016
  willChange.remove(key);
4880
5017
  }
@@ -5289,7 +5426,7 @@
5289
5426
  }
5290
5427
  let value = this.values.get(key);
5291
5428
  if (value === undefined && defaultValue !== undefined) {
5292
- value = motionValue(defaultValue);
5429
+ value = motionValue(defaultValue, { owner: this });
5293
5430
  this.addValue(key, value);
5294
5431
  }
5295
5432
  return value;