vendor: 14,473 bytes, lines 1-403
1(function (global, factory) { 2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 3 typeof define === 'function' && define.amd ? define(['exports'], factory) : 4 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Motion = {})); 5})(this, (function (exports) { 'use strict'; 6 7 function addUniqueItem(arr, item) { 8 if (arr.indexOf(item) === -1) 9 arr.push(item); 10 } 11 function removeItem(arr, item) { 12 const index = arr.indexOf(item); 13 if (index > -1) 14 arr.splice(index, 1); 15 } 16 // Adapted from array-move 17 function moveItem([...arr], fromIndex, toIndex) { 18 const startIndex = fromIndex < 0 ? arr.length + fromIndex : fromIndex; 19 if (startIndex >= 0 && startIndex < arr.length) { 20 const endIndex = toIndex < 0 ? arr.length + toIndex : toIndex; 21 const [item] = arr.splice(fromIndex, 1); 22 arr.splice(endIndex, 0, item); 23 } 24 return arr; 25 } 26 27 const clamp = (min, max, v) => { 28 if (v > max) 29 return max; 30 if (v < min) 31 return min; 32 return v; 33 }; 34 35 function formatErrorMessage(message, errorCode) { 36 return errorCode 37 ? `${message}. For more information and steps for solving, visit https://motion.dev/troubleshooting/${errorCode}` 38 : message; 39 } 40 41 exports.warning = () => { }; 42 exports.invariant = () => { }; 43 { 44 exports.warning = (check, message, errorCode) => { 45 if (!check && typeof console !== "undefined") { 46 console.warn(formatErrorMessage(message, errorCode)); 47 } 48 }; 49 exports.invariant = (check, message, errorCode) => { 50 if (!check) { 51 throw new Error(formatErrorMessage(message, errorCode)); 52 } 53 }; 54 } 55 56 const MotionGlobalConfig = {}; 57 58 /** 59 * Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1" 60 */ 61 const isNumericalString = (v) => /^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(v); 62 63 function isObject(value) { 64 return typeof value === "object" && value !== null; 65 } 66 67 /** 68 * Check if the value is a zero value string like "0px" or "0%" 69 */ 70 const isZeroValueString = (v) => /^0[^.\s]+$/u.test(v); 71 72 /*#__NO_SIDE_EFFECTS__*/ 73 function memo(callback) { 74 let result; 75 return () => { 76 if (result === undefined) 77 result = callback(); 78 return result; 79 }; 80 } 81 82 /*#__NO_SIDE_EFFECTS__*/ 83 const noop = (any) => any; 84 85 /** 86 * Pipe 87 * Compose other transformers to run linearily 88 * pipe(min(20), max(40)) 89 * @param {...functions} transformers 90 * @return {function} 91 */ 92 const combineFunctions = (a, b) => (v) => b(a(v)); 93 const pipe = (...transformers) => transformers.reduce(combineFunctions); 94 95 /* 96 Progress within given range 97 98 Given a lower limit and an upper limit, we return the progress 99 (expressed as a number 0-1) represented by the given value, and 100 limit that progress to within 0-1. 101 102 @param [number]: Lower limit 103 @param [number]: Upper limit 104 @param [number]: Value to find progress within given range 105 @return [number]: Progress of value within range as expressed 0-1 106 */ 107 /*#__NO_SIDE_EFFECTS__*/ 108 const progress = (from, to, value) => { 109 const toFromDifference = to - from; 110 return toFromDifference === 0 ? 1 : (value - from) / toFromDifference; 111 }; 112 113 class SubscriptionManager { 114 constructor() { 115 this.subscriptions = []; 116 } 117 add(handler) { 118 addUniqueItem(this.subscriptions, handler); 119 return () => removeItem(this.subscriptions, handler); 120 } 121 notify(a, b, c) { 122 const numSubscriptions = this.subscriptions.length; 123 if (!numSubscriptions) 124 return; 125 if (numSubscriptions === 1) { 126 /** 127 * If there's only a single handler we can just call it without invoking a loop. 128 */ 129 this.subscriptions[0](a, b, c); 130 } 131 else { 132 for (let i = 0; i < numSubscriptions; i++) { 133 /** 134 * Check whether the handler exists before firing as it's possible 135 * the subscriptions were modified during this loop running. 136 */ 137 const handler = this.subscriptions[i]; 138 handler && handler(a, b, c); 139 } 140 } 141 } 142 getSize() { 143 return this.subscriptions.length; 144 } 145 clear() { 146 this.subscriptions.length = 0; 147 } 148 } 149 150 /** 151 * Converts seconds to milliseconds 152 * 153 * @param seconds - Time in seconds. 154 * @return milliseconds - Converted time in milliseconds. 155 */ 156 /*#__NO_SIDE_EFFECTS__*/ 157 const secondsToMilliseconds = (seconds) => seconds * 1000; 158 /*#__NO_SIDE_EFFECTS__*/ 159 const millisecondsToSeconds = (milliseconds) => milliseconds / 1000; 160 161 /* 162 Convert velocity into velocity per second 163 164 @param [number]: Unit per frame 165 @param [number]: Frame duration in ms 166 */ 167 function velocityPerSecond(velocity, frameDuration) { 168 return frameDuration ? velocity * (1000 / frameDuration) : 0; 169 } 170 171 const warned = new Set(); 172 function hasWarned(message) { 173 return warned.has(message); 174 } 175 function warnOnce(condition, message, errorCode) { 176 if (condition || warned.has(message)) 177 return; 178 console.warn(formatErrorMessage(message, errorCode)); 179 warned.add(message); 180 } 181 182 const wrap = (min, max, v) => { 183 const rangeSize = max - min; 184 return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min; 185 }; 186 187 /* 188 Bezier function generator 189 This has been modified from Gaëtan Renaudeau's BezierEasing 190 https://github.com/gre/bezier-easing/blob/master/src/index.js 191 https://github.com/gre/bezier-easing/blob/master/LICENSE 192 193 I've removed the newtonRaphsonIterate algo because in benchmarking it 194 wasn't noticeably faster than binarySubdivision, indeed removing it 195 usually improved times, depending on the curve. 196 I also removed the lookup table, as for the added bundle size and loop we're 197 only cutting ~4 or so subdivision iterations. I bumped the max iterations up 198 to 12 to compensate and this still tended to be faster for no perceivable 199 loss in accuracy. 200 Usage 201 const easeOut = cubicBezier(.17,.67,.83,.67); 202 const x = easeOut(0.5); // returns 0.627... 203 */ 204 // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. 205 const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) * 206 t; 207 const subdivisionPrecision = 0.0000001; 208 const subdivisionMaxIterations = 12; 209 function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) { 210 let currentX; 211 let currentT; 212 let i = 0; 213 do { 214 currentT = lowerBound + (upperBound - lowerBound) / 2.0; 215 currentX = calcBezier(currentT, mX1, mX2) - x; 216 if (currentX > 0.0) { 217 upperBound = currentT; 218 } 219 else { 220 lowerBound = currentT; 221 } 222 } while (Math.abs(currentX) > subdivisionPrecision && 223 ++i < subdivisionMaxIterations); 224 return currentT; 225 } 226 function cubicBezier(mX1, mY1, mX2, mY2) { 227 // If this is a linear gradient, return linear easing 228 if (mX1 === mY1 && mX2 === mY2) 229 return noop; 230 const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2); 231 // If animation is at start/end, return t without easing 232 return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2); 233 } 234 235 // Accepts an easing function and returns a new one that outputs mirrored values for 236 // the second half of the animation. Turns easeIn into easeInOut. 237 const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2; 238 239 // Accepts an easing function and returns a new one that outputs reversed values. 240 // Turns easeIn into easeOut. 241 const reverseEasing = (easing) => (p) => 1 - easing(1 - p); 242 243 const backOut = /*@__PURE__*/ cubicBezier(0.33, 1.53, 0.69, 0.99); 244 const backIn = /*@__PURE__*/ reverseEasing(backOut); 245 const backInOut = /*@__PURE__*/ mirrorEasing(backIn); 246 247 const anticipate = (p) => (p *= 2) < 1 ? 0.5 * backIn(p) : 0.5 * (2 - Math.pow(2, -10 * (p - 1))); 248 249 const circIn = (p) => 1 - Math.sin(Math.acos(p)); 250 const circOut = reverseEasing(circIn); 251 const circInOut = mirrorEasing(circIn); 252 253 const easeIn = /*@__PURE__*/ cubicBezier(0.42, 0, 1, 1); 254 const easeOut = /*@__PURE__*/ cubicBezier(0, 0, 0.58, 1); 255 const easeInOut = /*@__PURE__*/ cubicBezier(0.42, 0, 0.58, 1); 256 257 function steps(numSteps, direction = "end") { 258 return (progress) => { 259 progress = 260 direction === "end" 261 ? Math.min(progress, 0.999) 262 : Math.max(progress, 0.001); 263 const expanded = progress * numSteps; 264 const rounded = direction === "end" ? Math.floor(expanded) : Math.ceil(expanded); 265 return clamp(0, 1, rounded / numSteps); 266 }; 267 } 268 269 const isEasingArray = (ease) => { 270 return Array.isArray(ease) && typeof ease[0] !== "number"; 271 }; 272 273 function getEasingForSegment(easing, i) { 274 return isEasingArray(easing) ? easing[wrap(0, easing.length, i)] : easing; 275 } 276 277 const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number"; 278 279 const easingLookup = { 280 linear: noop, 281 easeIn, 282 easeInOut, 283 easeOut, 284 circIn, 285 circInOut, 286 circOut, 287 backIn, 288 backInOut, 289 backOut, 290 anticipate, 291 }; 292 const isValidEasing = (easing) => { 293 return typeof easing === "string"; 294 }; 295 const easingDefinitionToFunction = (definition) => { 296 if (isBezierDefinition(definition)) { 297 // If cubic bezier definition, create bezier curve 298 exports.invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`, "cubic-bezier-length"); 299 const [x1, y1, x2, y2] = definition; 300 return cubicBezier(x1, y1, x2, y2); 301 } 302 else if (isValidEasing(definition)) { 303 // Else lookup from table 304 exports.invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`, "invalid-easing-type"); 305 return easingLookup[definition]; 306 } 307 return definition; 308 }; 309 310 const stepsOrder = [ 311 "setup", // Compute 312 "read", // Read 313 "resolveKeyframes", // Write/Read/Write/Read 314 "preUpdate", // Compute 315 "update", // Compute 316 "preRender", // Compute 317 "render", // Write 318 "postRender", // Compute 319 ]; 320 321 const statsBuffer = { 322 value: null, 323 addProjectionMetrics: null, 324 }; 325 326 function createRenderStep(runNextFrame, stepName) { 327 /** 328 * We create and reuse two queues, one to queue jobs for the current frame 329 * and one for the next. We reuse to avoid triggering GC after x frames. 330 */ 331 let thisFrame = new Set(); 332 let nextFrame = new Set(); 333 /** 334 * Track whether we're currently processing jobs in this step. This way 335 * we can decide whether to schedule new jobs for this frame or next. 336 */ 337 let isProcessing = false; 338 let flushNextFrame = false; 339 /** 340 * A set of processes which were marked keepAlive when scheduled. 341 */ 342 const toKeepAlive = new WeakSet(); 343 let latestFrameData = { 344 delta: 0.0, 345 timestamp: 0.0, 346 isProcessing: false, 347 }; 348 let numCalls = 0; 349 function triggerCallback(callback) { 350 if (toKeepAlive.has(callback)) { 351 step.schedule(callback); 352 runNextFrame(); 353 } 354 numCalls++; 355 callback(latestFrameData); 356 } 357 const step = { 358 /** 359 * Schedule a process to run on the next frame. 360 */ 361 schedule: (callback, keepAlive = false, immediate = false) => { 362 const addToCurrentFrame = immediate && isProcessing; 363 const queue = addToCurrentFrame ? thisFrame : nextFrame; 364 if (keepAlive) 365 toKeepAlive.add(callback); 366 if (!queue.has(callback)) 367 queue.add(callback); 368 return callback; 369 }, 370 /** 371 * Cancel the provided callback from running on the next frame. 372 */ 373 cancel: (callback) => { 374 nextFrame.delete(callback); 375 toKeepAlive.delete(callback); 376 }, 377 /** 378 * Execute all schedule callbacks. 379 */ 380 process: (frameData) => { 381 latestFrameData = frameData; 382 /** 383 * If we're already processing we've probably been triggered by a flushSync 384 * inside an existing process. Instead of executing, mark flushNextFrame 385 * as true and ensure we flush the following frame at the end of this one. 386 */ 387 if (isProcessing) { 388 flushNextFrame = true; 389 return; 390 } 391 isProcessing = true; 392 [thisFrame, nextFrame] = [nextFrame, thisFrame]; 393 // Execute this frame 394 thisFrame.forEach(triggerCallback); 395 /** 396 * If we're recording stats then 397 */ 398 if (stepName && statsBuffer.value) { 399 statsBuffer.value.frameloop[stepName].push(numCalls); 400 } 401 numCalls = 0; 402 // Clear the frame so no callbacks remain. This is to avoid 403 // memory leaks should this render step not run for a
vendor: 44,926 bytes, lines 403-1598
403while. 404 thisFrame.clear(); 405 isProcessing = false; 406 if (flushNextFrame) { 407 flushNextFrame = false; 408 step.process(frameData); 409 } 410 }, 411 }; 412 return step; 413 } 414 415 const maxElapsed$1 = 40; 416 function createRenderBatcher(scheduleNextBatch, allowKeepAlive) { 417 let runNextFrame = false; 418 let useDefaultElapsed = true; 419 const state = { 420 delta: 0.0, 421 timestamp: 0.0, 422 isProcessing: false, 423 }; 424 const flagRunNextFrame = () => (runNextFrame = true); 425 const steps = stepsOrder.reduce((acc, key) => { 426 acc[key] = createRenderStep(flagRunNextFrame, allowKeepAlive ? key : undefined); 427 return acc; 428 }, {}); 429 const { setup, read, resolveKeyframes, preUpdate, update, preRender, render, postRender, } = steps; 430 const processBatch = () => { 431 const timestamp = MotionGlobalConfig.useManualTiming 432 ? state.timestamp 433 : performance.now(); 434 runNextFrame = false; 435 if (!MotionGlobalConfig.useManualTiming) { 436 state.delta = useDefaultElapsed 437 ? 1000 / 60 438 : Math.max(Math.min(timestamp - state.timestamp, maxElapsed$1), 1); 439 } 440 state.timestamp = timestamp; 441 state.isProcessing = true; 442 // Unrolled render loop for better per-frame performance 443 setup.process(state); 444 read.process(state); 445 resolveKeyframes.process(state); 446 preUpdate.process(state); 447 update.process(state); 448 preRender.process(state); 449 render.process(state); 450 postRender.process(state); 451 state.isProcessing = false; 452 if (runNextFrame && allowKeepAlive) { 453 useDefaultElapsed = false; 454 scheduleNextBatch(processBatch); 455 } 456 }; 457 const wake = () => { 458 runNextFrame = true; 459 useDefaultElapsed = true; 460 if (!state.isProcessing) { 461 scheduleNextBatch(processBatch); 462 } 463 }; 464 const schedule = stepsOrder.reduce((acc, key) => { 465 const step = steps[key]; 466 acc[key] = (process, keepAlive = false, immediate = false) => { 467 if (!runNextFrame) 468 wake(); 469 return step.schedule(process, keepAlive, immediate); 470 }; 471 return acc; 472 }, {}); 473 const cancel = (process) => { 474 for (let i = 0; i < stepsOrder.length; i++) { 475 steps[stepsOrder[i]].cancel(process); 476 } 477 }; 478 return { schedule, cancel, state, steps }; 479 } 480 481 const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true); 482 483 let now; 484 function clearTime() { 485 now = undefined; 486 } 487 /** 488 * An eventloop-synchronous alternative to performance.now(). 489 * 490 * Ensures that time measurements remain consistent within a synchronous context. 491 * Usually calling performance.now() twice within the same synchronous context 492 * will return different values which isn't useful for animations when we're usually 493 * trying to sync animations to the same frame. 494 */ 495 const time = { 496 now: () => { 497 if (now === undefined) { 498 time.set(frameData.isProcessing || MotionGlobalConfig.useManualTiming 499 ? frameData.timestamp 500 : performance.now()); 501 } 502 return now; 503 }, 504 set: (newTime) => { 505 now = newTime; 506 queueMicrotask(clearTime); 507 }, 508 }; 509 510 const activeAnimations = { 511 layout: 0, 512 mainThread: 0, 513 waapi: 0, 514 }; 515 516 const checkStringStartsWith = (token) => (key) => typeof key === "string" && key.startsWith(token); 517 const isCSSVariableName = 518 /*@__PURE__*/ checkStringStartsWith("--"); 519 const startsAsVariableToken = 520 /*@__PURE__*/ checkStringStartsWith("var(--"); 521 const isCSSVariableToken = (value) => { 522 const startsWithToken = startsAsVariableToken(value); 523 if (!startsWithToken) 524 return false; 525 // Ensure any comments are stripped from the value as this can harm performance of the regex. 526 return singleCssVariableRegex.test(value.split("/*")[0].trim()); 527 }; 528 const singleCssVariableRegex = /var\(--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)$/iu; 529 530 const number = { 531 test: (v) => typeof v === "number", 532 parse: parseFloat, 533 transform: (v) => v, 534 }; 535 const alpha = { 536 ...number, 537 transform: (v) => clamp(0, 1, v), 538 }; 539 const scale = { 540 ...number, 541 default: 1, 542 }; 543 544 // If this number is a decimal, make it just five decimal places 545 // to avoid exponents 546 const sanitize = (v) => Math.round(v * 100000) / 100000; 547 548 const floatRegex = /-?(?:\d+(?:\.\d+)?|\.\d+)/gu; 549 550 function isNullish(v) { 551 return v == null; 552 } 553 554 const singleColorRegex = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))$/iu; 555 556 /** 557 * Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000, 558 * but false if a number or multiple colors 559 */ 560 const isColorString = (type, testProp) => (v) => { 561 return Boolean((typeof v === "string" && 562 singleColorRegex.test(v) && 563 v.startsWith(type)) || 564 (testProp && 565 !isNullish(v) && 566 Object.prototype.hasOwnProperty.call(v, testProp))); 567 }; 568 const splitColor = (aName, bName, cName) => (v) => { 569 if (typeof v !== "string") 570 return v; 571 const [a, b, c, alpha] = v.match(floatRegex); 572 return { 573 [aName]: parseFloat(a), 574 [bName]: parseFloat(b), 575 [cName]: parseFloat(c), 576 alpha: alpha !== undefined ? parseFloat(alpha) : 1, 577 }; 578 }; 579 580 const clampRgbUnit = (v) => clamp(0, 255, v); 581 const rgbUnit = { 582 ...number, 583 transform: (v) => Math.round(clampRgbUnit(v)), 584 }; 585 const rgba = { 586 test: /*@__PURE__*/ isColorString("rgb", "red"), 587 parse: /*@__PURE__*/ splitColor("red", "green", "blue"), 588 transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => "rgba(" + 589 rgbUnit.transform(red) + 590 ", " + 591 rgbUnit.transform(green) + 592 ", " + 593 rgbUnit.transform(blue) + 594 ", " + 595 sanitize(alpha.transform(alpha$1)) + 596 ")", 597 }; 598 599 function parseHex(v) { 600 let r = ""; 601 let g = ""; 602 let b = ""; 603 let a = ""; 604 // If we have 6 characters, ie #FF0000 605 if (v.length > 5) { 606 r = v.substring(1, 3); 607 g = v.substring(3, 5); 608 b = v.substring(5, 7); 609 a = v.substring(7, 9); 610 // Or we have 3 characters, ie #F00 611 } 612 else { 613 r = v.substring(1, 2); 614 g = v.substring(2, 3); 615 b = v.substring(3, 4); 616 a = v.substring(4, 5); 617 r += r; 618 g += g; 619 b += b; 620 a += a; 621 } 622 return { 623 red: parseInt(r, 16), 624 green: parseInt(g, 16), 625 blue: parseInt(b, 16), 626 alpha: a ? parseInt(a, 16) / 255 : 1, 627 }; 628 } 629 const hex = { 630 test: /*@__PURE__*/ isColorString("#"), 631 parse: parseHex, 632 transform: rgba.transform, 633 }; 634 635 /*#__NO_SIDE_EFFECTS__*/ 636 const createUnitType = (unit) => ({ 637 test: (v) => typeof v === "string" && v.endsWith(unit) && v.split(" ").length === 1, 638 parse: parseFloat, 639 transform: (v) => `${v}${unit}`, 640 }); 641 const degrees = /*@__PURE__*/ createUnitType("deg"); 642 const percent = /*@__PURE__*/ createUnitType("%"); 643 const px = /*@__PURE__*/ createUnitType("px"); 644 const vh = /*@__PURE__*/ createUnitType("vh"); 645 const vw = /*@__PURE__*/ createUnitType("vw"); 646 const progressPercentage = /*@__PURE__*/ (() => ({ 647 ...percent, 648 parse: (v) => percent.parse(v) / 100, 649 transform: (v) => percent.transform(v * 100), 650 }))(); 651 652 const hsla = { 653 test: /*@__PURE__*/ isColorString("hsl", "hue"), 654 parse: /*@__PURE__*/ splitColor("hue", "saturation", "lightness"), 655 transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => { 656 return ("hsla(" + 657 Math.round(hue) + 658 ", " + 659 percent.transform(sanitize(saturation)) + 660 ", " + 661 percent.transform(sanitize(lightness)) + 662 ", " + 663 sanitize(alpha.transform(alpha$1)) + 664 ")"); 665 }, 666 }; 667 668 const color = { 669 test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v), 670 parse: (v) => { 671 if (rgba.test(v)) { 672 return rgba.parse(v); 673 } 674 else if (hsla.test(v)) { 675 return hsla.parse(v); 676 } 677 else { 678 return hex.parse(v); 679 } 680 }, 681 transform: (v) => { 682 return typeof v === "string" 683 ? v 684 : v.hasOwnProperty("red") 685 ? rgba.transform(v) 686 : hsla.transform(v); 687 }, 688 getAnimatableNone: (v) => { 689 const parsed = color.parse(v); 690 parsed.alpha = 0; 691 return color.transform(parsed); 692 }, 693 }; 694 695 const colorRegex = /(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))/giu; 696 697 function test(v) { 698 return (isNaN(v) && 699 typeof v === "string" && 700 (v.match(floatRegex)?.length || 0) + 701 (v.match(colorRegex)?.length || 0) > 702 0); 703 } 704 const NUMBER_TOKEN = "number"; 705 const COLOR_TOKEN = "color"; 706 const VAR_TOKEN = "var"; 707 const VAR_FUNCTION_TOKEN = "var("; 708 const SPLIT_TOKEN = "${}"; 709 // this regex consists of the `singleCssVariableRegex|rgbHSLValueRegex|digitRegex` 710 const complexRegex = /var\s*\(\s*--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)|#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\)|-?(?:\d+(?:\.\d+)?|\.\d+)/giu; 711 function analyseComplexValue(value) { 712 const originalValue = value.toString(); 713 const values = []; 714 const indexes = { 715 color: [], 716 number: [], 717 var: [], 718 }; 719 const types = []; 720 let i = 0; 721 const tokenised = originalValue.replace(complexRegex, (parsedValue) => { 722 if (color.test(parsedValue)) { 723 indexes.color.push(i); 724 types.push(COLOR_TOKEN); 725 values.push(color.parse(parsedValue)); 726 } 727 else if (parsedValue.startsWith(VAR_FUNCTION_TOKEN)) { 728 indexes.var.push(i); 729 types.push(VAR_TOKEN); 730 values.push(parsedValue); 731 } 732 else { 733 indexes.number.push(i); 734 types.push(NUMBER_TOKEN); 735 values.push(parseFloat(parsedValue)); 736 } 737 ++i; 738 return SPLIT_TOKEN; 739 }); 740 const split = tokenised.split(SPLIT_TOKEN); 741 return { values, split, indexes, types }; 742 } 743 function parseComplexValue(v) { 744 return analyseComplexValue(v).values; 745 } 746 function createTransformer(source) { 747 const { split, types } = analyseComplexValue(source); 748 const numSections = split.length; 749 return (v) => { 750 let output = ""; 751 for (let i = 0; i < numSections; i++) { 752 output += split[i]; 753 if (v[i] !== undefined) { 754 const type = types[i]; 755 if (type === NUMBER_TOKEN) { 756 output += sanitize(v[i]); 757 } 758 else if (type === COLOR_TOKEN) { 759 output += color.transform(v[i]); 760 } 761 else { 762 output += v[i]; 763 } 764 } 765 } 766 return output; 767 }; 768 } 769 const convertNumbersToZero = (v) => typeof v === "number" ? 0 : color.test(v) ? color.getAnimatableNone(v) : v; 770 function getAnimatableNone$1(v) { 771 const parsed = parseComplexValue(v); 772 const transformer = createTransformer(v); 773 return transformer(parsed.map(convertNumbersToZero)); 774 } 775 const complex = { 776 test, 777 parse: parseComplexValue, 778 createTransformer, 779 getAnimatableNone: getAnimatableNone$1, 780 }; 781 782 // Adapted from https://gist.github.com/mjackson/5311256 783 function hueToRgb(p, q, t) { 784 if (t < 0) 785 t += 1; 786 if (t > 1) 787 t -= 1; 788 if (t < 1 / 6) 789 return p + (q - p) * 6 * t; 790 if (t < 1 / 2) 791 return q; 792 if (t < 2 / 3) 793 return p + (q - p) * (2 / 3 - t) * 6; 794 return p; 795 } 796 function hslaToRgba({ hue, saturation, lightness, alpha }) { 797 hue /= 360; 798 saturation /= 100; 799 lightness /= 100; 800 let red = 0; 801 let green = 0; 802 let blue = 0; 803 if (!saturation) { 804 red = green = blue = lightness; 805 } 806 else { 807 const q = lightness < 0.5 808 ? lightness * (1 + saturation) 809 : lightness + saturation - lightness * saturation; 810 const p = 2 * lightness - q; 811 red = hueToRgb(p, q, hue + 1 / 3); 812 green = hueToRgb(p, q, hue); 813 blue = hueToRgb(p, q, hue - 1 / 3); 814 } 815 return { 816 red: Math.round(red * 255), 817 green: Math.round(green * 255), 818 blue: Math.round(blue * 255), 819 alpha, 820 }; 821 } 822 823 function mixImmediate(a, b) { 824 return (p) => (p > 0 ? b : a); 825 } 826 827 /* 828 Value in range from progress 829 830 Given a lower limit and an upper limit, we return the value within 831 that range as expressed by progress (usually a number from 0 to 1) 832 833 So progress = 0.5 would change 834 835 from -------- to 836 837 to 838 839 from ---- to 840 841 E.g. from = 10, to = 20, progress = 0.5 => 15 842 843 @param [number]: Lower limit of range 844 @param [number]: Upper limit of range 845 @param [number]: The progress between lower and upper limits expressed 0-1 846 @return [number]: Value as calculated from progress within range (not limited within range) 847 */ 848 const mixNumber$1 = (from, to, progress) => { 849 return from + (to - from) * progress; 850 }; 851 852 // Linear color space blending 853 // Explained https://www.youtube.com/watch?v=LKnqECcg6Gw 854 // Demonstrated http://codepen.io/osublake/pen/xGVVaN 855 const mixLinearColor = (from, to, v) => { 856 const fromExpo = from * from; 857 const expo = v * (to * to - fromExpo) + fromExpo; 858 return expo < 0 ? 0 : Math.sqrt(expo); 859 }; 860 const colorTypes = [hex, rgba, hsla]; 861 const getColorType = (v) => colorTypes.find((type) => type.test(v)); 862 function asRGBA(color) { 863 const type = getColorType(color); 864 exports.warning(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`, "color-not-animatable"); 865 if (!Boolean(type)) 866 return false; 867 let model = type.parse(color); 868 if (type === hsla) { 869 // TODO Remove this cast - needed since Motion's stricter typing 870 model = hslaToRgba(model); 871 } 872 return model; 873 } 874 const mixColor = (from, to) => { 875 const fromRGBA = asRGBA(from); 876 const toRGBA = asRGBA(to); 877 if (!fromRGBA || !toRGBA) { 878 return mixImmediate(from, to); 879 } 880 const blended = { ...fromRGBA }; 881 return (v) => { 882 blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v); 883 blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v); 884 blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v); 885 blended.alpha = mixNumber$1(fromRGBA.alpha, toRGBA.alpha, v); 886 return rgba.transform(blended); 887 }; 888 }; 889 890 const invisibleValues = new Set(["none", "hidden"]); 891 /** 892 * Returns a function that, when provided a progress value between 0 and 1, 893 * will return the "none" or "hidden" string only when the progress is that of 894 * the origin or target. 895 */ 896 function mixVisibility(origin, target) { 897 if (invisibleValues.has(origin)) { 898 return (p) => (p <= 0 ? origin : target); 899 } 900 else { 901 return (p) => (p >= 1 ? target : origin); 902 } 903 } 904 905 function mixNumber(a, b) { 906 return (p) => mixNumber$1(a, b, p); 907 } 908 function getMixer(a) { 909 if (typeof a === "number") { 910 return mixNumber; 911 } 912 else if (typeof a === "string") { 913 return isCSSVariableToken(a) 914 ? mixImmediate 915 : color.test(a) 916 ? mixColor 917 : mixComplex; 918 } 919 else if (Array.isArray(a)) { 920 return mixArray; 921 } 922 else if (typeof a === "object") { 923 return color.test(a) ? mixColor : mixObject; 924 } 925 return mixImmediate; 926 } 927 function mixArray(a, b) { 928 const output = [...a]; 929 const numValues = output.length; 930 const blendValue = a.map((v, i) => getMixer(v)(v, b[i])); 931 return (p) => { 932 for (let i = 0; i < numValues; i++) { 933 output[i] = blendValue[i](p); 934 } 935 return output; 936 }; 937 } 938 function mixObject(a, b) { 939 const output = { ...a, ...b }; 940 const blendValue = {}; 941 for (const key in output) { 942 if (a[key] !== undefined && b[key] !== undefined) { 943 blendValue[key] = getMixer(a[key])(a[key], b[key]); 944 } 945 } 946 return (v) => { 947 for (const key in blendValue) { 948 output[key] = blendValue[key](v); 949 } 950 return output; 951 }; 952 } 953 function matchOrder(origin, target) { 954 const orderedOrigin = []; 955 const pointers = { color: 0, var: 0, number: 0 }; 956 for (let i = 0; i < target.values.length; i++) { 957 const type = target.types[i]; 958 const originIndex = origin.indexes[type][pointers[type]]; 959 const originValue = origin.values[originIndex] ?? 0; 960 orderedOrigin[i] = originValue; 961 pointers[type]++; 962 } 963 return orderedOrigin; 964 } 965 const mixComplex = (origin, target) => { 966 const template = complex.createTransformer(target); 967 const originStats = analyseComplexValue(origin); 968 const targetStats = analyseComplexValue(target); 969 const canInterpolate = originStats.indexes.var.length === targetStats.indexes.var.length && 970 originStats.indexes.color.length === targetStats.indexes.color.length && 971 originStats.indexes.number.length >= targetStats.indexes.number.length; 972 if (canInterpolate) { 973 if ((invisibleValues.has(origin) && 974 !targetStats.values.length) || 975 (invisibleValues.has(target) && 976 !originStats.values.length)) { 977 return mixVisibility(origin, target); 978 } 979 return pipe(mixArray(matchOrder(originStats, targetStats), targetStats.values), template); 980 } 981 else { 982 exports.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.`, "complex-values-different"); 983 return mixImmediate(origin, target); 984 } 985 }; 986 987 function mix(from, to, p) { 988 if (typeof from === "number" && 989 typeof to === "number" && 990 typeof p === "number") { 991 return mixNumber$1(from, to, p); 992 } 993 const mixer = getMixer(from); 994 return mixer(from, to); 995 } 996 997 const frameloopDriver = (update) => { 998 const passTimestamp = ({ timestamp }) => update(timestamp); 999 return { 1000 start: (keepAlive = true) => frame.update(passTimestamp, keepAlive), 1001 stop: () => cancelFrame(passTimestamp), 1002 /** 1003 * If we're processing this frame we can use the 1004 * framelocked timestamp to keep things in sync. 1005 */ 1006 now: () => (frameData.isProcessing ? frameData.timestamp : time.now()), 1007 }; 1008 }; 1009 1010 const generateLinearEasing = (easing, duration, // as milliseconds 1011 resolution = 10 // as milliseconds 1012 ) => { 1013 let points = ""; 1014 const numPoints = Math.max(Math.round(duration / resolution), 2); 1015 for (let i = 0; i < numPoints; i++) { 1016 points += Math.round(easing(i / (numPoints - 1)) * 10000) / 10000 + ", "; 1017 } 1018 return `linear(${points.substring(0, points.length - 2)})`; 1019 }; 1020 1021 /** 1022 * Implement a practical max duration for keyframe generation 1023 * to prevent infinite loops 1024 */ 1025 const maxGeneratorDuration = 20000; 1026 function calcGeneratorDuration(generator) { 1027 let duration = 0; 1028 const timeStep = 50; 1029 let state = generator.next(duration); 1030 while (!state.done && duration < maxGeneratorDuration) { 1031 duration += timeStep; 1032 state = generator.next(duration); 1033 } 1034 return duration >= maxGeneratorDuration ? Infinity : duration; 1035 } 1036 1037 /** 1038 * Create a progress => progress easing function from a generator. 1039 */ 1040 function createGeneratorEasing(options, scale = 100, createGenerator) { 1041 const generator = createGenerator({ ...options, keyframes: [0, scale] }); 1042 const duration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration); 1043 return { 1044 type: "keyframes", 1045 ease: (progress) => { 1046 return generator.next(duration * progress).value / scale; 1047 }, 1048 duration: millisecondsToSeconds(duration), 1049 }; 1050 } 1051 1052 const velocitySampleDuration = 5; // ms 1053 function calcGeneratorVelocity(resolveValue, t, current) { 1054 const prevT = Math.max(t - velocitySampleDuration, 0); 1055 return velocityPerSecond(current - resolveValue(prevT), t - prevT); 1056 } 1057 1058 const springDefaults = { 1059 // Default spring physics 1060 stiffness: 100, 1061 damping: 10, 1062 mass: 1.0, 1063 velocity: 0.0, 1064 // Default duration/bounce-based options 1065 duration: 800, // in ms 1066 bounce: 0.3, 1067 visualDuration: 0.3, // in seconds 1068 // Rest thresholds 1069 restSpeed: { 1070 granular: 0.01, 1071 default: 2, 1072 }, 1073 restDelta: { 1074 granular: 0.005, 1075 default: 0.5, 1076 }, 1077 // Limits 1078 minDuration: 0.01, // in seconds 1079 maxDuration: 10.0, // in seconds 1080 minDamping: 0.05, 1081 maxDamping: 1, 1082 }; 1083 1084 const safeMin = 0.001; 1085 function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, velocity = springDefaults.velocity, mass = springDefaults.mass, }) { 1086 let envelope; 1087 let derivative; 1088 exports.warning(duration <= secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less", "spring-duration-limit"); 1089 let dampingRatio = 1 - bounce; 1090 /** 1091 * Restrict dampingRatio and duration to within acceptable ranges. 1092 */ 1093 dampingRatio = clamp(springDefaults.minDamping, springDefaults.maxDamping, dampingRatio); 1094 duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, millisecondsToSeconds(duration)); 1095 if (dampingRatio < 1) { 1096 /** 1097 * Underdamped spring 1098 */ 1099 envelope = (undampedFreq) => { 1100 const exponentialDecay = undampedFreq * dampingRatio; 1101 const delta = exponentialDecay * duration; 1102 const a = exponentialDecay - velocity; 1103 const b = calcAngularFreq(undampedFreq, dampingRatio); 1104 const c = Math.exp(-delta); 1105 return safeMin - (a / b) * c; 1106 }; 1107 derivative = (undampedFreq) => { 1108 const exponentialDecay = undampedFreq * dampingRatio; 1109 const delta = exponentialDecay * duration; 1110 const d = delta * velocity + velocity; 1111 const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration; 1112 const f = Math.exp(-delta); 1113 const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio); 1114 const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1; 1115 return (factor * ((d - e) * f)) / g; 1116 }; 1117 } 1118 else { 1119 /** 1120 * Critically-damped spring 1121 */ 1122 envelope = (undampedFreq) => { 1123 const a = Math.exp(-undampedFreq * duration); 1124 const b = (undampedFreq - velocity) * duration + 1; 1125 return -safeMin + a * b; 1126 }; 1127 derivative = (undampedFreq) => { 1128 const a = Math.exp(-undampedFreq * duration); 1129 const b = (velocity - undampedFreq) * (duration * duration); 1130 return a * b; 1131 }; 1132 } 1133 const initialGuess = 5 / duration; 1134 const undampedFreq = approximateRoot(envelope, derivative, initialGuess); 1135 duration = secondsToMilliseconds(duration); 1136 if (isNaN(undampedFreq)) { 1137 return { 1138 stiffness: springDefaults.stiffness, 1139 damping: springDefaults.damping, 1140 duration, 1141 }; 1142 } 1143 else { 1144 const stiffness = Math.pow(undampedFreq, 2) * mass; 1145 return { 1146 stiffness, 1147 damping: dampingRatio * 2 * Math.sqrt(mass * stiffness), 1148 duration, 1149 }; 1150 } 1151 } 1152 const rootIterations = 12; 1153 function approximateRoot(envelope, derivative, initialGuess) { 1154 let result = initialGuess; 1155 for (let i = 1; i < rootIterations; i++) { 1156 result = result - envelope(result) / derivative(result); 1157 } 1158 return result; 1159 } 1160 function calcAngularFreq(undampedFreq, dampingRatio) { 1161 return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio); 1162 } 1163 1164 const durationKeys = ["duration", "bounce"]; 1165 const physicsKeys = ["stiffness", "damping", "mass"]; 1166 function isSpringType(options, keys) { 1167 return keys.some((key) => options[key] !== undefined); 1168 } 1169 function getSpringOptions(options) { 1170 let springOptions = { 1171 velocity: springDefaults.velocity, 1172 stiffness: springDefaults.stiffness, 1173 damping: springDefaults.damping, 1174 mass: springDefaults.mass, 1175 isResolvedFromDuration: false, 1176 ...options, 1177 }; 1178 // stiffness/damping/mass overrides duration/bounce 1179 if (!isSpringType(options, physicsKeys) && 1180 isSpringType(options, durationKeys)) { 1181 if (options.visualDuration) { 1182 const visualDuration = options.visualDuration; 1183 const root = (2 * Math.PI) / (visualDuration * 1.2); 1184 const stiffness = root * root; 1185 const damping = 2 * 1186 clamp(0.05, 1, 1 - (options.bounce || 0)) * 1187 Math.sqrt(stiffness); 1188 springOptions = { 1189 ...springOptions, 1190 mass: springDefaults.mass, 1191 stiffness, 1192 damping, 1193 }; 1194 } 1195 else { 1196 const derived = findSpring(options); 1197 springOptions = { 1198 ...springOptions, 1199 ...derived, 1200 mass: springDefaults.mass, 1201 }; 1202 springOptions.isResolvedFromDuration = true; 1203 } 1204 } 1205 return springOptions; 1206 } 1207 function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce = springDefaults.bounce) { 1208 const options = typeof optionsOrVisualDuration !== "object" 1209 ? { 1210 visualDuration: optionsOrVisualDuration, 1211 keyframes: [0, 1], 1212 bounce, 1213 } 1214 : optionsOrVisualDuration; 1215 let { restSpeed, restDelta } = options; 1216 const origin = options.keyframes[0]; 1217 const target = options.keyframes[options.keyframes.length - 1]; 1218 /** 1219 * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator 1220 * to reduce GC during animation. 1221 */ 1222 const state = { done: false, value: origin }; 1223 const { stiffness, damping, mass, duration, velocity, isResolvedFromDuration, } = getSpringOptions({ 1224 ...options, 1225 velocity: -millisecondsToSeconds(options.velocity || 0), 1226 }); 1227 const initialVelocity = velocity || 0.0; 1228 const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass)); 1229 const initialDelta = target - origin; 1230 const undampedAngularFreq = millisecondsToSeconds(Math.sqrt(stiffness / mass)); 1231 /** 1232 * If we're working on a granular scale, use smaller defaults for determining 1233 * when the spring is finished. 1234 * 1235 * These defaults have been selected emprically based on what strikes a good 1236 * ratio between feeling good and finishing as soon as changes are imperceptible. 1237 */ 1238 const isGranularScale = Math.abs(initialDelta) < 5; 1239 restSpeed || (restSpeed = isGranularScale 1240 ? springDefaults.restSpeed.granular 1241 : springDefaults.restSpeed.default); 1242 restDelta || (restDelta = isGranularScale 1243 ? springDefaults.restDelta.granular 1244 : springDefaults.restDelta.default); 1245 let resolveSpring; 1246 if (dampingRatio < 1) { 1247 const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio); 1248 // Underdamped spring 1249 resolveSpring = (t) => { 1250 const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t); 1251 return (target - 1252 envelope * 1253 (((initialVelocity + 1254 dampingRatio * undampedAngularFreq * initialDelta) / 1255 angularFreq) * 1256 Math.sin(angularFreq * t) + 1257 initialDelta * Math.cos(angularFreq * t))); 1258 }; 1259 } 1260 else if (dampingRatio === 1) { 1261 // Critically damped spring 1262 resolveSpring = (t) => target - 1263 Math.exp(-undampedAngularFreq * t) * 1264 (initialDelta + 1265 (initialVelocity + undampedAngularFreq * initialDelta) * t); 1266 } 1267 else { 1268 // Overdamped spring 1269 const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1); 1270 resolveSpring = (t) => { 1271 const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t); 1272 // When performing sinh or cosh values can hit Infinity so we cap them here 1273 const freqForT = Math.min(dampedAngularFreq * t, 300); 1274 return (target - 1275 (envelope * 1276 ((initialVelocity + 1277 dampingRatio * undampedAngularFreq * initialDelta) * 1278 Math.sinh(freqForT) + 1279 dampedAngularFreq * 1280 initialDelta * 1281 Math.cosh(freqForT))) / 1282 dampedAngularFreq); 1283 }; 1284 } 1285 const generator = { 1286 calculatedDuration: isResolvedFromDuration ? duration || null : null, 1287 next: (t) => { 1288 const current = resolveSpring(t); 1289 if (!isResolvedFromDuration) { 1290 let currentVelocity = t === 0 ? initialVelocity : 0.0; 1291 /** 1292 * We only need to calculate velocity for under-damped springs 1293 * as over- and critically-damped springs can't overshoot, so 1294 * checking only for displacement is enough. 1295 */ 1296 if (dampingRatio < 1) { 1297 currentVelocity = 1298 t === 0 1299 ? secondsToMilliseconds(initialVelocity) 1300 : calcGeneratorVelocity(resolveSpring, t, current); 1301 } 1302 const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed; 1303 const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta; 1304 state.done = 1305 isBelowVelocityThreshold && isBelowDisplacementThreshold; 1306 } 1307 else { 1308 state.done = t >= duration; 1309 } 1310 state.value = state.done ? target : current; 1311 return state; 1312 }, 1313 toString: () => { 1314 const calculatedDuration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration); 1315 const easing = generateLinearEasing((progress) => generator.next(calculatedDuration * progress).value, calculatedDuration, 30); 1316 return calculatedDuration + "ms " + easing; 1317 }, 1318 toTransition: () => { }, 1319 }; 1320 return generator; 1321 } 1322 spring.applyToOptions = (options) => { 1323 const generatorOptions = createGeneratorEasing(options, 100, spring); 1324 options.ease = generatorOptions.ease; 1325 options.duration = secondsToMilliseconds(generatorOptions.duration); 1326 options.type = "keyframes"; 1327 return options; 1328 }; 1329 1330 function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) { 1331 const origin = keyframes[0]; 1332 const state = { 1333 done: false, 1334 value: origin, 1335 }; 1336 const isOutOfBounds = (v) => (min !== undefined && v < min) || (max !== undefined && v > max); 1337 const nearestBoundary = (v) => { 1338 if (min === undefined) 1339 return max; 1340 if (max === undefined) 1341 return min; 1342 return Math.abs(min - v) < Math.abs(max - v) ? min : max; 1343 }; 1344 let amplitude = power * velocity; 1345 const ideal = origin + amplitude; 1346 const target = modifyTarget === undefined ? ideal : modifyTarget(ideal); 1347 /** 1348 * If the target has changed we need to re-calculate the amplitude, otherwise 1349 * the animation will start from the wrong position. 1350 */ 1351 if (target !== ideal) 1352 amplitude = target - origin; 1353 const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant); 1354 const calcLatest = (t) => target + calcDelta(t); 1355 const applyFriction = (t) => { 1356 const delta = calcDelta(t); 1357 const latest = calcLatest(t); 1358 state.done = Math.abs(delta) <= restDelta; 1359 state.value = state.done ? target : latest; 1360 }; 1361 /** 1362 * Ideally this would resolve for t in a stateless way, we could 1363 * do that by always precalculating the animation but as we know 1364 * this will be done anyway we can assume that spring will 1365 * be discovered during that. 1366 */ 1367 let timeReachedBoundary; 1368 let spring$1; 1369 const checkCatchBoundary = (t) => { 1370 if (!isOutOfBounds(state.value)) 1371 return; 1372 timeReachedBoundary = t; 1373 spring$1 = spring({ 1374 keyframes: [state.value, nearestBoundary(state.value)], 1375 velocity: calcGeneratorVelocity(calcLatest, t, state.value), // TODO: This should be passing * 1000 1376 damping: bounceDamping, 1377 stiffness: bounceStiffness, 1378 restDelta, 1379 restSpeed, 1380 }); 1381 }; 1382 checkCatchBoundary(0); 1383 return { 1384 calculatedDuration: null, 1385 next: (t) => { 1386 /** 1387 * We need to resolve the friction to figure out if we need a 1388 * spring but we don't want to do this twice per frame. So here 1389 * we flag if we updated for this frame and later if we did 1390 * we can skip doing it again. 1391 */ 1392 let hasUpdatedFrame = false; 1393 if (!spring$1 && timeReachedBoundary === undefined) { 1394 hasUpdatedFrame = true; 1395 applyFriction(t); 1396 checkCatchBoundary(t); 1397 } 1398 /** 1399 * If we have a spring and the provided t is beyond the moment the friction 1400 * animation crossed the min/max boundary, use the spring. 1401 */ 1402 if (timeReachedBoundary !== undefined && t >= timeReachedBoundary) { 1403 return spring$1.next(t - timeReachedBoundary); 1404 } 1405 else { 1406 !hasUpdatedFrame && applyFriction(t); 1407 return state; 1408 } 1409 }, 1410 }; 1411 } 1412 1413 function createMixers(output, ease, customMixer) { 1414 const mixers = []; 1415 const mixerFactory = customMixer || MotionGlobalConfig.mix || mix; 1416 const numMixers = output.length - 1; 1417 for (let i = 0; i < numMixers; i++) { 1418 let mixer = mixerFactory(output[i], output[i + 1]); 1419 if (ease) { 1420 const easingFunction = Array.isArray(ease) ? ease[i] || noop : ease; 1421 mixer = pipe(easingFunction, mixer); 1422 } 1423 mixers.push(mixer); 1424 } 1425 return mixers; 1426 } 1427 /** 1428 * Create a function that maps from a numerical input array to a generic output array. 1429 * 1430 * Accepts: 1431 * - Numbers 1432 * - Colors (hex, hsl, hsla, rgb, rgba) 1433 * - Complex (combinations of one or more numbers or strings) 1434 * 1435 * ```jsx 1436 * const mixColor = interpolate([0, 1], ['#fff', '#000']) 1437 * 1438 * mixColor(0.5) // 'rgba(128, 128, 128, 1)' 1439 * ``` 1440 * 1441 * TODO Revisit this approach once we've moved to data models for values, 1442 * probably not needed to pregenerate mixer functions. 1443 * 1444 * @public 1445 */ 1446 function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) { 1447 const inputLength = input.length; 1448 exports.invariant(inputLength === output.length, "Both input and output ranges must be the same length", "range-length"); 1449 /** 1450 * If we're only provided a single input, we can just make a function 1451 * that returns the output. 1452 */ 1453 if (inputLength === 1) 1454 return () => output[0]; 1455 if (inputLength === 2 && output[0] === output[1]) 1456 return () => output[1]; 1457 const isZeroDeltaRange = input[0] === input[1]; 1458 // If input runs highest -> lowest, reverse both arrays 1459 if (input[0] > input[inputLength - 1]) { 1460 input = [...input].reverse(); 1461 output = [...output].reverse(); 1462 } 1463 const mixers = createMixers(output, ease, mixer); 1464 const numMixers = mixers.length; 1465 const interpolator = (v) => { 1466 if (isZeroDeltaRange && v < input[0]) 1467 return output[0]; 1468 let i = 0; 1469 if (numMixers > 1) { 1470 for (; i < input.length - 2; i++) { 1471 if (v < input[i + 1]) 1472 break; 1473 } 1474 } 1475 const progressInRange = progress(input[i], input[i + 1], v); 1476 return mixers[i](progressInRange); 1477 }; 1478 return isClamp 1479 ? (v) => interpolator(clamp(input[0], input[inputLength - 1], v)) 1480 : interpolator; 1481 } 1482 1483 function fillOffset(offset, remaining) { 1484 const min = offset[offset.length - 1]; 1485 for (let i = 1; i <= remaining; i++) { 1486 const offsetProgress = progress(0, remaining, i); 1487 offset.push(mixNumber$1(min, 1, offsetProgress)); 1488 } 1489 } 1490 1491 function defaultOffset$1(arr) { 1492 const offset = [0]; 1493 fillOffset(offset, arr.length - 1); 1494 return offset; 1495 } 1496 1497 function convertOffsetToTimes(offset, duration) { 1498 return offset.map((o) => o * duration); 1499 } 1500 1501 function defaultEasing(values, easing) { 1502 return values.map(() => easing || easeInOut).splice(0, values.length - 1); 1503 } 1504 function keyframes({ duration = 300, keyframes: keyframeValues, times, ease = "easeInOut", }) { 1505 /** 1506 * Easing functions can be externally defined as strings. Here we convert them 1507 * into actual functions. 1508 */ 1509 const easingFunctions = isEasingArray(ease) 1510 ? ease.map(easingDefinitionToFunction) 1511 : easingDefinitionToFunction(ease); 1512 /** 1513 * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator 1514 * to reduce GC during animation. 1515 */ 1516 const state = { 1517 done: false, 1518 value: keyframeValues[0], 1519 }; 1520 /** 1521 * Create a times array based on the provided 0-1 offsets 1522 */ 1523 const absoluteTimes = convertOffsetToTimes( 1524 // Only use the provided offsets if they're the correct length 1525 // TODO Maybe we should warn here if there's a length mismatch 1526 times && times.length === keyframeValues.length 1527 ? times 1528 : defaultOffset$1(keyframeValues), duration); 1529 const mapTimeToKeyframe = interpolate(absoluteTimes, keyframeValues, { 1530 ease: Array.isArray(easingFunctions) 1531 ? easingFunctions 1532 : defaultEasing(keyframeValues, easingFunctions), 1533 }); 1534 return { 1535 calculatedDuration: duration, 1536 next: (t) => { 1537 state.value = mapTimeToKeyframe(t); 1538 state.done = t >= duration; 1539 return state; 1540 }, 1541 }; 1542 } 1543 1544 const isNotNull$1 = (value) => value !== null; 1545 function getFinalKeyframe$1(keyframes, { repeat, repeatType = "loop" }, finalKeyframe, speed = 1) { 1546 const resolvedKeyframes = keyframes.filter(isNotNull$1); 1547 const useFirstKeyframe = speed < 0 || (repeat && repeatType !== "loop" && repeat % 2 === 1); 1548 const index = useFirstKeyframe ? 0 : resolvedKeyframes.length - 1; 1549 return !index || finalKeyframe === undefined 1550 ? resolvedKeyframes[index] 1551 : finalKeyframe; 1552 } 1553 1554 const transitionTypeMap = { 1555 decay: inertia, 1556 inertia, 1557 tween: keyframes, 1558 keyframes: keyframes, 1559 spring, 1560 }; 1561 function replaceTransitionType(transition) { 1562 if (typeof transition.type === "string") { 1563 transition.type = transitionTypeMap[transition.type]; 1564 } 1565 } 1566 1567 class WithPromise { 1568 constructor() { 1569 this.updateFinished(); 1570 } 1571 get finished() { 1572 return this._finished; 1573 } 1574 updateFinished() { 1575 this._finished = new Promise((resolve) => { 1576 this.resolve = resolve; 1577 }); 1578 } 1579 notifyFinished() { 1580 this.resolve(); 1581 } 1582 /** 1583 * Allows the animation to be awaited. 1584 * 1585 * @deprecated Use `finished` instead. 1586 */ 1587 then(onResolve, onReject) { 1588 return this.finished.then(onResolve, onReject); 1589 } 1590 } 1591 1592 const percentToProgress = (percent) => percent / 100; 1593 class JSAnimation extends WithPromise { 1594 constructor(options) { 1595 super(); 1596 this.state = "idle"; 1597 this.startTime = null; 1598 this.isStopped = false;
vendor: 4,373 bytes, lines 1599-1692
1599 /** 1600 * The current time of the animation. 1601 */ 1602 this.currentTime = 0; 1603 /** 1604 * The time at which the animation was paused. 1605 */ 1606 this.holdTime = null; 1607 /** 1608 * Playback speed as a factor. 0 would be stopped, -1 reverse and 2 double speed. 1609 */ 1610 this.playbackSpeed = 1; 1611 /** 1612 * This method is bound to the instance to fix a pattern where 1613 * animation.stop is returned as a reference from a useEffect. 1614 */ 1615 this.stop = () => { 1616 const { motionValue } = this.options; 1617 if (motionValue && motionValue.updatedAt !== time.now()) { 1618 this.tick(time.now()); 1619 } 1620 this.isStopped = true; 1621 if (this.state === "idle") 1622 return; 1623 this.teardown(); 1624 this.options.onStop?.(); 1625 }; 1626 activeAnimations.mainThread++; 1627 this.options = options; 1628 this.initAnimation(); 1629 this.play(); 1630 if (options.autoplay === false) 1631 this.pause(); 1632 } 1633 initAnimation() { 1634 const { options } = this; 1635 replaceTransitionType(options); 1636 const { type = keyframes, repeat = 0, repeatDelay = 0, repeatType, velocity = 0, } = options; 1637 let { keyframes: keyframes$1 } = options; 1638 const generatorFactory = type || keyframes; 1639 if (generatorFactory !== keyframes) { 1640 exports.invariant(keyframes$1.length <= 2, `Only two keyframes currently supported with spring and inertia animations. Trying to animate ${keyframes$1}`, "spring-two-frames"); 1641 } 1642 if (generatorFactory !== keyframes && 1643 typeof keyframes$1[0] !== "number") { 1644 this.mixKeyframes = pipe(percentToProgress, mix(keyframes$1[0], keyframes$1[1])); 1645 keyframes$1 = [0, 100]; 1646 } 1647 const generator = generatorFactory({ ...options, keyframes: keyframes$1 }); 1648 /** 1649 * If we have a mirror repeat type we need to create a second generator that outputs the 1650 * mirrored (not reversed) animation and later ping pong between the two generators. 1651 */ 1652 if (repeatType === "mirror") { 1653 this.mirroredGenerator = generatorFactory({ 1654 ...options, 1655 keyframes: [...keyframes$1].reverse(), 1656 velocity: -velocity, 1657 }); 1658 } 1659 /** 1660 * If duration is undefined and we have repeat options, 1661 * we need to calculate a duration from the generator. 1662 * 1663 * We set it to the generator itself to cache the duration. 1664 * Any timeline resolver will need to have already precalculated 1665 * the duration by this step. 1666 */ 1667 if (generator.calculatedDuration === null) { 1668 generator.calculatedDuration = calcGeneratorDuration(generator); 1669 } 1670 const { calculatedDuration } = generator; 1671 this.calculatedDuration = calculatedDuration; 1672 this.resolvedDuration = calculatedDuration + repeatDelay; 1673 this.totalDuration = this.resolvedDuration * (repeat + 1) - repeatDelay; 1674 this.generator = generator; 1675 } 1676 updateTime(timestamp) { 1677 const animationTime = Math.round(timestamp - this.startTime) * this.playbackSpeed; 1678 // Update currentTime 1679 if (this.holdTime !== null) { 1680 this.currentTime = this.holdTime; 1681 } 1682 else { 1683 // Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 = 1684 // 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for 1685 // example. 1686 this.currentTime = animationTime; 1687 } 1688 } 1689 tick(timestamp, sample = false) { 1690 const { generator, totalDuration, mixKeyframes, mirroredGenerator, resolvedDuration, calculatedDuration, } = this; 1691 if (this.startTime === null) 1692 return generator.next(0);
vendor: 20,678 bytes, lines 1693-2242
1693 const { delay = 0, keyframes, repeat, repeatType, repeatDelay, type, onUpdate, finalKeyframe, } = this.options; 1694 /** 1695 * requestAnimationFrame timestamps can come through as lower than 1696 * the startTime as set by performance.now(). Here we prevent this, 1697 * though in the future it could be possible to make setting startTime 1698 * a pending operation that gets resolved here. 1699 */ 1700 if (this.speed > 0) { 1701 this.startTime = Math.min(this.startTime, timestamp); 1702 } 1703 else if (this.speed < 0) { 1704 this.startTime = Math.min(timestamp - totalDuration / this.speed, this.startTime); 1705 } 1706 if (sample) { 1707 this.currentTime = timestamp; 1708 } 1709 else { 1710 this.updateTime(timestamp); 1711 } 1712 // Rebase on delay 1713 const timeWithoutDelay = this.currentTime - delay * (this.playbackSpeed >= 0 ? 1 : -1); 1714 const isInDelayPhase = this.playbackSpeed >= 0 1715 ? timeWithoutDelay < 0 1716 : timeWithoutDelay > totalDuration; 1717 this.currentTime = Math.max(timeWithoutDelay, 0); 1718 // If this animation has finished, set the current time to the total duration. 1719 if (this.state === "finished" && this.holdTime === null) { 1720 this.currentTime = totalDuration; 1721 } 1722 let elapsed = this.currentTime; 1723 let frameGenerator = generator; 1724 if (repeat) { 1725 /** 1726 * Get the current progress (0-1) of the animation. If t is > 1727 * than duration we'll get values like 2.5 (midway through the 1728 * third iteration) 1729 */ 1730 const progress = Math.min(this.currentTime, totalDuration) / resolvedDuration; 1731 /** 1732 * Get the current iteration (0 indexed). For instance the floor of 1733 * 2.5 is 2. 1734 */ 1735 let currentIteration = Math.floor(progress); 1736 /** 1737 * Get the current progress of the iteration by taking the remainder 1738 * so 2.5 is 0.5 through iteration 2 1739 */ 1740 let iterationProgress = progress % 1.0; 1741 /** 1742 * If iteration progress is 1 we count that as the end 1743 * of the previous iteration. 1744 */ 1745 if (!iterationProgress && progress >= 1) { 1746 iterationProgress = 1; 1747 } 1748 iterationProgress === 1 && currentIteration--; 1749 currentIteration = Math.min(currentIteration, repeat + 1); 1750 /** 1751 * Reverse progress if we're not running in "normal" direction 1752 */ 1753 const isOddIteration = Boolean(currentIteration % 2); 1754 if (isOddIteration) { 1755 if (repeatType === "reverse") { 1756 iterationProgress = 1 - iterationProgress; 1757 if (repeatDelay) { 1758 iterationProgress -= repeatDelay / resolvedDuration; 1759 } 1760 } 1761 else if (repeatType === "mirror") { 1762 frameGenerator = mirroredGenerator; 1763 } 1764 } 1765 elapsed = clamp(0, 1, iterationProgress) * resolvedDuration; 1766 } 1767 /** 1768 * If we're in negative time, set state as the initial keyframe. 1769 * This prevents delay: x, duration: 0 animations from finishing 1770 * instantly. 1771 */ 1772 const state = isInDelayPhase 1773 ? { done: false, value: keyframes[0] } 1774 : frameGenerator.next(elapsed); 1775 if (mixKeyframes) { 1776 state.value = mixKeyframes(state.value); 1777 } 1778 let { done } = state; 1779 if (!isInDelayPhase && calculatedDuration !== null) { 1780 done = 1781 this.playbackSpeed >= 0 1782 ? this.currentTime >= totalDuration 1783 : this.currentTime <= 0; 1784 } 1785 const isAnimationFinished = this.holdTime === null && 1786 (this.state === "finished" || (this.state === "running" && done)); 1787 // TODO: The exception for inertia could be cleaner here 1788 if (isAnimationFinished && type !== inertia) { 1789 state.value = getFinalKeyframe$1(keyframes, this.options, finalKeyframe, this.speed); 1790 } 1791 if (onUpdate) { 1792 onUpdate(state.value); 1793 } 1794 if (isAnimationFinished) { 1795 this.finish(); 1796 } 1797 return state; 1798 } 1799 /** 1800 * Allows the returned animation to be awaited or promise-chained. Currently 1801 * resolves when the animation finishes at all but in a future update could/should 1802 * reject if its cancels. 1803 */ 1804 then(resolve, reject) { 1805 return this.finished.then(resolve, reject); 1806 } 1807 get duration() { 1808 return millisecondsToSeconds(this.calculatedDuration); 1809 } 1810 get iterationDuration() { 1811 const { delay = 0 } = this.options || {}; 1812 return this.duration + millisecondsToSeconds(delay); 1813 } 1814 get time() { 1815 return millisecondsToSeconds(this.currentTime); 1816 } 1817 set time(newTime) { 1818 newTime = secondsToMilliseconds(newTime); 1819 this.currentTime = newTime; 1820 if (this.startTime === null || 1821 this.holdTime !== null || 1822 this.playbackSpeed === 0) { 1823 this.holdTime = newTime; 1824 } 1825 else if (this.driver) { 1826 this.startTime = this.driver.now() - newTime / this.playbackSpeed; 1827 } 1828 this.driver?.start(false); 1829 } 1830 get speed() { 1831 return this.playbackSpeed; 1832 } 1833 set speed(newSpeed) { 1834 this.updateTime(time.now()); 1835 const hasChanged = this.playbackSpeed !== newSpeed; 1836 this.playbackSpeed = newSpeed; 1837 if (hasChanged) { 1838 this.time = millisecondsToSeconds(this.currentTime); 1839 } 1840 } 1841 play() { 1842 if (this.isStopped) 1843 return; 1844 const { driver = frameloopDriver, startTime } = this.options; 1845 if (!this.driver) { 1846 this.driver = driver((timestamp) => this.tick(timestamp)); 1847 } 1848 this.options.onPlay?.(); 1849 const now = this.driver.now(); 1850 if (this.state === "finished") { 1851 this.updateFinished(); 1852 this.startTime = now; 1853 } 1854 else if (this.holdTime !== null) { 1855 this.startTime = now - this.holdTime; 1856 } 1857 else if (!this.startTime) { 1858 this.startTime = startTime ?? now; 1859 } 1860 if (this.state === "finished" && this.speed < 0) { 1861 this.startTime += this.calculatedDuration; 1862 } 1863 this.holdTime = null; 1864 /** 1865 * Set playState to running only after we've used it in 1866 * the previous logic. 1867 */ 1868 this.state = "running"; 1869 this.driver.start(); 1870 } 1871 pause() { 1872 this.state = "paused"; 1873 this.updateTime(time.now()); 1874 this.holdTime = this.currentTime; 1875 } 1876 complete() { 1877 if (this.state !== "running") { 1878 this.play(); 1879 } 1880 this.state = "finished"; 1881 this.holdTime = null; 1882 } 1883 finish() { 1884 this.notifyFinished(); 1885 this.teardown(); 1886 this.state = "finished"; 1887 this.options.onComplete?.(); 1888 } 1889 cancel() { 1890 this.holdTime = null; 1891 this.startTime = 0; 1892 this.tick(0); 1893 this.teardown(); 1894 this.options.onCancel?.(); 1895 } 1896 teardown() { 1897 this.state = "idle"; 1898 this.stopDriver(); 1899 this.startTime = this.holdTime = null; 1900 activeAnimations.mainThread--; 1901 } 1902 stopDriver() { 1903 if (!this.driver) 1904 return; 1905 this.driver.stop(); 1906 this.driver = undefined; 1907 } 1908 sample(sampleTime) { 1909 this.startTime = 0; 1910 return this.tick(sampleTime, true); 1911 } 1912 attachTimeline(timeline) { 1913 if (this.options.allowFlatten) { 1914 this.options.type = "keyframes"; 1915 this.options.ease = "linear"; 1916 this.initAnimation(); 1917 } 1918 this.driver?.stop(); 1919 return timeline.observe(this); 1920 } 1921 } 1922 // Legacy function support 1923 function animateValue(options) { 1924 return new JSAnimation(options); 1925 } 1926 1927 function fillWildcards(keyframes) { 1928 for (let i = 1; i < keyframes.length; i++) { 1929 keyframes[i] ?? (keyframes[i] = keyframes[i - 1]); 1930 } 1931 } 1932 1933 const radToDeg = (rad) => (rad * 180) / Math.PI; 1934 const rotate = (v) => { 1935 const angle = radToDeg(Math.atan2(v[1], v[0])); 1936 return rebaseAngle(angle); 1937 }; 1938 const matrix2dParsers = { 1939 x: 4, 1940 y: 5, 1941 translateX: 4, 1942 translateY: 5, 1943 scaleX: 0, 1944 scaleY: 3, 1945 scale: (v) => (Math.abs(v[0]) + Math.abs(v[3])) / 2, 1946 rotate, 1947 rotateZ: rotate, 1948 skewX: (v) => radToDeg(Math.atan(v[1])), 1949 skewY: (v) => radToDeg(Math.atan(v[2])), 1950 skew: (v) => (Math.abs(v[1]) + Math.abs(v[2])) / 2, 1951 }; 1952 const rebaseAngle = (angle) => { 1953 angle = angle % 360; 1954 if (angle < 0) 1955 angle += 360; 1956 return angle; 1957 }; 1958 const rotateZ = rotate; 1959 const scaleX = (v) => Math.sqrt(v[0] * v[0] + v[1] * v[1]); 1960 const scaleY = (v) => Math.sqrt(v[4] * v[4] + v[5] * v[5]); 1961 const matrix3dParsers = { 1962 x: 12, 1963 y: 13, 1964 z: 14, 1965 translateX: 12, 1966 translateY: 13, 1967 translateZ: 14, 1968 scaleX, 1969 scaleY, 1970 scale: (v) => (scaleX(v) + scaleY(v)) / 2, 1971 rotateX: (v) => rebaseAngle(radToDeg(Math.atan2(v[6], v[5]))), 1972 rotateY: (v) => rebaseAngle(radToDeg(Math.atan2(-v[2], v[0]))), 1973 rotateZ, 1974 rotate: rotateZ, 1975 skewX: (v) => radToDeg(Math.atan(v[4])), 1976 skewY: (v) => radToDeg(Math.atan(v[1])), 1977 skew: (v) => (Math.abs(v[1]) + Math.abs(v[4])) / 2, 1978 }; 1979 function defaultTransformValue(name) { 1980 return name.includes("scale") ? 1 : 0; 1981 } 1982 function parseValueFromTransform(transform, name) { 1983 if (!transform || transform === "none") { 1984 return defaultTransformValue(name); 1985 } 1986 const matrix3dMatch = transform.match(/^matrix3d\(([-\d.e\s,]+)\)$/u); 1987 let parsers; 1988 let match; 1989 if (matrix3dMatch) { 1990 parsers = matrix3dParsers; 1991 match = matrix3dMatch; 1992 } 1993 else { 1994 const matrix2dMatch = transform.match(/^matrix\(([-\d.e\s,]+)\)$/u); 1995 parsers = matrix2dParsers; 1996 match = matrix2dMatch; 1997 } 1998 if (!match) { 1999 return defaultTransformValue(name); 2000 } 2001 const valueParser = parsers[name]; 2002 const values = match[1].split(",").map(convertTransformToNumber); 2003 return typeof valueParser === "function" 2004 ? valueParser(values) 2005 : values[valueParser]; 2006 } 2007 const readTransformValue = (instance, name) => { 2008 const { transform = "none" } = getComputedStyle(instance); 2009 return parseValueFromTransform(transform, name); 2010 }; 2011 function convertTransformToNumber(value) { 2012 return parseFloat(value.trim()); 2013 } 2014 2015 /** 2016 * Generate a list of every possible transform key. 2017 */ 2018 const transformPropOrder = [ 2019 "transformPerspective", 2020 "x", 2021 "y", 2022 "z", 2023 "translateX", 2024 "translateY", 2025 "translateZ", 2026 "scale", 2027 "scaleX", 2028 "scaleY", 2029 "rotate", 2030 "rotateX", 2031 "rotateY", 2032 "rotateZ", 2033 "skew", 2034 "skewX", 2035 "skewY", 2036 ]; 2037 /** 2038 * A quick lookup for transform props. 2039 */ 2040 const transformProps = /*@__PURE__*/ (() => new Set(transformPropOrder))(); 2041 2042 const isNumOrPxType = (v) => v === number || v === px; 2043 const transformKeys = new Set(["x", "y", "z"]); 2044 const nonTranslationalTransformKeys = transformPropOrder.filter((key) => !transformKeys.has(key)); 2045 function removeNonTranslationalTransform(visualElement) { 2046 const removedTransforms = []; 2047 nonTranslationalTransformKeys.forEach((key) => { 2048 const value = visualElement.getValue(key); 2049 if (value !== undefined) { 2050 removedTransforms.push([key, value.get()]); 2051 value.set(key.startsWith("scale") ? 1 : 0); 2052 } 2053 }); 2054 return removedTransforms; 2055 } 2056 const positionalValues = { 2057 // Dimensions 2058 width: ({ x }, { paddingLeft = "0", paddingRight = "0" }) => x.max - x.min - parseFloat(paddingLeft) - parseFloat(paddingRight), 2059 height: ({ y }, { paddingTop = "0", paddingBottom = "0" }) => y.max - y.min - parseFloat(paddingTop) - parseFloat(paddingBottom), 2060 top: (_bbox, { top }) => parseFloat(top), 2061 left: (_bbox, { left }) => parseFloat(left), 2062 bottom: ({ y }, { top }) => parseFloat(top) + (y.max - y.min), 2063 right: ({ x }, { left }) => parseFloat(left) + (x.max - x.min), 2064 // Transform 2065 x: (_bbox, { transform }) => parseValueFromTransform(transform, "x"), 2066 y: (_bbox, { transform }) => parseValueFromTransform(transform, "y"), 2067 }; 2068 // Alias translate longform names 2069 positionalValues.translateX = positionalValues.x; 2070 positionalValues.translateY = positionalValues.y; 2071 2072 const toResolve = new Set(); 2073 let isScheduled = false; 2074 let anyNeedsMeasurement = false; 2075 let isForced = false; 2076 function measureAllKeyframes() { 2077 if (anyNeedsMeasurement) { 2078 const resolversToMeasure = Array.from(toResolve).filter((resolver) => resolver.needsMeasurement); 2079 const elementsToMeasure = new Set(resolversToMeasure.map((resolver) => resolver.element)); 2080 const transformsToRestore = new Map(); 2081 /** 2082 * Write pass 2083 * If we're measuring elements we want to remove bounding box-changing transforms. 2084 */ 2085 elementsToMeasure.forEach((element) => { 2086 const removedTransforms = removeNonTranslationalTransform(element); 2087 if (!removedTransforms.length) 2088 return; 2089 transformsToRestore.set(element, removedTransforms); 2090 element.render(); 2091 }); 2092 // Read 2093 resolversToMeasure.forEach((resolver) => resolver.measureInitialState()); 2094 // Write 2095 elementsToMeasure.forEach((element) => { 2096 element.render(); 2097 const restore = transformsToRestore.get(element); 2098 if (restore) { 2099 restore.forEach(([key, value]) => { 2100 element.getValue(key)?.set(value); 2101 }); 2102 } 2103 }); 2104 // Read 2105 resolversToMeasure.forEach((resolver) => resolver.measureEndState()); 2106 // Write 2107 resolversToMeasure.forEach((resolver) => { 2108 if (resolver.suspendedScrollY !== undefined) { 2109 window.scrollTo(0, resolver.suspendedScrollY); 2110 } 2111 }); 2112 } 2113 anyNeedsMeasurement = false; 2114 isScheduled = false; 2115 toResolve.forEach((resolver) => resolver.complete(isForced)); 2116 toResolve.clear(); 2117 } 2118 function readAllKeyframes() { 2119 toResolve.forEach((resolver) => { 2120 resolver.readKeyframes(); 2121 if (resolver.needsMeasurement) { 2122 anyNeedsMeasurement = true; 2123 } 2124 }); 2125 } 2126 function flushKeyframeResolvers() { 2127 isForced = true; 2128 readAllKeyframes(); 2129 measureAllKeyframes(); 2130 isForced = false; 2131 } 2132 class KeyframeResolver { 2133 constructor(unresolvedKeyframes, onComplete, name, motionValue, element, isAsync = false) { 2134 this.state = "pending"; 2135 /** 2136 * Track whether this resolver is async. If it is, it'll be added to the 2137 * resolver queue and flushed in the next frame. Resolvers that aren't going 2138 * to trigger read/write thrashing don't need to be async. 2139 */ 2140 this.isAsync = false; 2141 /** 2142 * Track whether this resolver needs to perform a measurement 2143 * to resolve its keyframes. 2144 */ 2145 this.needsMeasurement = false; 2146 this.unresolvedKeyframes = [...unresolvedKeyframes]; 2147 this.onComplete = onComplete; 2148 this.name = name; 2149 this.motionValue = motionValue; 2150 this.element = element; 2151 this.isAsync = isAsync; 2152 } 2153 scheduleResolve() { 2154 this.state = "scheduled"; 2155 if (this.isAsync) { 2156 toResolve.add(this); 2157 if (!isScheduled) { 2158 isScheduled = true; 2159 frame.read(readAllKeyframes); 2160 frame.resolveKeyframes(measureAllKeyframes); 2161 } 2162 } 2163 else { 2164 this.readKeyframes(); 2165 this.complete(); 2166 } 2167 } 2168 readKeyframes() { 2169 const { unresolvedKeyframes, name, element, motionValue } = this; 2170 // If initial keyframe is null we need to read it from the DOM 2171 if (unresolvedKeyframes[0] === null) { 2172 const currentValue = motionValue?.get(); 2173 // TODO: This doesn't work if the final keyframe is a wildcard 2174 const finalKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1]; 2175 if (currentValue !== undefined) { 2176 unresolvedKeyframes[0] = currentValue; 2177 } 2178 else if (element && name) { 2179 const valueAsRead = element.readValue(name, finalKeyframe); 2180 if (valueAsRead !== undefined && valueAsRead !== null) { 2181 unresolvedKeyframes[0] = valueAsRead; 2182 } 2183 } 2184 if (unresolvedKeyframes[0] === undefined) { 2185 unresolvedKeyframes[0] = finalKeyframe; 2186 } 2187 if (motionValue && currentValue === undefined) { 2188 motionValue.set(unresolvedKeyframes[0]); 2189 } 2190 } 2191 fillWildcards(unresolvedKeyframes); 2192 } 2193 setFinalKeyframe() { } 2194 measureInitialState() { } 2195 renderEndStyles() { } 2196 measureEndState() { } 2197 complete(isForcedComplete = false) { 2198 this.state = "complete"; 2199 this.onComplete(this.unresolvedKeyframes, this.finalKeyframe, isForcedComplete); 2200 toResolve.delete(this); 2201 } 2202 cancel() { 2203 if (this.state === "scheduled") { 2204 toResolve.delete(this); 2205 this.state = "pending"; 2206 } 2207 } 2208 resume() { 2209 if (this.state === "pending") 2210 this.scheduleResolve(); 2211 } 2212 } 2213 2214 const isCSSVar = (name) => name.startsWith("--"); 2215 2216 function setStyle(element, name, value) { 2217 isCSSVar(name) 2218 ? element.style.setProperty(name, value) 2219 : (element.style[name] = value); 2220 } 2221 2222 const supportsScrollTimeline = /* @__PURE__ */ memo(() => window.ScrollTimeline !== undefined); 2223 2224 /** 2225 * Add the ability for test suites to manually set support flags 2226 * to better test more environments. 2227 */ 2228 const supportsFlags = {}; 2229 2230 function memoSupports(callback, supportsFlag) { 2231 const memoized = memo(callback); 2232 return () => supportsFlags[supportsFlag] ?? memoized(); 2233 } 2234 2235 const supportsLinearEasing = /*@__PURE__*/ memoSupports(() => { 2236 try { 2237 document 2238 .createElement("div") 2239 .animate({ opacity: 0 }, { easing: "linear(0, 1)" }); 2240 } 2241 catch (e) { 2242 return false;
vendor: 4,186 bytes, lines 2243-2353
2243 } 2244 return true; 2245 }, "linearEasing"); 2246 2247 const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`; 2248 2249 const supportedWaapiEasing = { 2250 linear: "linear", 2251 ease: "ease", 2252 easeIn: "ease-in", 2253 easeOut: "ease-out", 2254 easeInOut: "ease-in-out", 2255 circIn: /*@__PURE__*/ cubicBezierAsString([0, 0.65, 0.55, 1]), 2256 circOut: /*@__PURE__*/ cubicBezierAsString([0.55, 0, 1, 0.45]), 2257 backIn: /*@__PURE__*/ cubicBezierAsString([0.31, 0.01, 0.66, -0.59]), 2258 backOut: /*@__PURE__*/ cubicBezierAsString([0.33, 1.53, 0.69, 0.99]), 2259 }; 2260 2261 function mapEasingToNativeEasing(easing, duration) { 2262 if (!easing) { 2263 return undefined; 2264 } 2265 else if (typeof easing === "function") { 2266 return supportsLinearEasing() 2267 ? generateLinearEasing(easing, duration) 2268 : "ease-out"; 2269 } 2270 else if (isBezierDefinition(easing)) { 2271 return cubicBezierAsString(easing); 2272 } 2273 else if (Array.isArray(easing)) { 2274 return easing.map((segmentEasing) => mapEasingToNativeEasing(segmentEasing, duration) || 2275 supportedWaapiEasing.easeOut); 2276 } 2277 else { 2278 return supportedWaapiEasing[easing]; 2279 } 2280 } 2281 2282 function startWaapiAnimation(element, valueName, keyframes, { delay = 0, duration = 300, repeat = 0, repeatType = "loop", ease = "easeOut", times, } = {}, pseudoElement = undefined) { 2283 const keyframeOptions = { 2284 [valueName]: keyframes, 2285 }; 2286 if (times) 2287 keyframeOptions.offset = times; 2288 const easing = mapEasingToNativeEasing(ease, duration); 2289 /** 2290 * If this is an easing array, apply to keyframes, not animation as a whole 2291 */ 2292 if (Array.isArray(easing)) 2293 keyframeOptions.easing = easing; 2294 if (statsBuffer.value) { 2295 activeAnimations.waapi++; 2296 } 2297 const options = { 2298 delay, 2299 duration, 2300 easing: !Array.isArray(easing) ? easing : "linear", 2301 fill: "both", 2302 iterations: repeat + 1, 2303 direction: repeatType === "reverse" ? "alternate" : "normal", 2304 }; 2305 if (pseudoElement) 2306 options.pseudoElement = pseudoElement; 2307 const animation = element.animate(keyframeOptions, options); 2308 if (statsBuffer.value) { 2309 animation.finished.finally(() => { 2310 activeAnimations.waapi--; 2311 }); 2312 } 2313 return animation; 2314 } 2315 2316 function isGenerator(type) { 2317 return typeof type === "function" && "applyToOptions" in type; 2318 } 2319 2320 function applyGeneratorOptions({ type, ...options }) { 2321 if (isGenerator(type) && supportsLinearEasing()) { 2322 return type.applyToOptions(options); 2323 } 2324 else { 2325 options.duration ?? (options.duration = 300); 2326 options.ease ?? (options.ease = "easeOut"); 2327 } 2328 return options; 2329 } 2330 2331 /** 2332 * NativeAnimation implements AnimationPlaybackControls for the browser's Web Animations API. 2333 */ 2334 class NativeAnimation extends WithPromise { 2335 constructor(options) { 2336 super(); 2337 this.finishedTime = null; 2338 this.isStopped = false; 2339 if (!options) 2340 return; 2341 const { element, name, keyframes, pseudoElement, allowFlatten = false, finalKeyframe, onComplete, } = options; 2342 this.isPseudoElement = Boolean(pseudoElement); 2343 this.allowFlatten = allowFlatten; 2344 this.options = options; 2345 exports.invariant(typeof options.type !== "string", `Mini animate() doesn't support "type" as a string.`, "mini-spring"); 2346 const transition = applyGeneratorOptions(options); 2347 this.animation = startWaapiAnimation(element, name, keyframes, transition, pseudoElement); 2348 if (transition.autoplay === false) { 2349 this.animation.pause(); 2350 } 2351 this.animation.onfinish = () => { 2352 this.finishedTime = this.time; 2353 if (!pseudoElement) {
vendor: 18,281 bytes, lines 2354-2833
2354 const keyframe = getFinalKeyframe$1(keyframes, this.options, finalKeyframe, this.speed); 2355 if (this.updateMotionValue) { 2356 this.updateMotionValue(keyframe); 2357 } 2358 else { 2359 /** 2360 * If we can, we want to commit the final style as set by the user, 2361 * rather than the computed keyframe value supplied by the animation. 2362 */ 2363 setStyle(element, name, keyframe); 2364 } 2365 this.animation.cancel(); 2366 } 2367 onComplete?.(); 2368 this.notifyFinished(); 2369 }; 2370 } 2371 play() { 2372 if (this.isStopped) 2373 return; 2374 this.animation.play(); 2375 if (this.state === "finished") { 2376 this.updateFinished(); 2377 } 2378 } 2379 pause() { 2380 this.animation.pause(); 2381 } 2382 complete() { 2383 this.animation.finish?.(); 2384 } 2385 cancel() { 2386 try { 2387 this.animation.cancel(); 2388 } 2389 catch (e) { } 2390 } 2391 stop() { 2392 if (this.isStopped) 2393 return; 2394 this.isStopped = true; 2395 const { state } = this; 2396 if (state === "idle" || state === "finished") { 2397 return; 2398 } 2399 if (this.updateMotionValue) { 2400 this.updateMotionValue(); 2401 } 2402 else { 2403 this.commitStyles(); 2404 } 2405 if (!this.isPseudoElement) 2406 this.cancel(); 2407 } 2408 /** 2409 * WAAPI doesn't natively have any interruption capabilities. 2410 * 2411 * In this method, we commit styles back to the DOM before cancelling 2412 * the animation. 2413 * 2414 * This is designed to be overridden by NativeAnimationExtended, which 2415 * will create a renderless JS animation and sample it twice to calculate 2416 * its current value, "previous" value, and therefore allow 2417 * Motion to also correctly calculate velocity for any subsequent animation 2418 * while deferring the commit until the next animation frame. 2419 */ 2420 commitStyles() { 2421 if (!this.isPseudoElement) { 2422 this.animation.commitStyles?.(); 2423 } 2424 } 2425 get duration() { 2426 const duration = this.animation.effect?.getComputedTiming?.().duration || 0; 2427 return millisecondsToSeconds(Number(duration)); 2428 } 2429 get iterationDuration() { 2430 const { delay = 0 } = this.options || {}; 2431 return this.duration + millisecondsToSeconds(delay); 2432 } 2433 get time() { 2434 return millisecondsToSeconds(Number(this.animation.currentTime) || 0); 2435 } 2436 set time(newTime) { 2437 this.finishedTime = null; 2438 this.animation.currentTime = secondsToMilliseconds(newTime); 2439 } 2440 /** 2441 * The playback speed of the animation. 2442 * 1 = normal speed, 2 = double speed, 0.5 = half speed. 2443 */ 2444 get speed() { 2445 return this.animation.playbackRate; 2446 } 2447 set speed(newSpeed) { 2448 // Allow backwards playback after finishing 2449 if (newSpeed < 0) 2450 this.finishedTime = null; 2451 this.animation.playbackRate = newSpeed; 2452 } 2453 get state() { 2454 return this.finishedTime !== null 2455 ? "finished" 2456 : this.animation.playState; 2457 } 2458 get startTime() { 2459 return Number(this.animation.startTime); 2460 } 2461 set startTime(newStartTime) { 2462 this.animation.startTime = newStartTime; 2463 } 2464 /** 2465 * Attaches a timeline to the animation, for instance the `ScrollTimeline`. 2466 */ 2467 attachTimeline({ timeline, observe }) { 2468 if (this.allowFlatten) { 2469 this.animation.effect?.updateTiming({ easing: "linear" }); 2470 } 2471 this.animation.onfinish = null; 2472 if (timeline && supportsScrollTimeline()) { 2473 this.animation.timeline = timeline; 2474 return noop; 2475 } 2476 else { 2477 return observe(this); 2478 } 2479 } 2480 } 2481 2482 const unsupportedEasingFunctions = { 2483 anticipate, 2484 backInOut, 2485 circInOut, 2486 }; 2487 function isUnsupportedEase(key) { 2488 return key in unsupportedEasingFunctions; 2489 } 2490 function replaceStringEasing(transition) { 2491 if (typeof transition.ease === "string" && 2492 isUnsupportedEase(transition.ease)) { 2493 transition.ease = unsupportedEasingFunctions[transition.ease]; 2494 } 2495 } 2496 2497 /** 2498 * 10ms is chosen here as it strikes a balance between smooth 2499 * results (more than one keyframe per frame at 60fps) and 2500 * keyframe quantity. 2501 */ 2502 const sampleDelta = 10; //ms 2503 class NativeAnimationExtended extends NativeAnimation { 2504 constructor(options) { 2505 /** 2506 * The base NativeAnimation function only supports a subset 2507 * of Motion easings, and WAAPI also only supports some 2508 * easing functions via string/cubic-bezier definitions. 2509 * 2510 * This function replaces those unsupported easing functions 2511 * with a JS easing function. This will later get compiled 2512 * to a linear() easing function. 2513 */ 2514 replaceStringEasing(options); 2515 /** 2516 * Ensure we replace the transition type with a generator function 2517 * before passing to WAAPI. 2518 * 2519 * TODO: Does this have a better home? It could be shared with 2520 * JSAnimation. 2521 */ 2522 replaceTransitionType(options); 2523 super(options); 2524 if (options.startTime) { 2525 this.startTime = options.startTime; 2526 } 2527 this.options = options; 2528 } 2529 /** 2530 * WAAPI doesn't natively have any interruption capabilities. 2531 * 2532 * Rather than read commited styles back out of the DOM, we can 2533 * create a renderless JS animation and sample it twice to calculate 2534 * its current value, "previous" value, and therefore allow 2535 * Motion to calculate velocity for any subsequent animation. 2536 */ 2537 updateMotionValue(value) { 2538 const { motionValue, onUpdate, onComplete, element, ...options } = this.options; 2539 if (!motionValue) 2540 return; 2541 if (value !== undefined) { 2542 motionValue.set(value); 2543 return; 2544 } 2545 const sampleAnimation = new JSAnimation({ 2546 ...options, 2547 autoplay: false, 2548 }); 2549 const sampleTime = secondsToMilliseconds(this.finishedTime ?? this.time); 2550 motionValue.setWithVelocity(sampleAnimation.sample(sampleTime - sampleDelta).value, sampleAnimation.sample(sampleTime).value, sampleDelta); 2551 sampleAnimation.stop(); 2552 } 2553 } 2554 2555 /** 2556 * Check if a value is animatable. Examples: 2557 * 2558 * â : 100, "100px", "#fff" 2559 * â: "block", "url(2.jpg)" 2560 * @param value 2561 * 2562 * @internal 2563 */ 2564 const isAnimatable = (value, name) => { 2565 // If the list of keys that might be non-animatable grows, replace with Set 2566 if (name === "zIndex") 2567 return false; 2568 // If it's a number or a keyframes array, we can animate it. We might at some point 2569 // need to do a deep isAnimatable check of keyframes, or let Popmotion handle this, 2570 // but for now lets leave it like this for performance reasons 2571 if (typeof value === "number" || Array.isArray(value)) 2572 return true; 2573 if (typeof value === "string" && // It's animatable if we have a string 2574 (complex.test(value) || value === "0") && // And it contains numbers and/or colors 2575 !value.startsWith("url(") // Unless it starts with "url(" 2576 ) { 2577 return true; 2578 } 2579 return false; 2580 }; 2581 2582 function hasKeyframesChanged(keyframes) { 2583 const current = keyframes[0]; 2584 if (keyframes.length === 1) 2585 return true; 2586 for (let i = 0; i < keyframes.length; i++) { 2587 if (keyframes[i] !== current) 2588 return true; 2589 } 2590 } 2591 function canAnimate(keyframes, name, type, velocity) { 2592 /** 2593 * Check if we're able to animate between the start and end keyframes, 2594 * and throw a warning if we're attempting to animate between one that's 2595 * animatable and another that isn't. 2596 */ 2597 const originKeyframe = keyframes[0]; 2598 if (originKeyframe === null) 2599 return false; 2600 /** 2601 * These aren't traditionally animatable but we do support them. 2602 * In future we could look into making this more generic or replacing 2603 * this function with mix() === mixImmediate 2604 */ 2605 if (name === "display" || name === "visibility") 2606 return true; 2607 const targetKeyframe = keyframes[keyframes.length - 1]; 2608 const isOriginAnimatable = isAnimatable(originKeyframe, name); 2609 const isTargetAnimatable = isAnimatable(targetKeyframe, name); 2610 exports.warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${name} from "${originKeyframe}" to "${targetKeyframe}". "${isOriginAnimatable ? targetKeyframe : originKeyframe}" is not an animatable value.`, "value-not-animatable"); 2611 // Always skip if any of these are true 2612 if (!isOriginAnimatable || !isTargetAnimatable) { 2613 return false; 2614 } 2615 return (hasKeyframesChanged(keyframes) || 2616 ((type === "spring" || isGenerator(type)) && velocity)); 2617 } 2618 2619 function makeAnimationInstant(options) { 2620 options.duration = 0; 2621 options.type = "keyframes"; 2622 } 2623 2624 /** 2625 * A list of values that can be hardware-accelerated. 2626 */ 2627 const acceleratedValues$1 = new Set([ 2628 "opacity", 2629 "clipPath", 2630 "filter", 2631 "transform", 2632 // TODO: Could be re-enabled now we have support for linear() easing 2633 // "background-color" 2634 ]); 2635 const supportsWaapi = /*@__PURE__*/ memo(() => Object.hasOwnProperty.call(Element.prototype, "animate")); 2636 function supportsBrowserAnimation(options) { 2637 const { motionValue, name, repeatDelay, repeatType, damping, type } = options; 2638 const subject = motionValue?.owner?.current; 2639 /** 2640 * We use this check instead of isHTMLElement() because we explicitly 2641 * **don't** want elements in different timing contexts (i.e. popups) 2642 * to be accelerated, as it's not possible to sync these animations 2643 * properly with those driven from the main window frameloop. 2644 */ 2645 if (!(subject instanceof HTMLElement)) { 2646 return false; 2647 } 2648 const { onUpdate, transformTemplate } = motionValue.owner.getProps(); 2649 return (supportsWaapi() && 2650 name && 2651 acceleratedValues$1.has(name) && 2652 (name !== "transform" || !transformTemplate) && 2653 /** 2654 * If we're outputting values to onUpdate then we can't use WAAPI as there's 2655 * no way to read the value from WAAPI every frame. 2656 */ 2657 !onUpdate && 2658 !repeatDelay && 2659 repeatType !== "mirror" && 2660 damping !== 0 && 2661 type !== "inertia"); 2662 } 2663 2664 /** 2665 * Maximum time allowed between an animation being created and it being 2666 * resolved for us to use the latter as the start time. 2667 * 2668 * This is to ensure that while we prefer to "start" an animation as soon 2669 * as it's triggered, we also want to avoid a visual jump if there's a big delay 2670 * between these two moments. 2671 */ 2672 const MAX_RESOLVE_DELAY = 40; 2673 class AsyncMotionValueAnimation extends WithPromise { 2674 constructor({ autoplay = true, delay = 0, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", keyframes, name, motionValue, element, ...options }) { 2675 super(); 2676 /** 2677 * Bound to support return animation.stop pattern 2678 */ 2679 this.stop = () => { 2680 if (this._animation) { 2681 this._animation.stop(); 2682 this.stopTimeline?.(); 2683 } 2684 this.keyframeResolver?.cancel(); 2685 }; 2686 this.createdAt = time.now(); 2687 const optionsWithDefaults = { 2688 autoplay, 2689 delay, 2690 type, 2691 repeat, 2692 repeatDelay, 2693 repeatType, 2694 name, 2695 motionValue, 2696 element, 2697 ...options, 2698 }; 2699 const KeyframeResolver$1 = element?.KeyframeResolver || KeyframeResolver; 2700 this.keyframeResolver = new KeyframeResolver$1(keyframes, (resolvedKeyframes, finalKeyframe, forced) => this.onKeyframesResolved(resolvedKeyframes, finalKeyframe, optionsWithDefaults, !forced), name, motionValue, element); 2701 this.keyframeResolver?.scheduleResolve(); 2702 } 2703 onKeyframesResolved(keyframes, finalKeyframe, options, sync) { 2704 this.keyframeResolver = undefined; 2705 const { name, type, velocity, delay, isHandoff, onUpdate } = options; 2706 this.resolvedAt = time.now(); 2707 /** 2708 * If we can't animate this value with the resolved keyframes 2709 * then we should complete it immediately. 2710 */ 2711 if (!canAnimate(keyframes, name, type, velocity)) { 2712 if (MotionGlobalConfig.instantAnimations || !delay) { 2713 onUpdate?.(getFinalKeyframe$1(keyframes, options, finalKeyframe)); 2714 } 2715 keyframes[0] = keyframes[keyframes.length - 1]; 2716 makeAnimationInstant(options); 2717 options.repeat = 0; 2718 } 2719 /** 2720 * Resolve startTime for the animation. 2721 * 2722 * This method uses the createdAt and resolvedAt to calculate the 2723 * animation startTime. *Ideally*, we would use the createdAt time as t=0 2724 * as the following frame would then be the first frame of the animation in 2725 * progress, which would feel snappier. 2726 * 2727 * However, if there's a delay (main thread work) between the creation of 2728 * the animation and the first commited frame, we prefer to use resolvedAt 2729 * to avoid a sudden jump into the animation. 2730 */ 2731 const startTime = sync 2732 ? !this.resolvedAt 2733 ? this.createdAt 2734 : this.resolvedAt - this.createdAt > MAX_RESOLVE_DELAY 2735 ? this.resolvedAt 2736 : this.createdAt 2737 : undefined; 2738 const resolvedOptions = { 2739 startTime, 2740 finalKeyframe, 2741 ...options, 2742 keyframes, 2743 }; 2744 /** 2745 * Animate via WAAPI if possible. If this is a handoff animation, the optimised animation will be running via 2746 * WAAPI. Therefore, this animation must be JS to ensure it runs "under" the 2747 * optimised animation. 2748 */ 2749 const animation = !isHandoff && supportsBrowserAnimation(resolvedOptions) 2750 ? new NativeAnimationExtended({ 2751 ...resolvedOptions, 2752 element: resolvedOptions.motionValue.owner.current, 2753 }) 2754 : new JSAnimation(resolvedOptions); 2755 animation.finished.then(() => this.notifyFinished()).catch(noop); 2756 if (this.pendingTimeline) { 2757 this.stopTimeline = animation.attachTimeline(this.pendingTimeline); 2758 this.pendingTimeline = undefined; 2759 } 2760 this._animation = animation; 2761 } 2762 get finished() { 2763 if (!this._animation) { 2764 return this._finished; 2765 } 2766 else { 2767 return this.animation.finished; 2768 } 2769 } 2770 then(onResolve, _onReject) { 2771 return this.finished.finally(onResolve).then(() => { }); 2772 } 2773 get animation() { 2774 if (!this._animation) { 2775 this.keyframeResolver?.resume(); 2776 flushKeyframeResolvers(); 2777 } 2778 return this._animation; 2779 } 2780 get duration() { 2781 return this.animation.duration; 2782 } 2783 get iterationDuration() { 2784 return this.animation.iterationDuration; 2785 } 2786 get time() { 2787 return this.animation.time; 2788 } 2789 set time(newTime) { 2790 this.animation.time = newTime; 2791 } 2792 get speed() { 2793 return this.animation.speed; 2794 } 2795 get state() { 2796 return this.animation.state; 2797 } 2798 set speed(newSpeed) { 2799 this.animation.speed = newSpeed; 2800 } 2801 get startTime() { 2802 return this.animation.startTime; 2803 } 2804 attachTimeline(timeline) { 2805 if (this._animation) { 2806 this.stopTimeline = this.animation.attachTimeline(timeline); 2807 } 2808 else { 2809 this.pendingTimeline = timeline; 2810 } 2811 return () => this.stop(); 2812 } 2813 play() { 2814 this.animation.play(); 2815 } 2816 pause() { 2817 this.animation.pause(); 2818 } 2819 complete() { 2820 this.animation.complete(); 2821 } 2822 cancel() { 2823 if (this._animation) { 2824 this.animation.cancel(); 2825 } 2826 this.keyframeResolver?.cancel(); 2827 } 2828 } 2829 2830 class GroupAnimation { 2831 constructor(animations) { 2832 // Bound to accomadate common `return animation.stop` pattern 2833 this.stop = () => this.runAll("stop");
vendor: 10,157 bytes, lines 2834-3169
2834 this.animations = animations.filter(Boolean); 2835 } 2836 get finished() { 2837 return Promise.all(this.animations.map((animation) => animation.finished)); 2838 } 2839 /** 2840 * TODO: Filter out cancelled or stopped animations before returning 2841 */ 2842 getAll(propName) { 2843 return this.animations[0][propName]; 2844 } 2845 setAll(propName, newValue) { 2846 for (let i = 0; i < this.animations.length; i++) { 2847 this.animations[i][propName] = newValue; 2848 } 2849 } 2850 attachTimeline(timeline) { 2851 const subscriptions = this.animations.map((animation) => animation.attachTimeline(timeline)); 2852 return () => { 2853 subscriptions.forEach((cancel, i) => { 2854 cancel && cancel(); 2855 this.animations[i].stop(); 2856 }); 2857 }; 2858 } 2859 get time() { 2860 return this.getAll("time"); 2861 } 2862 set time(time) { 2863 this.setAll("time", time); 2864 } 2865 get speed() { 2866 return this.getAll("speed"); 2867 } 2868 set speed(speed) { 2869 this.setAll("speed", speed); 2870 } 2871 get state() { 2872 return this.getAll("state"); 2873 } 2874 get startTime() { 2875 return this.getAll("startTime"); 2876 } 2877 get duration() { 2878 return getMax(this.animations, "duration"); 2879 } 2880 get iterationDuration() { 2881 return getMax(this.animations, "iterationDuration"); 2882 } 2883 runAll(methodName) { 2884 this.animations.forEach((controls) => controls[methodName]()); 2885 } 2886 play() { 2887 this.runAll("play"); 2888 } 2889 pause() { 2890 this.runAll("pause"); 2891 } 2892 cancel() { 2893 this.runAll("cancel"); 2894 } 2895 complete() { 2896 this.runAll("complete"); 2897 } 2898 } 2899 function getMax(animations, propName) { 2900 let max = 0; 2901 for (let i = 0; i < animations.length; i++) { 2902 const value = animations[i][propName]; 2903 if (value !== null && value > max) { 2904 max = value; 2905 } 2906 } 2907 return max; 2908 } 2909 2910 class GroupAnimationWithThen extends GroupAnimation { 2911 then(onResolve, _onReject) { 2912 return this.finished.finally(onResolve).then(() => { }); 2913 } 2914 } 2915 2916 class NativeAnimationWrapper extends NativeAnimation { 2917 constructor(animation) { 2918 super(); 2919 this.animation = animation; 2920 animation.onfinish = () => { 2921 this.finishedTime = this.time; 2922 this.notifyFinished(); 2923 }; 2924 } 2925 } 2926 2927 const animationMaps = new WeakMap(); 2928 const animationMapKey = (name, pseudoElement = "") => `${name}:${pseudoElement}`; 2929 function getAnimationMap(element) { 2930 const map = animationMaps.get(element) || new Map(); 2931 animationMaps.set(element, map); 2932 return map; 2933 } 2934 2935 /** 2936 * Parse Framer's special CSS variable format into a CSS token and a fallback. 2937 * 2938 * ``` 2939 * `var(--foo, #fff)` => [`--foo`, '#fff'] 2940 * ``` 2941 * 2942 * @param current 2943 */ 2944 const splitCSSVariableRegex = 2945 // eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive, as it can match a lot of words 2946 /^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u; 2947 function parseCSSVariable(current) { 2948 const match = splitCSSVariableRegex.exec(current); 2949 if (!match) 2950 return [,]; 2951 const [, token1, token2, fallback] = match; 2952 return [`--${token1 ?? token2}`, fallback]; 2953 } 2954 const maxDepth = 4; 2955 function getVariableValue(current, element, depth = 1) { 2956 exports.invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`, "max-css-var-depth"); 2957 const [token, fallback] = parseCSSVariable(current); 2958 // No CSS variable detected 2959 if (!token) 2960 return; 2961 // Attempt to read this CSS variable off the element 2962 const resolved = window.getComputedStyle(element).getPropertyValue(token); 2963 if (resolved) { 2964 const trimmed = resolved.trim(); 2965 return isNumericalString(trimmed) ? parseFloat(trimmed) : trimmed; 2966 } 2967 return isCSSVariableToken(fallback) 2968 ? getVariableValue(fallback, element, depth + 1) 2969 : fallback; 2970 } 2971 2972 function getValueTransition$1(transition, key) { 2973 return (transition?.[key] ?? 2974 transition?.["default"] ?? 2975 transition); 2976 } 2977 2978 const positionalKeys = new Set([ 2979 "width", 2980 "height", 2981 "top", 2982 "left", 2983 "right", 2984 "bottom", 2985 ...transformPropOrder, 2986 ]); 2987 2988 /** 2989 * ValueType for "auto" 2990 */ 2991 const auto = { 2992 test: (v) => v === "auto", 2993 parse: (v) => v, 2994 }; 2995 2996 /** 2997 * Tests a provided value against a ValueType 2998 */ 2999 const testValueType = (v) => (type) => type.test(v); 3000 3001 /** 3002 * A list of value types commonly used for dimensions 3003 */ 3004 const dimensionValueTypes = [number, px, percent, degrees, vw, vh, auto]; 3005 /** 3006 * Tests a dimensional value against the list of dimension ValueTypes 3007 */ 3008 const findDimensionValueType = (v) => dimensionValueTypes.find(testValueType(v)); 3009 3010 function isNone(value) { 3011 if (typeof value === "number") { 3012 return value === 0; 3013 } 3014 else if (value !== null) { 3015 return value === "none" || value === "0" || isZeroValueString(value); 3016 } 3017 else { 3018 return true; 3019 } 3020 } 3021 3022 /** 3023 * Properties that should default to 1 or 100% 3024 */ 3025 const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]); 3026 function applyDefaultFilter(v) { 3027 const [name, value] = v.slice(0, -1).split("("); 3028 if (name === "drop-shadow") 3029 return v; 3030 const [number] = value.match(floatRegex) || []; 3031 if (!number) 3032 return v; 3033 const unit = value.replace(number, ""); 3034 let defaultValue = maxDefaults.has(name) ? 1 : 0; 3035 if (number !== value) 3036 defaultValue *= 100; 3037 return name + "(" + defaultValue + unit + ")"; 3038 } 3039 const functionRegex = /\b([a-z-]*)\(.*?\)/gu; 3040 const filter = { 3041 ...complex, 3042 getAnimatableNone: (v) => { 3043 const functions = v.match(functionRegex); 3044 return functions ? functions.map(applyDefaultFilter).join(" ") : v; 3045 }, 3046 }; 3047 3048 const int = { 3049 ...number, 3050 transform: Math.round, 3051 }; 3052 3053 const transformValueTypes = { 3054 rotate: degrees, 3055 rotateX: degrees, 3056 rotateY: degrees, 3057 rotateZ: degrees, 3058 scale, 3059 scaleX: scale, 3060 scaleY: scale, 3061 scaleZ: scale, 3062 skew: degrees, 3063 skewX: degrees, 3064 skewY: degrees, 3065 distance: px, 3066 translateX: px, 3067 translateY: px, 3068 translateZ: px, 3069 x: px, 3070 y: px, 3071 z: px, 3072 perspective: px, 3073 transformPerspective: px, 3074 opacity: alpha, 3075 originX: progressPercentage, 3076 originY: progressPercentage, 3077 originZ: px, 3078 }; 3079 3080 const numberValueTypes = { 3081 // Border props 3082 borderWidth: px, 3083 borderTopWidth: px, 3084 borderRightWidth: px, 3085 borderBottomWidth: px, 3086 borderLeftWidth: px, 3087 borderRadius: px, 3088 radius: px, 3089 borderTopLeftRadius: px, 3090 borderTopRightRadius: px, 3091 borderBottomRightRadius: px, 3092 borderBottomLeftRadius: px, 3093 // Positioning props 3094 width: px, 3095 maxWidth: px, 3096 height: px, 3097 maxHeight: px, 3098 top: px, 3099 right: px, 3100 bottom: px, 3101 left: px, 3102 // Spacing props 3103 padding: px, 3104 paddingTop: px, 3105 paddingRight: px, 3106 paddingBottom: px, 3107 paddingLeft: px, 3108 margin: px, 3109 marginTop: px, 3110 marginRight: px, 3111 marginBottom: px, 3112 marginLeft: px, 3113 // Misc 3114 backgroundPositionX: px, 3115 backgroundPositionY: px, 3116 ...transformValueTypes, 3117 zIndex: int, 3118 // SVG 3119 fillOpacity: alpha, 3120 strokeOpacity: alpha, 3121 numOctaves: int, 3122 }; 3123 3124 /** 3125 * A map of default value types for common values 3126 */ 3127 const defaultValueTypes = { 3128 ...numberValueTypes, 3129 // Color props 3130 color, 3131 backgroundColor: color, 3132 outlineColor: color, 3133 fill: color, 3134 stroke: color, 3135 // Border props 3136 borderColor: color, 3137 borderTopColor: color, 3138 borderRightColor: color, 3139 borderBottomColor: color, 3140 borderLeftColor: color, 3141 filter, 3142 WebkitFilter: filter, 3143 }; 3144 /** 3145 * Gets the default ValueType for the provided value key 3146 */ 3147 const getDefaultValueType = (key) => defaultValueTypes[key]; 3148 3149 function getAnimatableNone(key, value) { 3150 let defaultValueType = getDefaultValueType(key); 3151 if (defaultValueType !== filter) 3152 defaultValueType = complex; 3153 // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target 3154 return defaultValueType.getAnimatableNone 3155 ? defaultValueType.getAnimatableNone(value) 3156 : undefined; 3157 } 3158 3159 /** 3160 * If we encounter keyframes like "none" or "0" and we also have keyframes like 3161 * "#fff" or "200px 200px" we want to find a keyframe to serve as a template for 3162 * the "none" keyframes. In this case "#fff" or "200px 200px" - then these get turned into 3163 * zero equivalents, i.e. "#fff0" or "0px 0px". 3164 */ 3165 const invalidTemplates = new Set(["auto", "none", "0"]); 3166 function makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name) { 3167 let i = 0; 3168 let animatableTemplate = undefined; 3169 while (i < unresolvedKeyframes.length && !animatableTemplate) {
vendor: 5,998 bytes, lines 3170-3295
3170 const keyframe = unresolvedKeyframes[i]; 3171 if (typeof keyframe === "string" && 3172 !invalidTemplates.has(keyframe) && 3173 analyseComplexValue(keyframe).values.length) { 3174 animatableTemplate = unresolvedKeyframes[i]; 3175 } 3176 i++; 3177 } 3178 if (animatableTemplate && name) { 3179 for (const noneIndex of noneKeyframeIndexes) { 3180 unresolvedKeyframes[noneIndex] = getAnimatableNone(name, animatableTemplate); 3181 } 3182 } 3183 } 3184 3185 class DOMKeyframesResolver extends KeyframeResolver { 3186 constructor(unresolvedKeyframes, onComplete, name, motionValue, element) { 3187 super(unresolvedKeyframes, onComplete, name, motionValue, element, true); 3188 } 3189 readKeyframes() { 3190 const { unresolvedKeyframes, element, name } = this; 3191 if (!element || !element.current) 3192 return; 3193 super.readKeyframes(); 3194 /** 3195 * If any keyframe is a CSS variable, we need to find its value by sampling the element 3196 */ 3197 for (let i = 0; i < unresolvedKeyframes.length; i++) { 3198 let keyframe = unresolvedKeyframes[i]; 3199 if (typeof keyframe === "string") { 3200 keyframe = keyframe.trim(); 3201 if (isCSSVariableToken(keyframe)) { 3202 const resolved = getVariableValue(keyframe, element.current); 3203 if (resolved !== undefined) { 3204 unresolvedKeyframes[i] = resolved; 3205 } 3206 if (i === unresolvedKeyframes.length - 1) { 3207 this.finalKeyframe = keyframe; 3208 } 3209 } 3210 } 3211 } 3212 /** 3213 * Resolve "none" values. We do this potentially twice - once before and once after measuring keyframes. 3214 * This could be seen as inefficient but it's a trade-off to avoid measurements in more situations, which 3215 * have a far bigger performance impact. 3216 */ 3217 this.resolveNoneKeyframes(); 3218 /** 3219 * Check to see if unit type has changed. If so schedule jobs that will 3220 * temporarily set styles to the destination keyframes. 3221 * Skip if we have more than two keyframes or this isn't a positional value. 3222 * TODO: We can throw if there are multiple keyframes and the value type changes. 3223 */ 3224 if (!positionalKeys.has(name) || unresolvedKeyframes.length !== 2) { 3225 return; 3226 } 3227 const [origin, target] = unresolvedKeyframes; 3228 const originType = findDimensionValueType(origin); 3229 const targetType = findDimensionValueType(target); 3230 /** 3231 * Either we don't recognise these value types or we can animate between them. 3232 */ 3233 if (originType === targetType) 3234 return; 3235 /** 3236 * If both values are numbers or pixels, we can animate between them by 3237 * converting them to numbers. 3238 */ 3239 if (isNumOrPxType(originType) && isNumOrPxType(targetType)) { 3240 for (let i = 0; i < unresolvedKeyframes.length; i++) { 3241 const value = unresolvedKeyframes[i]; 3242 if (typeof value === "string") { 3243 unresolvedKeyframes[i] = parseFloat(value); 3244 } 3245 } 3246 } 3247 else if (positionalValues[name]) { 3248 /** 3249 * Else, the only way to resolve this is by measuring the element. 3250 */ 3251 this.needsMeasurement = true; 3252 } 3253 } 3254 resolveNoneKeyframes() { 3255 const { unresolvedKeyframes, name } = this; 3256 const noneKeyframeIndexes = []; 3257 for (let i = 0; i < unresolvedKeyframes.length; i++) { 3258 if (unresolvedKeyframes[i] === null || 3259 isNone(unresolvedKeyframes[i])) { 3260 noneKeyframeIndexes.push(i); 3261 } 3262 } 3263 if (noneKeyframeIndexes.length) { 3264 makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name); 3265 } 3266 } 3267 measureInitialState() { 3268 const { element, unresolvedKeyframes, name } = this; 3269 if (!element || !element.current) 3270 return; 3271 if (name === "height") { 3272 this.suspendedScrollY = window.pageYOffset; 3273 } 3274 this.measuredOrigin = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current)); 3275 unresolvedKeyframes[0] = this.measuredOrigin; 3276 // Set final key frame to measure after next render 3277 const measureKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1]; 3278 if (measureKeyframe !== undefined) { 3279 element.getValue(name, measureKeyframe).jump(measureKeyframe, false); 3280 } 3281 } 3282 measureEndState() { 3283 const { element, name, unresolvedKeyframes } = this; 3284 if (!element || !element.current) 3285 return; 3286 const value = element.getValue(name); 3287 value && value.jump(this.measuredOrigin, false); 3288 const finalKeyframeIndex = unresolvedKeyframes.length - 1; 3289 const finalKeyframe = unresolvedKeyframes[finalKeyframeIndex]; 3290 unresolvedKeyframes[finalKeyframeIndex] = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current)); 3291 if (finalKeyframe !== null && this.finalKeyframe === undefined) { 3292 this.finalKeyframe = finalKeyframe; 3293 } 3294 // If we removed transform values, reapply them before the next render 3295 if (this.removedTransforms?.length) {
3296 this.removedTransforms.forEach(([unsetTransformName, unsetTransformValue]) => { 3297 element 3298 .getValue(unsetTransformName) 3299 .set(unsetTransformValue); 3300 }); 3301 } 3302 this.resolveNoneKeyframes(); 3303 } 3304 } 3305 3306 const pxValues = new Set([ 3307 // Border props 3308 "borderWidth", 3309 "borderTopWidth", 3310 "borderRightWidth", 3311 "borderBottomWidth", 3312 "borderLeftWidth", 3313 "borderRadius", 3314 "radius", 3315 "borderTopLeftRadius", 3316 "borderTopRightRadius", 3317 "borderBottomRightRadius", 3318 "borderBottomLeftRadius", 3319 // Positioning props 3320 "width", 3321 "maxWidth", 3322 "height", 3323 "maxHeight", 3324 "top", 3325 "right", 3326 "bottom", 3327 "left", 3328 // Spacing props 3329 "padding", 3330 "paddingTop", 3331 "paddingRight", 3332 "paddingBottom", 3333 "paddingLeft", 3334 "margin", 3335 "marginTop", 3336 "marginRight", 3337 "marginBottom", 3338 "marginLeft", 3339 // Misc 3340 "backgroundPositionX", 3341 "backgroundPositionY", 3342 ]); 3343 3344 function applyPxDefaults(keyframes, name) { 3345 for (let i = 0; i < keyframes.length; i++) {
3346 if (typeof keyframes[i] === "number" && pxValues.has(name)) { 3347 keyframes[i] = keyframes[i] + "px"; 3348 } 3349 } 3350 } 3351 3352 function isWaapiSupportedEasing(easing) { 3353 return Boolean((typeof easing === "function" && supportsLinearEasing()) || 3354 !easing || 3355 (typeof easing === "string" && 3356 (easing in supportedWaapiEasing || supportsLinearEasing())) || 3357 isBezierDefinition(easing) || 3358 (Array.isArray(easing) && easing.every(isWaapiSupportedEasing))); 3359 } 3360 3361 const supportsPartialKeyframes = /*@__PURE__*/ memo(() => { 3362 try { 3363 document.createElement("div").animate({ opacity: [1] }); 3364 } 3365 catch (e) { 3366 return false; 3367 } 3368 return true; 3369 }); 3370 3371 /** 3372 * A list of values that can be hardware-accelerated. 3373 */ 3374 const acceleratedValues = new Set([ 3375 "opacity", 3376 "clipPath", 3377 "filter", 3378 "transform", 3379 // TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved 3380 // or until we implement support for linear() easing. 3381 // "background-color" 3382 ]); 3383 3384 function camelToDash$1(str) { 3385 return str.replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`); 3386 } 3387 3388 function resolveElements(elementOrSelector, scope, selectorCache) { 3389 if (elementOrSelector instanceof EventTarget) { 3390 return [elementOrSelector]; 3391 } 3392 else if (typeof elementOrSelector === "string") { 3393 let root = document; 3394 if (scope) { 3395 root = scope.current; 3396 } 3397 const elements = selectorCache?.[elementOrSelector] ?? 3398 root.querySelectorAll(elementOrSelector); 3399 return elements ? Array.from(elements) : []; 3400 } 3401 return Array.from(elementOrSelector); 3402 } 3403 3404 function createSelectorEffect(subjectEffect) { 3405 return (subject, values) => { 3406 const elements = resolveElements(subject); 3407 const subscriptions = []; 3408 for (const element of elements) { 3409 const remove = subjectEffect(element, values); 3410 subscriptions.push(remove); 3411 } 3412 return () => { 3413 for (const remove of subscriptions) 3414 remove(); 3415 }; 3416 }; 3417 } 3418 3419 /** 3420 * Provided a value and a ValueType, returns the value as that value type. 3421 */ 3422 const getValueAsType = (value, type) => { 3423 return type && typeof value === "number" 3424 ? type.transform(value) 3425 : value; 3426 }; 3427 3428 class MotionValueState { 3429 constructor() { 3430 this.latest = {}; 3431 this.values = new Map(); 3432 } 3433 set(name, value, render, computed, useDefaultValueType = true) { 3434 const existingValue = this.values.get(name); 3435 if (existingValue) { 3436 existingValue.onRemove(); 3437 } 3438 const onChange = () => { 3439 const v = value.get(); 3440 if (useDefaultValueType) { 3441 this.latest[name] = getValueAsType(v, numberValueTypes[name]); 3442 } 3443 else { 3444 this.latest[name] = v; 3445 } 3446 render && frame.render(render); 3447 }; 3448 onChange(); 3449 const cancelOnChange = value.on("change", onChange); 3450 computed && value.addDependent(computed); 3451 const remove = () => { 3452 cancelOnChange(); 3453 render && cancelFrame(render); 3454 this.values.delete(name); 3455 computed && value.removeDependent(computed); 3456 }; 3457 this.values.set(name, { value, onRemove: remove }); 3458 return remove; 3459 } 3460 get(name) { 3461 return this.values.get(name)?.value; 3462 } 3463 destroy() { 3464 for (const value of this.values.values()) { 3465 value.onRemove(); 3466 } 3467 } 3468 } 3469
3470 function createEffect(addValue) { 3471 const stateCache = new WeakMap(); 3472 const subscriptions = []; 3473 return (subject, values) => { 3474 const state = stateCache.get(subject) ?? new MotionValueState(); 3475 stateCache.set(subject, state); 3476 for (const key in values) { 3477 const value = values[key]; 3478 const remove = addValue(subject, state, key, value); 3479 subscriptions.push(remove); 3480 } 3481 return () => { 3482 for (const cancel of subscriptions) 3483 cancel(); 3484 }; 3485 }; 3486 } 3487 3488 function canSetAsProperty(element, name) { 3489 if (!(name in element)) 3490 return false; 3491 const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(element), name) || 3492 Object.getOwnPropertyDescriptor(element, name); 3493 // Check if it has a setter 3494 return descriptor && typeof descriptor.set === "function"; 3495 } 3496 const addAttrValue = (element, state, key, value) => { 3497 const isProp = canSetAsProperty(element, key); 3498 const name = isProp 3499 ? key 3500 : key.startsWith("data") || key.startsWith("aria") 3501 ? camelToDash$1(key) 3502 : key; 3503 /** 3504 * Set attribute directly via property if available 3505 */ 3506 const render = isProp 3507 ? () => { 3508 element[name] = state.latest[key]; 3509 } 3510 : () => { 3511 const v = state.latest[key]; 3512 if (v === null || v === undefined) { 3513 element.removeAttribute(name); 3514 } 3515 else { 3516 element.setAttribute(name, String(v)); 3517 } 3518 }; 3519 return state.set(key, value, render); 3520 }; 3521 const attrEffect = /*@__PURE__*/ createSelectorEffect( 3522 /*@__PURE__*/ createEffect(addAttrValue)); 3523 3524 const propEffect = /*@__PURE__*/ createEffect((subject, state, key, value) => { 3525 return state.set(key, value, () => { 3526 subject[key] = state.latest[key]; 3527 }, undefined, false); 3528 }); 3529 3530 /** 3531 * Checks if an element is an HTML element in a way 3532 * that works across iframes 3533 */ 3534 function isHTMLElement(element) { 3535 return isObject(element) && "offsetHeight" in element; 3536 } 3537 3538 /** 3539 * Maximum time between the value of two frames, beyond which we 3540 * assume the velocity has since been 0. 3541 */ 3542 const MAX_VELOCITY_DELTA = 30; 3543 const isFloat = (value) => { 3544 return !isNaN(parseFloat(value)); 3545 }; 3546 const collectMotionValues = { 3547 current: undefined, 3548 }; 3549 /** 3550 * `MotionValue` is used to track the state and velocity of motion values. 3551 * 3552 * @public 3553 */ 3554 class MotionValue { 3555 /** 3556 * @param init - The initiating value 3557 * @param config - Optional configuration options 3558 * 3559 * - `transformer`: A function to transform incoming values with. 3560 */ 3561 constructor(init, options = {}) { 3562 /** 3563 * Tracks whether this value can output a velocity. Currently this is only true 3564 * if the value is numerical, but we might be able to widen the scope here and support 3565 * other value types. 3566 * 3567 * @internal 3568 */ 3569 this.canTrackVelocity = null; 3570 /** 3571 * An object containing a SubscriptionManager for each active event. 3572 */ 3573 this.events = {}; 3574 this.updateAndNotify = (v) => { 3575 const currentTime = time.now(); 3576 /** 3577 * If we're updating the value during another frame or eventloop 3578 * than the previous frame, then the we set the previous frame value 3579 * to current. 3580 */ 3581 if (this.updatedAt !== currentTime) { 3582 this.setPrevFrameValue(); 3583 } 3584 this.prev = this.current; 3585 this.setCurrent(v); 3586 // Update update subscribers 3587 if (this.current !== this.prev) { 3588 this.events.change?.notify(this.current); 3589 if (this.dependents) { 3590 for (const dependent of this.dependents) { 3591 dependent.dirty(); 3592 } 3593 } 3594 } 3595 }; 3596 this.hasAnimated = false;
vendor: 9,660 bytes, lines 3597-3882
3597 this.setCurrent(init); 3598 this.owner = options.owner; 3599 } 3600 setCurrent(current) { 3601 this.current = current; 3602 this.updatedAt = time.now(); 3603 if (this.canTrackVelocity === null && current !== undefined) { 3604 this.canTrackVelocity = isFloat(this.current); 3605 } 3606 } 3607 setPrevFrameValue(prevFrameValue = this.current) { 3608 this.prevFrameValue = prevFrameValue; 3609 this.prevUpdatedAt = this.updatedAt; 3610 } 3611 /** 3612 * Adds a function that will be notified when the `MotionValue` is updated. 3613 * 3614 * It returns a function that, when called, will cancel the subscription. 3615 * 3616 * When calling `onChange` inside a React component, it should be wrapped with the 3617 * `useEffect` hook. As it returns an unsubscribe function, this should be returned 3618 * from the `useEffect` function to ensure you don't add duplicate subscribers.. 3619 * 3620 * ```jsx 3621 * export const MyComponent = () => { 3622 * const x = useMotionValue(0) 3623 * const y = useMotionValue(0) 3624 * const opacity = useMotionValue(1) 3625 * 3626 * useEffect(() => { 3627 * function updateOpacity() { 3628 * const maxXY = Math.max(x.get(), y.get()) 3629 * const newOpacity = transform(maxXY, [0, 100], [1, 0]) 3630 * opacity.set(newOpacity) 3631 * } 3632 * 3633 * const unsubscribeX = x.on("change", updateOpacity) 3634 * const unsubscribeY = y.on("change", updateOpacity) 3635 * 3636 * return () => { 3637 * unsubscribeX() 3638 * unsubscribeY() 3639 * } 3640 * }, []) 3641 * 3642 * return <motion.div style={{ x }} /> 3643 * } 3644 * ``` 3645 * 3646 * @param subscriber - A function that receives the latest value. 3647 * @returns A function that, when called, will cancel this subscription. 3648 * 3649 * @deprecated 3650 */ 3651 onChange(subscription) { 3652 { 3653 warnOnce(false, `value.onChange(callback) is deprecated. Switch to value.on("change", callback).`); 3654 } 3655 return this.on("change", subscription); 3656 } 3657 on(eventName, callback) { 3658 if (!this.events[eventName]) { 3659 this.events[eventName] = new SubscriptionManager(); 3660 } 3661 const unsubscribe = this.events[eventName].add(callback); 3662 if (eventName === "change") { 3663 return () => { 3664 unsubscribe(); 3665 /** 3666 * If we have no more change listeners by the start 3667 * of the next frame, stop active animations. 3668 */ 3669 frame.read(() => { 3670 if (!this.events.change.getSize()) { 3671 this.stop(); 3672 } 3673 }); 3674 }; 3675 } 3676 return unsubscribe; 3677 } 3678 clearListeners() { 3679 for (const eventManagers in this.events) { 3680 this.events[eventManagers].clear(); 3681 } 3682 } 3683 /** 3684 * Attaches a passive effect to the `MotionValue`. 3685 */ 3686 attach(passiveEffect, stopPassiveEffect) { 3687 this.passiveEffect = passiveEffect; 3688 this.stopPassiveEffect = stopPassiveEffect; 3689 } 3690 /** 3691 * Sets the state of the `MotionValue`. 3692 * 3693 * @remarks 3694 * 3695 * ```jsx 3696 * const x = useMotionValue(0) 3697 * x.set(10) 3698 * ``` 3699 * 3700 * @param latest - Latest value to set. 3701 * @param render - Whether to notify render subscribers. Defaults to `true` 3702 * 3703 * @public 3704 */ 3705 set(v) { 3706 if (!this.passiveEffect) { 3707 this.updateAndNotify(v); 3708 } 3709 else { 3710 this.passiveEffect(v, this.updateAndNotify); 3711 } 3712 } 3713 setWithVelocity(prev, current, delta) { 3714 this.set(current); 3715 this.prev = undefined; 3716 this.prevFrameValue = prev; 3717 this.prevUpdatedAt = this.updatedAt - delta; 3718 } 3719 /** 3720 * Set the state of the `MotionValue`, stopping any active animations, 3721 * effects, and resets velocity to `0`. 3722 */ 3723 jump(v, endAnimation = true) { 3724 this.updateAndNotify(v); 3725 this.prev = v; 3726 this.prevUpdatedAt = this.prevFrameValue = undefined; 3727 endAnimation && this.stop(); 3728 if (this.stopPassiveEffect) 3729 this.stopPassiveEffect(); 3730 } 3731 dirty() { 3732 this.events.change?.notify(this.current); 3733 } 3734 addDependent(dependent) { 3735 if (!this.dependents) { 3736 this.dependents = new Set(); 3737 } 3738 this.dependents.add(dependent); 3739 } 3740 removeDependent(dependent) { 3741 if (this.dependents) { 3742 this.dependents.delete(dependent); 3743 } 3744 } 3745 /** 3746 * Returns the latest state of `MotionValue` 3747 * 3748 * @returns - The latest state of `MotionValue` 3749 * 3750 * @public 3751 */ 3752 get() { 3753 if (collectMotionValues.current) { 3754 collectMotionValues.current.push(this); 3755 } 3756 return this.current; 3757 } 3758 /** 3759 * @public 3760 */ 3761 getPrevious() { 3762 return this.prev; 3763 } 3764 /** 3765 * Returns the latest velocity of `MotionValue` 3766 * 3767 * @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical. 3768 * 3769 * @public 3770 */ 3771 getVelocity() { 3772 const currentTime = time.now(); 3773 if (!this.canTrackVelocity || 3774 this.prevFrameValue === undefined || 3775 currentTime - this.updatedAt > MAX_VELOCITY_DELTA) { 3776 return 0; 3777 } 3778 const delta = Math.min(this.updatedAt - this.prevUpdatedAt, MAX_VELOCITY_DELTA); 3779 // Casts because of parseFloat's poor typing 3780 return velocityPerSecond(parseFloat(this.current) - 3781 parseFloat(this.prevFrameValue), delta); 3782 } 3783 /** 3784 * Registers a new animation to control this `MotionValue`. Only one 3785 * animation can drive a `MotionValue` at one time. 3786 * 3787 * ```jsx 3788 * value.start() 3789 * ``` 3790 * 3791 * @param animation - A function that starts the provided animation 3792 */ 3793 start(startAnimation) { 3794 this.stop(); 3795 return new Promise((resolve) => { 3796 this.hasAnimated = true; 3797 this.animation = startAnimation(resolve); 3798 if (this.events.animationStart) { 3799 this.events.animationStart.notify(); 3800 } 3801 }).then(() => { 3802 if (this.events.animationComplete) { 3803 this.events.animationComplete.notify(); 3804 } 3805 this.clearAnimation(); 3806 }); 3807 } 3808 /** 3809 * Stop the currently active animation. 3810 * 3811 * @public 3812 */ 3813 stop() { 3814 if (this.animation) { 3815 this.animation.stop(); 3816 if (this.events.animationCancel) { 3817 this.events.animationCancel.notify(); 3818 } 3819 } 3820 this.clearAnimation(); 3821 } 3822 /** 3823 * Returns `true` if this value is currently animating. 3824 * 3825 * @public 3826 */ 3827 isAnimating() { 3828 return !!this.animation; 3829 } 3830 clearAnimation() { 3831 delete this.animation; 3832 } 3833 /** 3834 * Destroy and clean up subscribers to this `MotionValue`. 3835 * 3836 * The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically 3837 * handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually 3838 * created a `MotionValue` via the `motionValue` function. 3839 * 3840 * @public 3841 */ 3842 destroy() { 3843 this.dependents?.clear(); 3844 this.events.destroy?.notify(); 3845 this.clearListeners(); 3846 this.stop(); 3847 if (this.stopPassiveEffect) { 3848 this.stopPassiveEffect(); 3849 } 3850 } 3851 } 3852 function motionValue(init, options) { 3853 return new MotionValue(init, options); 3854 } 3855 3856 const translateAlias$1 = { 3857 x: "translateX", 3858 y: "translateY", 3859 z: "translateZ", 3860 transformPerspective: "perspective", 3861 }; 3862 function buildTransform$1(state) { 3863 let transform = ""; 3864 let transformIsDefault = true; 3865 /** 3866 * Loop over all possible transforms in order, adding the ones that 3867 * are present to the transform string. 3868 */ 3869 for (let i = 0; i < transformPropOrder.length; i++) { 3870 const key = transformPropOrder[i]; 3871 const value = state.latest[key]; 3872 if (value === undefined) 3873 continue; 3874 let valueIsDefault = true; 3875 if (typeof value === "number") { 3876 valueIsDefault = value === (key.startsWith("scale") ? 1 : 0); 3877 } 3878 else { 3879 valueIsDefault = parseFloat(value) === 0; 3880 } 3881 if (!valueIsDefault) { 3882 transformIsDefault = false;
3883 const transformName = translateAlias$1[key] || key; 3884 const valueToRender = state.latest[key]; 3885 transform += `${transformName}(${valueToRender}) `; 3886 } 3887 } 3888 return transformIsDefault ? "none" : transform.trim(); 3889 } 3890 3891 const originProps = new Set(["originX", "originY", "originZ"]); 3892 const addStyleValue = (element, state, key, value) => { 3893 let render = undefined; 3894 let computed = undefined; 3895 if (transformProps.has(key)) { 3896 if (!state.get("transform")) { 3897 // If this is an HTML element, we need to set the transform-box to fill-box 3898 // to normalise the transform relative to the element's bounding box 3899 if (!isHTMLElement(element) && !state.get("transformBox")) { 3900 addStyleValue(element, state, "transformBox", new MotionValue("fill-box")); 3901 } 3902 state.set("transform", new MotionValue("none"), () => { 3903 element.style.transform = buildTransform$1(state); 3904 }); 3905 } 3906 computed = state.get("transform"); 3907 } 3908 else if (originProps.has(key)) { 3909 if (!state.get("transformOrigin")) { 3910 state.set("transformOrigin", new MotionValue(""), () => { 3911 const originX = state.latest.originX ?? "50%"; 3912 const originY = state.latest.originY ?? "50%"; 3913 const originZ = state.latest.originZ ?? 0; 3914 element.style.transformOrigin = `${originX} ${originY} ${originZ}`; 3915 }); 3916 } 3917 computed = state.get("transformOrigin"); 3918 } 3919 else if (isCSSVar(key)) { 3920 render = () => { 3921 element.style.setProperty(key, state.latest[key]); 3922 }; 3923 } 3924 else { 3925 render = () => { 3926 element.style[key] = state.latest[key]; 3927 }; 3928 } 3929 return state.set(key, value, render, computed); 3930 }; 3931 const styleEffect = /*@__PURE__*/ createSelectorEffect( 3932 /*@__PURE__*/ createEffect(addStyleValue)); 3933 3934 const toPx = px.transform; 3935 function addSVGPathValue(element, state, key, value) { 3936 frame.render(() => element.setAttribute("pathLength", "1")); 3937 if (key === "pathOffset") { 3938 return state.set(key, value, () => element.setAttribute("stroke-dashoffset", toPx(-state.latest[key]))); 3939 } 3940 else { 3941 if (!state.get("stroke-dasharray")) { 3942 state.set("stroke-dasharray", new MotionValue("1 1"), () => { 3943 const { pathLength = 1, pathSpacing } = state.latest; 3944 element.setAttribute("stroke-dasharray", `${toPx(pathLength)} ${toPx(pathSpacing ?? 1 - Number(pathLength))}`); 3945 }); 3946 } 3947 return state.set(key, value, undefined, state.get("stroke-dasharray")); 3948 } 3949 } 3950 const addSVGValue = (element, state, key, value) => { 3951 if (key.startsWith("path")) { 3952 return addSVGPathValue(element, state, key, value); 3953 } 3954 else if (key.startsWith("attr")) { 3955 return addAttrValue(element, state, convertAttrKey(key), value); 3956 } 3957 const handler = key in element.style ? addStyleValue : addAttrValue; 3958 return handler(element, state, key, value); 3959 }; 3960 const svgEffect = /*@__PURE__*/ createSelectorEffect( 3961 /*@__PURE__*/ createEffect(addSVGValue)); 3962 function convertAttrKey(key) { 3963 return key.replace(/^attr([A-Z])/, (_, firstChar) => firstChar.toLowerCase()); 3964 } 3965 3966 const { schedule: microtask, cancel: cancelMicrotask } = 3967 /* @__PURE__ */ createRenderBatcher(queueMicrotask, false); 3968 3969 const isDragging = { 3970 x: false, 3971 y: false, 3972 }; 3973 function isDragActive() { 3974 return isDragging.x || isDragging.y; 3975 } 3976 3977 function setDragLock(axis) { 3978 if (axis === "x" || axis === "y") { 3979 if (isDragging[axis]) { 3980 return null; 3981 } 3982 else { 3983 isDragging[axis] = true; 3984 return () => { 3985 isDragging[axis] = false;
vendor: 7,642 bytes, lines 3986-4190
3986 }; 3987 } 3988 } 3989 else { 3990 if (isDragging.x || isDragging.y) { 3991 return null; 3992 } 3993 else { 3994 isDragging.x = isDragging.y = true; 3995 return () => { 3996 isDragging.x = isDragging.y = false; 3997 }; 3998 } 3999 } 4000 } 4001 4002 function setupGesture(elementOrSelector, options) { 4003 const elements = resolveElements(elementOrSelector); 4004 const gestureAbortController = new AbortController(); 4005 const eventOptions = { 4006 passive: true, 4007 ...options, 4008 signal: gestureAbortController.signal, 4009 }; 4010 const cancel = () => gestureAbortController.abort(); 4011 return [elements, eventOptions, cancel]; 4012 } 4013 4014 function isValidHover(event) { 4015 return !(event.pointerType === "touch" || isDragActive()); 4016 } 4017 /** 4018 * Create a hover gesture. hover() is different to .addEventListener("pointerenter") 4019 * in that it has an easier syntax, filters out polyfilled touch events, interoperates 4020 * with drag gestures, and automatically removes the "pointerennd" event listener when the hover ends. 4021 * 4022 * @public 4023 */ 4024 function hover(elementOrSelector, onHoverStart, options = {}) { 4025 const [elements, eventOptions, cancel] = setupGesture(elementOrSelector, options); 4026 const onPointerEnter = (enterEvent) => { 4027 if (!isValidHover(enterEvent)) 4028 return; 4029 const { target } = enterEvent; 4030 const onHoverEnd = onHoverStart(target, enterEvent); 4031 if (typeof onHoverEnd !== "function" || !target) 4032 return; 4033 const onPointerLeave = (leaveEvent) => { 4034 if (!isValidHover(leaveEvent)) 4035 return; 4036 onHoverEnd(leaveEvent); 4037 target.removeEventListener("pointerleave", onPointerLeave); 4038 }; 4039 target.addEventListener("pointerleave", onPointerLeave, eventOptions); 4040 }; 4041 elements.forEach((element) => { 4042 element.addEventListener("pointerenter", onPointerEnter, eventOptions); 4043 }); 4044 return cancel; 4045 } 4046 4047 /** 4048 * Recursively traverse up the tree to check whether the provided child node 4049 * is the parent or a descendant of it. 4050 * 4051 * @param parent - Element to find 4052 * @param child - Element to test against parent 4053 */ 4054 const isNodeOrChild = (parent, child) => { 4055 if (!child) { 4056 return false; 4057 } 4058 else if (parent === child) { 4059 return true; 4060 } 4061 else { 4062 return isNodeOrChild(parent, child.parentElement); 4063 } 4064 }; 4065 4066 const isPrimaryPointer = (event) => { 4067 if (event.pointerType === "mouse") { 4068 return typeof event.button !== "number" || event.button <= 0; 4069 } 4070 else { 4071 /** 4072 * isPrimary is true for all mice buttons, whereas every touch point 4073 * is regarded as its own input. So subsequent concurrent touch points 4074 * will be false. 4075 * 4076 * Specifically match against false here as incomplete versions of 4077 * PointerEvents in very old browser might have it set as undefined. 4078 */ 4079 return event.isPrimary !== false; 4080 } 4081 }; 4082 4083 const focusableElements = new Set([ 4084 "BUTTON", 4085 "INPUT", 4086 "SELECT", 4087 "TEXTAREA", 4088 "A", 4089 ]); 4090 function isElementKeyboardAccessible(element) { 4091 return (focusableElements.has(element.tagName) || 4092 element.tabIndex !== -1); 4093 } 4094 4095 const isPressing = new WeakSet(); 4096 4097 /** 4098 * Filter out events that are not "Enter" keys. 4099 */ 4100 function filterEvents(callback) { 4101 return (event) => { 4102 if (event.key !== "Enter") 4103 return; 4104 callback(event); 4105 }; 4106 } 4107 function firePointerEvent(target, type) { 4108 target.dispatchEvent(new PointerEvent("pointer" + type, { isPrimary: true, bubbles: true })); 4109 } 4110 const enableKeyboardPress = (focusEvent, eventOptions) => { 4111 const element = focusEvent.currentTarget; 4112 if (!element) 4113 return; 4114 const handleKeydown = filterEvents(() => { 4115 if (isPressing.has(element)) 4116 return; 4117 firePointerEvent(element, "down"); 4118 const handleKeyup = filterEvents(() => { 4119 firePointerEvent(element, "up"); 4120 }); 4121 const handleBlur = () => firePointerEvent(element, "cancel"); 4122 element.addEventListener("keyup", handleKeyup, eventOptions); 4123 element.addEventListener("blur", handleBlur, eventOptions); 4124 }); 4125 element.addEventListener("keydown", handleKeydown, eventOptions); 4126 /** 4127 * Add an event listener that fires on blur to remove the keydown events. 4128 */ 4129 element.addEventListener("blur", () => element.removeEventListener("keydown", handleKeydown), eventOptions); 4130 }; 4131 4132 /** 4133 * Filter out events that are not primary pointer events, or are triggering 4134 * while a Motion gesture is active. 4135 */ 4136 function isValidPressEvent(event) { 4137 return isPrimaryPointer(event) && !isDragActive(); 4138 } 4139 /** 4140 * Create a press gesture. 4141 * 4142 * Press is different to `"pointerdown"`, `"pointerup"` in that it 4143 * automatically filters out secondary pointer events like right 4144 * click and multitouch. 4145 * 4146 * It also adds accessibility support for keyboards, where 4147 * an element with a press gesture will receive focus and 4148 * trigger on Enter `"keydown"` and `"keyup"` events. 4149 * 4150 * This is different to a browser's `"click"` event, which does 4151 * respond to keyboards but only for the `"click"` itself, rather 4152 * than the press start and end/cancel. The element also needs 4153 * to be focusable for this to work, whereas a press gesture will 4154 * make an element focusable by default. 4155 * 4156 * @public 4157 */ 4158 function press(targetOrSelector, onPressStart, options = {}) { 4159 const [targets, eventOptions, cancelEvents] = setupGesture(targetOrSelector, options); 4160 const startPress = (startEvent) => { 4161 const target = startEvent.currentTarget; 4162 if (!isValidPressEvent(startEvent)) 4163 return; 4164 isPressing.add(target); 4165 const onPressEnd = onPressStart(target, startEvent); 4166 const onPointerEnd = (endEvent, success) => { 4167 window.removeEventListener("pointerup", onPointerUp); 4168 window.removeEventListener("pointercancel", onPointerCancel); 4169 if (isPressing.has(target)) { 4170 isPressing.delete(target); 4171 } 4172 if (!isValidPressEvent(endEvent)) { 4173 return; 4174 } 4175 if (typeof onPressEnd === "function") { 4176 onPressEnd(endEvent, { success }); 4177 } 4178 }; 4179 const onPointerUp = (upEvent) => { 4180 onPointerEnd(upEvent, target === window || 4181 target === document || 4182 options.useGlobalTarget || 4183 isNodeOrChild(target, upEvent.target)); 4184 }; 4185 const onPointerCancel = (cancelEvent) => { 4186 onPointerEnd(cancelEvent, false); 4187 }; 4188 window.addEventListener("pointerup", onPointerUp, eventOptions); 4189 window.addEventListener("pointercancel", onPointerCancel, eventOptions); 4190 };
4191 targets.forEach((target) => { 4192 const pointerDownTarget = options.useGlobalTarget ? window : target; 4193 pointerDownTarget.addEventListener("pointerdown", startPress, eventOptions); 4194 if (isHTMLElement(target)) { 4195 target.addEventListener("focus", (event) => enableKeyboardPress(event, eventOptions)); 4196 if (!isElementKeyboardAccessible(target) && 4197 !target.hasAttribute("tabindex")) { 4198 target.tabIndex = 0; 4199 } 4200 } 4201 }); 4202 return cancelEvents; 4203 } 4204 4205 function getComputedStyle$2(element, name) { 4206 const computedStyle = window.getComputedStyle(element); 4207 return isCSSVar(name) 4208 ? computedStyle.getPropertyValue(name) 4209 : computedStyle[name]; 4210 } 4211 4212 /** 4213 * Checks if an element is an SVG element in a way 4214 * that works across iframes 4215 */ 4216 function isSVGElement(element) { 4217 return isObject(element) && "ownerSVGElement" in element; 4218 } 4219 4220 const resizeHandlers = new WeakMap(); 4221 let observer; 4222 const getSize = (borderBoxAxis, svgAxis, htmlAxis) => (target, borderBoxSize) => { 4223 if (borderBoxSize && borderBoxSize[0]) { 4224 return borderBoxSize[0][(borderBoxAxis + "Size")]; 4225 } 4226 else if (isSVGElement(target) && "getBBox" in target) { 4227 return target.getBBox()[svgAxis]; 4228 } 4229 else { 4230 return target[htmlAxis]; 4231 } 4232 }; 4233 const getWidth = /*@__PURE__*/ getSize("inline", "width", "offsetWidth"); 4234 const getHeight = /*@__PURE__*/ getSize("block", "height", "offsetHeight"); 4235 function notifyTarget({ target, borderBoxSize }) { 4236 resizeHandlers.get(target)?.forEach((handler) => { 4237 handler(target, { 4238 get width() { 4239 return getWidth(target, borderBoxSize); 4240 }, 4241 get height() { 4242 return getHeight(target, borderBoxSize); 4243 }, 4244 }); 4245 }); 4246 } 4247 function notifyAll(entries) { 4248 entries.forEach(notifyTarget); 4249 } 4250 function createResizeObserver() { 4251 if (typeof ResizeObserver === "undefined") 4252 return; 4253 observer = new ResizeObserver(notifyAll); 4254 } 4255 function resizeElement(target, handler) { 4256 if (!observer) 4257 createResizeObserver(); 4258 const elements = resolveElements(target); 4259 elements.forEach((element) => { 4260 let elementHandlers = resizeHandlers.get(element); 4261 if (!elementHandlers) { 4262 elementHandlers = new Set(); 4263 resizeHandlers.set(element, elementHandlers); 4264 } 4265 elementHandlers.add(handler); 4266 observer?.observe(element); 4267 }); 4268 return () => { 4269 elements.forEach((element) => { 4270 const elementHandlers = resizeHandlers.get(element); 4271 elementHandlers?.delete(handler); 4272 if (!elementHandlers?.size) { 4273 observer?.unobserve(element); 4274 } 4275 }); 4276 }; 4277 } 4278 4279 const windowCallbacks = new Set(); 4280 let windowResizeHandler; 4281 function createWindowResizeHandler() { 4282 windowResizeHandler = () => { 4283 const info = { 4284 get width() { 4285 return window.innerWidth; 4286 }, 4287 get height() { 4288 return window.innerHeight; 4289 }, 4290 }; 4291 windowCallbacks.forEach((callback) => callback(info)); 4292 }; 4293 window.addEventListener("resize", windowResizeHandler); 4294 } 4295 function resizeWindow(callback) { 4296 windowCallbacks.add(callback); 4297 if (!windowResizeHandler) 4298 createWindowResizeHandler(); 4299 return () => { 4300 windowCallbacks.delete(callback); 4301 if (!windowCallbacks.size && 4302 typeof windowResizeHandler === "function") { 4303 window.removeEventListener("resize", windowResizeHandler); 4304 windowResizeHandler = undefined; 4305 } 4306 }; 4307 } 4308 4309 function resize(a, b) { 4310 return typeof a === "function" ? resizeWindow(a) : resizeElement(a, b); 4311 } 4312 4313 function observeTimeline(update, timeline) { 4314 let prevProgress; 4315 const onFrame = () => { 4316 const { currentTime } = timeline; 4317 const percentage = currentTime === null ? 0 : currentTime.value; 4318 const progress = percentage / 100; 4319 if (prevProgress !== progress) { 4320 update(progress); 4321 } 4322 prevProgress = progress; 4323 }; 4324 frame.preUpdate(onFrame, true); 4325 return () => cancelFrame(onFrame); 4326 } 4327 4328 function record() { 4329 const { value } = statsBuffer; 4330 if (value === null) { 4331 cancelFrame(record); 4332 return; 4333 } 4334 value.frameloop.rate.push(frameData.delta); 4335 value.animations.mainThread.push(activeAnimations.mainThread); 4336 value.animations.waapi.push(activeAnimations.waapi); 4337 value.animations.layout.push(activeAnimations.layout); 4338 } 4339 function mean(values) { 4340 return values.reduce((acc, value) => acc + value, 0) / values.length; 4341 } 4342 function summarise(values, calcAverage = mean) { 4343 if (values.length === 0) { 4344 return { 4345 min: 0, 4346 max: 0, 4347 avg: 0, 4348 }; 4349 } 4350 return { 4351 min: Math.min(...values), 4352 max: Math.max(...values), 4353 avg: calcAverage(values), 4354 }; 4355 } 4356 const msToFps = (ms) => Math.round(1000 / ms); 4357 function clearStatsBuffer() { 4358 statsBuffer.value = null; 4359 statsBuffer.addProjectionMetrics = null; 4360 } 4361 function reportStats() { 4362 const { value } = statsBuffer; 4363 if (!value) { 4364 throw new Error("Stats are not being measured"); 4365 } 4366 clearStatsBuffer(); 4367 cancelFrame(record); 4368 const summary = { 4369 frameloop: { 4370 setup: summarise(value.frameloop.setup), 4371 rate: summarise(value.frameloop.rate), 4372 read: summarise(value.frameloop.read), 4373 resolveKeyframes: summarise(value.frameloop.resolveKeyframes), 4374 preUpdate: summarise(value.frameloop.preUpdate), 4375 update: summarise(value.frameloop.update), 4376 preRender: summarise(value.frameloop.preRender), 4377 render: summarise(value.frameloop.render), 4378 postRender: summarise(value.frameloop.postRender), 4379 }, 4380 animations: { 4381 mainThread: summarise(value.animations.mainThread), 4382 waapi: summarise(value.animations.waapi), 4383 layout: summarise(value.animations.layout), 4384 }, 4385 layoutProjection: { 4386 nodes: summarise(value.layoutProjection.nodes), 4387 calculatedTargetDeltas: summarise(value.layoutProjection.calculatedTargetDeltas), 4388 calculatedProjections: summarise(value.layoutProjection.calculatedProjections), 4389 }, 4390 }; 4391 /** 4392 * Convert the rate to FPS 4393 */ 4394 const { rate } = summary.frameloop; 4395 rate.min = msToFps(rate.min); 4396 rate.max = msToFps(rate.max); 4397 rate.avg = msToFps(rate.avg); 4398 [rate.min, rate.max] = [rate.max, rate.min]; 4399 return summary; 4400 } 4401 function recordStats() { 4402 if (statsBuffer.value) { 4403 clearStatsBuffer(); 4404 throw new Error("Stats are already being measured"); 4405 } 4406 const newStatsBuffer = statsBuffer; 4407 newStatsBuffer.value = { 4408 frameloop: { 4409 setup: [], 4410 rate: [], 4411 read: [], 4412 resolveKeyframes: [], 4413 preUpdate: [], 4414 update: [], 4415 preRender: [], 4416 render: [], 4417 postRender: [], 4418 }, 4419 animations: { 4420 mainThread: [], 4421 waapi: [], 4422 layout: [], 4423 }, 4424 layoutProjection: { 4425 nodes: [], 4426 calculatedTargetDeltas: [], 4427 calculatedProjections: [], 4428 }, 4429 }; 4430 newStatsBuffer.addProjectionMetrics = (metrics) => { 4431 const { layoutProjection } = newStatsBuffer.value; 4432 layoutProjection.nodes.push(metrics.nodes); 4433 layoutProjection.calculatedTargetDeltas.push(metrics.calculatedTargetDeltas); 4434 layoutProjection.calculatedProjections.push(metrics.calculatedProjections); 4435 }; 4436 frame.postRender(record, true); 4437 return reportStats; 4438 } 4439 4440 /** 4441 * Checks if an element is specifically an SVGSVGElement (the root SVG element) 4442 * in a way that works across iframes 4443 */ 4444 function isSVGSVGElement(element) { 4445 return isSVGElement(element) && element.tagName === "svg"; 4446 } 4447 4448 function getOriginIndex(from, total) { 4449 if (from === "first") { 4450 return 0; 4451 } 4452 else { 4453 const lastIndex = total - 1; 4454 return from === "last" ? lastIndex : lastIndex / 2; 4455 } 4456 } 4457 function stagger(duration = 0.1, { startDelay = 0, from = 0, ease } = {}) { 4458 return (i, total) => { 4459 const fromIndex = typeof from === "number" ? from : getOriginIndex(from, total); 4460 const distance = Math.abs(fromIndex - i); 4461 let delay = duration * distance; 4462 if (ease) { 4463 const maxDelay = total * duration; 4464 const easingFunction = easingDefinitionToFunction(ease);
4465 delay = easingFunction(delay / maxDelay) * maxDelay; 4466 } 4467 return startDelay + delay; 4468 }; 4469 } 4470 4471 function transform(...args) { 4472 const useImmediate = !Array.isArray(args[0]); 4473 const argOffset = useImmediate ? 0 : -1; 4474 const inputValue = args[0 + argOffset]; 4475 const inputRange = args[1 + argOffset]; 4476 const outputRange = args[2 + argOffset]; 4477 const options = args[3 + argOffset]; 4478 const interpolator = interpolate(inputRange, outputRange, options); 4479 return useImmediate ? interpolator(inputValue) : interpolator; 4480 } 4481 4482 function subscribeValue(inputValues, outputValue, getLatest) { 4483 const update = () => outputValue.set(getLatest()); 4484 const scheduleUpdate = () => frame.preRender(update, false, true); 4485 const subscriptions = inputValues.map((v) => v.on("change", scheduleUpdate)); 4486 outputValue.on("destroy", () => { 4487 subscriptions.forEach((unsubscribe) => unsubscribe()); 4488 cancelFrame(update); 4489 }); 4490 } 4491 4492 /** 4493 * Create a `MotionValue` that transforms the output of other `MotionValue`s by 4494 * passing their latest values through a transform function. 4495 * 4496 * Whenever a `MotionValue` referred to in the provided function is updated, 4497 * it will be re-evaluated. 4498 * 4499 * ```jsx 4500 * const x = motionValue(0) 4501 * const y = transformValue(() => x.get() * 2) // double x 4502 * ``` 4503 * 4504 * @param transformer - A transform function. This function must be pure with no side-effects or conditional statements. 4505 * @returns `MotionValue` 4506 * 4507 * @public 4508 */ 4509 function transformValue(transform) { 4510 const collectedValues = []; 4511 /** 4512 * Open session of collectMotionValues. Any MotionValue that calls get() 4513 * inside transform will be saved into this array. 4514 */ 4515 collectMotionValues.current = collectedValues; 4516 const initialValue = transform(); 4517 collectMotionValues.current = undefined; 4518 const value = motionValue(initialValue); 4519 subscribeValue(collectedValues, value, transform); 4520 return value; 4521 } 4522 4523 /** 4524 * Create a `MotionValue` that maps the output of another `MotionValue` by 4525 * mapping it from one range of values into another. 4526 * 4527 * @remarks 4528 * 4529 * Given an input range of `[-200, -100, 100, 200]` and an output range of 4530 * `[0, 1, 1, 0]`, the returned `MotionValue` will: 4531 * 4532 * - When provided a value between `-200` and `-100`, will return a value between `0` and `1`. 4533 * - When provided a value between `-100` and `100`, will return `1`. 4534 * - When provided a value between `100` and `200`, will return a value between `1` and `0` 4535 * 4536 * The input range must be a linear series of numbers. The output range 4537 * can be any value type supported by Motion: numbers, colors, shadows, etc. 4538 * 4539 * Every value in the output range must be of the same type and in the same format. 4540 * 4541 * ```jsx 4542 * const x = motionValue(0) 4543 * const xRange = [-200, -100, 100, 200] 4544 * const opacityRange = [0, 1, 1, 0] 4545 * const opacity = mapValue(x, xRange, opacityRange) 4546 * ``` 4547 * 4548 * @param inputValue - `MotionValue` 4549 * @param inputRange - A linear series of numbers (either all increasing or decreasing) 4550 * @param outputRange - A series of numbers, colors or strings. Must be the same length as `inputRange`. 4551 * @param options - 4552 * 4553 * - clamp: boolean. Clamp values to within the given range. Defaults to `true` 4554 * - ease: EasingFunction[]. Easing functions to use on the interpolations between each value in the input and output ranges. If provided as an array, the array must be one item shorter than the input and output ranges, as the easings apply to the transition between each. 4555 * 4556 * @returns `MotionValue` 4557 * 4558 * @public 4559 */ 4560 function mapValue(inputValue, inputRange, outputRange, options) { 4561 const map = transform(inputRange, outputRange, options); 4562 return transformValue(() => map(inputValue.get())); 4563 } 4564 4565 const isMotionValue = (value) => Boolean(value && value.getVelocity); 4566 4567 /** 4568 * Create a `MotionValue` that animates to its latest value using a spring. 4569 * Can either be a value or track another `MotionValue`. 4570 * 4571 * ```jsx 4572 * const x = motionValue(0) 4573 * const y = transformValue(() => x.get() * 2) // double x 4574 * ``` 4575 * 4576 * @param transformer - A transform function. This function must be pure with no side-effects or conditional statements. 4577 * @returns `MotionValue` 4578 * 4579 * @public 4580 */ 4581 function springValue(source, options) { 4582 const initialValue = isMotionValue(source) ? source.get() : source; 4583 const value = motionValue(initialValue); 4584 attachSpring(value, source, options); 4585 return value; 4586 } 4587 function attachSpring(value, source, options) { 4588 const initialValue = value.get(); 4589 let activeAnimation = null; 4590 let latestValue = initialValue; 4591 let latestSetter; 4592 const unit = typeof initialValue === "string" 4593 ? initialValue.replace(/[\d.-]/g, "") 4594 : undefined; 4595 const stopAnimation = () => { 4596 if (activeAnimation) { 4597 activeAnimation.stop(); 4598 activeAnimation = null; 4599 } 4600 }; 4601 const startAnimation = () => { 4602 stopAnimation(); 4603 activeAnimation = new JSAnimation({ 4604 keyframes: [asNumber(value.get()), asNumber(latestValue)], 4605 velocity: value.getVelocity(), 4606 type: "spring", 4607 restDelta: 0.001, 4608 restSpeed: 0.01, 4609 ...options, 4610 onUpdate: latestSetter, 4611 }); 4612 }; 4613 value.attach((v, set) => { 4614 latestValue = v; 4615 latestSetter = (latest) => set(parseValue(latest, unit)); 4616 frame.postRender(startAnimation); 4617 }, stopAnimation); 4618 if (isMotionValue(source)) {
4619 const removeSourceOnChange = source.on("change", (v) => value.set(parseValue(v, unit))); 4620 const removeValueOnDestroy = value.on("destroy", removeSourceOnChange); 4621 return () => { 4622 removeSourceOnChange(); 4623 removeValueOnDestroy(); 4624 }; 4625 } 4626 return stopAnimation; 4627 } 4628 function parseValue(v, unit) { 4629 return unit ? v + unit : v; 4630 } 4631 function asNumber(v) { 4632 return typeof v === "number" ? v : parseFloat(v); 4633 } 4634 4635 /** 4636 * A list of all ValueTypes 4637 */ 4638 const valueTypes = [...dimensionValueTypes, color, complex]; 4639 /** 4640 * Tests a value against the list of ValueTypes 4641 */ 4642 const findValueType = (v) => valueTypes.find(testValueType(v)); 4643 4644 function chooseLayerType(valueName) { 4645 if (valueName === "layout") 4646 return "group"; 4647 if (valueName === "enter" || valueName === "new") 4648 return "new"; 4649 if (valueName === "exit" || valueName === "old") 4650 return "old"; 4651 return "group"; 4652 } 4653 4654 let pendingRules = {}; 4655 let style = null; 4656 const css = { 4657 set: (selector, values) => { 4658 pendingRules[selector] = values; 4659 }, 4660 commit: () => { 4661 if (!style) { 4662 style = document.createElement("style"); 4663 style.id = "motion-view"; 4664 } 4665 let cssText = ""; 4666 for (const selector in pendingRules) { 4667 const rule = pendingRules[selector]; 4668 cssText += `${selector} {\n`; 4669 for (const [property, value] of Object.entries(rule)) { 4670 cssText += ` ${property}: ${value};\n`; 4671 } 4672 cssText += "}\n"; 4673 } 4674 style.textContent = cssText; 4675 document.head.appendChild(style); 4676 pendingRules = {}; 4677 }, 4678 remove: () => { 4679 if (style && style.parentElement) { 4680 style.parentElement.removeChild(style); 4681 } 4682 }, 4683 }; 4684 4685 function getViewAnimationLayerInfo(pseudoElement) { 4686 const match = pseudoElement.match(/::view-transition-(old|new|group|image-pair)\((.*?)\)/); 4687 if (!match) 4688 return null; 4689 return { layer: match[2], type: match[1] }; 4690 } 4691 4692 function filterViewAnimations(animation) { 4693 const { effect } = animation; 4694 if (!effect) 4695 return false; 4696 return (effect.target === document.documentElement && 4697 effect.pseudoElement?.startsWith("::view-transition")); 4698 } 4699 function getViewAnimations() { 4700 return document.getAnimations().filter(filterViewAnimations); 4701 } 4702 4703 function hasTarget(target, targets) { 4704 return targets.has(target) && Object.keys(targets.get(target)).length > 0; 4705 } 4706 4707 const definitionNames = ["layout", "enter", "exit", "new", "old"]; 4708 function startViewAnimation(builder) { 4709 const { update, targets, options: defaultOptions } = builder; 4710 if (!document.startViewTransition) { 4711 return new Promise(async (resolve) => { 4712 await update(); 4713 resolve(new GroupAnimation([])); 4714 }); 4715 } 4716 // TODO: Go over existing targets and ensure they all have ids 4717 /** 4718 * If we don't have any animations defined for the root target, 4719 * remove it from being captured. 4720 */ 4721 if (!hasTarget("root", targets)) { 4722 css.set(":root", { 4723 "view-transition-name": "none", 4724 }); 4725 } 4726 /** 4727 * Set the timing curve to linear for all view transition layers. 4728 * This gets baked into the keyframes, which can't be changed 4729 * without breaking the generated animation. 4730 * 4731 * This allows us to set easing via updateTiming - which can be changed. 4732 */ 4733 css.set("::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*)", { "animation-timing-function": "linear !important" }); 4734 css.commit(); // Write 4735 const transition = document.startViewTransition(async () => { 4736 await update(); 4737 // TODO: Go over new targets and ensure they all have ids 4738 }); 4739 transition.finished.finally(() => { 4740 css.remove(); // Write 4741 }); 4742 return new Promise((resolve) => { 4743 transition.ready.then(() => { 4744 const generatedViewAnimations = getViewAnimations(); 4745 const animations = []; 4746 /** 4747 * Create animations for each of our explicitly-defined subjects. 4748 */
4749 targets.forEach((definition, target) => { 4750 // TODO: If target is not "root", resolve elements 4751 // and iterate over each 4752 for (const key of definitionNames) { 4753 if (!definition[key]) 4754 continue; 4755 const { keyframes, options } = definition[key]; 4756 for (let [valueName, valueKeyframes] of Object.entries(keyframes)) { 4757 if (!valueKeyframes) 4758 continue; 4759 const valueOptions = { 4760 ...getValueTransition$1(defaultOptions, valueName), 4761 ...getValueTransition$1(options, valueName), 4762 }; 4763 const type = chooseLayerType(key); 4764 /** 4765 * If this is an opacity animation, and keyframes are not an array, 4766 * we need to convert them into an array and set an initial value. 4767 */ 4768 if (valueName === "opacity" && 4769 !Array.isArray(valueKeyframes)) { 4770 const initialValue = type === "new" ? 0 : 1; 4771 valueKeyframes = [initialValue, valueKeyframes]; 4772 } 4773 /** 4774 * Resolve stagger function if provided. 4775 */ 4776 if (typeof valueOptions.delay === "function") { 4777 valueOptions.delay = valueOptions.delay(0, 1); 4778 } 4779 valueOptions.duration && (valueOptions.duration = secondsToMilliseconds(valueOptions.duration)); 4780 valueOptions.delay && (valueOptions.delay = secondsToMilliseconds(valueOptions.delay)); 4781 const animation = new NativeAnimation({ 4782 ...valueOptions, 4783 element: document.documentElement, 4784 name: valueName, 4785 pseudoElement: `::view-transition-${type}(${target})`, 4786 keyframes: valueKeyframes, 4787 }); 4788 animations.push(animation); 4789 } 4790 } 4791 }); 4792 /** 4793 * Handle browser generated animations 4794 */ 4795 for (const animation of generatedViewAnimations) { 4796 if (animation.playState === "finished") 4797 continue; 4798 const { effect } = animation; 4799 if (!effect || !(effect instanceof KeyframeEffect)) 4800 continue; 4801 const { pseudoElement } = effect; 4802 if (!pseudoElement) 4803 continue; 4804 const name = getViewAnimationLayerInfo(pseudoElement); 4805 if (!name) 4806 continue; 4807 const targetDefinition = targets.get(name.layer); 4808 if (!targetDefinition) { 4809 /** 4810 * If transition name is group then update the timing of the animation 4811 * whereas if it's old or new then we could possibly replace it using 4812 * the above method. 4813 */ 4814 const transitionName = name.type === "group" ? "layout" : ""; 4815 let animationTransition = { 4816 ...getValueTransition$1(defaultOptions, transitionName), 4817 }; 4818 animationTransition.duration && (animationTransition.duration = secondsToMilliseconds(animationTransition.duration)); 4819 animationTransition = 4820 applyGeneratorOptions(animationTransition); 4821 const easing = mapEasingToNativeEasing(animationTransition.ease, animationTransition.duration); 4822 effect.updateTiming({ 4823 delay: secondsToMilliseconds(animationTransition.delay ?? 0), 4824 duration: animationTransition.duration, 4825 easing, 4826 }); 4827 animations.push(new NativeAnimationWrapper(animation)); 4828 } 4829 else if (hasOpacity(targetDefinition, "enter") && 4830 hasOpacity(targetDefinition, "exit") && 4831 effect 4832 .getKeyframes() 4833 .some((keyframe) => keyframe.mixBlendMode)) { 4834 animations.push(new NativeAnimationWrapper(animation)); 4835 } 4836 else { 4837 animation.cancel(); 4838 } 4839 } 4840 resolve(new GroupAnimation(animations)); 4841 }); 4842 }); 4843 } 4844 function hasOpacity(target, key) { 4845 return target?.[key]?.keyframes.opacity; 4846 } 4847 4848 let builders = [];
vendor: 1,253 bytes, lines 4849-4879
4849 let current = null; 4850 function next() { 4851 current = null; 4852 const [nextBuilder] = builders; 4853 if (nextBuilder) 4854 start(nextBuilder); 4855 } 4856 function start(builder) { 4857 removeItem(builders, builder); 4858 current = builder; 4859 startViewAnimation(builder).then((animation) => { 4860 builder.notifyReady(animation); 4861 animation.finished.finally(next); 4862 }); 4863 } 4864 function processQueue() { 4865 /** 4866 * Iterate backwards over the builders array. We can ignore the 4867 * "wait" animations. If we have an interrupting animation in the 4868 * queue then we need to batch all preceeding animations into it. 4869 * Currently this only batches the update functions but will also 4870 * need to batch the targets. 4871 */ 4872 for (let i = builders.length - 1; i >= 0; i--) { 4873 const builder = builders[i]; 4874 const { interrupt } = builder.options; 4875 if (interrupt === "immediate") { 4876 const batchedUpdates = builders.slice(0, i + 1).map((b) => b.update); 4877 const remaining = builders.slice(i + 1); 4878 builder.update = () => { 4879 batchedUpdates.forEach((update) =>
4879 update()); 4880 }; 4881 // Put the current builder at the front, followed by any "wait" builders 4882 builders = [builder, ...remaining]; 4883 break; 4884 } 4885 } 4886 if (!current || builders[0]?.options.interrupt === "immediate") { 4887 next(); 4888 } 4889 } 4890 function addToQueue(builder) { 4891 builders.push(builder); 4892 microtask.render(processQueue); 4893 } 4894 4895 class ViewTransitionBuilder { 4896 constructor(update, options = {}) { 4897 this.currentSubject = "root"; 4898 this.targets = new Map(); 4899 this.notifyReady = noop; 4900 this.readyPromise = new Promise((resolve) => { 4901 this.notifyReady = resolve; 4902 }); 4903 this.update = update; 4904 this.options = { 4905 interrupt: "wait", 4906 ...options, 4907 }; 4908 addToQueue(this); 4909 } 4910 get(subject) { 4911 this.currentSubject = subject; 4912 return this; 4913 } 4914 layout(keyframes, options) { 4915 this.updateTarget("layout", keyframes, options); 4916 return this; 4917 } 4918 new(keyframes, options) { 4919 this.updateTarget("new", keyframes, options); 4920 return this; 4921 } 4922 old(keyframes, options) { 4923 this.updateTarget("old", keyframes, options); 4924 return this; 4925 } 4926 enter(keyframes, options) { 4927 this.updateTarget("enter", keyframes, options); 4928 return this; 4929 } 4930 exit(keyframes, options) { 4931 this.updateTarget("exit", keyframes, options); 4932 return this; 4933 } 4934 crossfade(options) { 4935 this.updateTarget("enter", { opacity: 1 }, options); 4936 this.updateTarget("exit", { opacity: 0 }, options); 4937 return this; 4938 } 4939 updateTarget(target, keyframes, options = {}) { 4940 const { currentSubject, targets } = this; 4941 if (!targets.has(currentSubject)) { 4942 targets.set(currentSubject, {}); 4943 } 4944 const targetData = targets.get(currentSubject); 4945 targetData[target] = { keyframes, options }; 4946 } 4947 then(resolve, reject) { 4948 return this.readyPromise.then(resolve, reject); 4949 } 4950 } 4951 function animateView(update, defaultOptions = {}) { 4952 return new ViewTransitionBuilder(update, defaultOptions); 4953 } 4954 4955 /** 4956 * @deprecated 4957 * 4958 * Import as `frame` instead. 4959 */ 4960 const sync = frame; 4961 /** 4962 * @deprecated 4963 * 4964 * Use cancelFrame(callback) instead. 4965 */ 4966 const cancelSync = stepsOrder.reduce((acc, key) => { 4967 acc[key] = (process) => cancelFrame(process); 4968 return acc; 4969 }, {}); 4970 4971 function isDOMKeyframes(keyframes) { 4972 return typeof keyframes === "object" && !Array.isArray(keyframes); 4973 } 4974 4975 function resolveSubjects(subject, keyframes, scope, selectorCache) { 4976 if (typeof subject === "string" && isDOMKeyframes(keyframes)) { 4977 return resolveElements(subject, scope, selectorCache); 4978 } 4979 else if (subject instanceof NodeList) { 4980 return Array.from(subject); 4981 } 4982 else if (Array.isArray(subject)) { 4983 return subject; 4984 } 4985 else { 4986 return [subject]; 4987 } 4988 } 4989 4990 function calculateRepeatDuration(duration, repeat, _repeatDelay) { 4991 return duration * (repeat + 1); 4992 } 4993 4994 /** 4995 * Given a absolute or relative time definition and current/prev time state of the sequence, 4996 * calculate an absolute time for the next keyframes. 4997 */ 4998 function calcNextTime(current, next, prev, labels) { 4999 if (typeof next === "number") { 5000 return next; 5001 } 5002 else if (next.startsWith("-") || next.startsWith("+")) { 5003 return Math.max(0, current + parseFloat(next)); 5004 } 5005 else if (next === "<") { 5006 return prev; 5007 } 5008 else if (next.startsWith("<")) { 5009 return Math.max(0, prev + parseFloat(next.slice(1))); 5010 } 5011 else { 5012 return labels.get(next) ?? current; 5013 } 5014 } 5015 5016 function eraseKeyframes(sequence, startTime, endTime) { 5017 for (let i = 0; i < sequence.length; i++) {
vendor: 2,206 bytes, lines 5018-5079
5018 const keyframe = sequence[i]; 5019 if (keyframe.at > startTime && keyframe.at < endTime) { 5020 removeItem(sequence, keyframe); 5021 // If we remove this item we have to push the pointer back one 5022 i--; 5023 } 5024 } 5025 } 5026 function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) { 5027 /** 5028 * Erase every existing value between currentTime and targetTime, 5029 * this will essentially splice this timeline into any currently 5030 * defined ones. 5031 */ 5032 eraseKeyframes(sequence, startTime, endTime); 5033 for (let i = 0; i < keyframes.length; i++) { 5034 sequence.push({ 5035 value: keyframes[i], 5036 at: mixNumber$1(startTime, endTime, offset[i]), 5037 easing: getEasingForSegment(easing, i), 5038 }); 5039 } 5040 } 5041 5042 /** 5043 * Take an array of times that represent repeated keyframes. For instance 5044 * if we have original times of [0, 0.5, 1] then our repeated times will 5045 * be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back 5046 * down to a 0-1 scale. 5047 */ 5048 function normalizeTimes(times, repeat) { 5049 for (let i = 0; i < times.length; i++) { 5050 times[i] = times[i] / (repeat + 1); 5051 } 5052 } 5053 5054 function compareByTime(a, b) { 5055 if (a.at === b.at) { 5056 if (a.value === null) 5057 return 1; 5058 if (b.value === null) 5059 return -1; 5060 return 0; 5061 } 5062 else { 5063 return a.at - b.at; 5064 } 5065 } 5066 5067 const defaultSegmentEasing = "easeInOut"; 5068 const MAX_REPEAT = 20; 5069 function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) { 5070 const defaultDuration = defaultTransition.duration || 0.3; 5071 const animationDefinitions = new Map(); 5072 const sequences = new Map(); 5073 const elementCache = {}; 5074 const timeLabels = new Map(); 5075 let prevTime = 0; 5076 let currentTime = 0; 5077 let totalDuration = 0; 5078 /** 5079 * Build the timeline by mapping over the sequence array and convert
vendor: 7,724 bytes, lines 5079-5224
5079ing 5080 * the definitions into keyframes and offsets with absolute time values. 5081 * These will later get converted into relative offsets in a second pass. 5082 */ 5083 for (let i = 0; i < sequence.length; i++) { 5084 const segment = sequence[i]; 5085 /** 5086 * If this is a timeline label, mark it and skip the rest of this iteration. 5087 */ 5088 if (typeof segment === "string") { 5089 timeLabels.set(segment, currentTime); 5090 continue; 5091 } 5092 else if (!Array.isArray(segment)) { 5093 timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels)); 5094 continue; 5095 } 5096 let [subject, keyframes, transition = {}] = segment; 5097 /** 5098 * If a relative or absolute time value has been specified we need to resolve 5099 * it in relation to the currentTime. 5100 */ 5101 if (transition.at !== undefined) { 5102 currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels); 5103 } 5104 /** 5105 * Keep track of the maximum duration in this definition. This will be 5106 * applied to currentTime once the definition has been parsed. 5107 */ 5108 let maxDuration = 0; 5109 const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => { 5110 const valueKeyframesAsList = keyframesAsList(valueKeyframes); 5111 const { delay = 0, times = defaultOffset$1(valueKeyframesAsList), type = "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition; 5112 let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition; 5113 /** 5114 * Resolve stagger() if defined. 5115 */ 5116 const calculatedDelay = typeof delay === "function" 5117 ? delay(elementIndex, numSubjects) 5118 : delay; 5119 /** 5120 * If this animation should and can use a spring, generate a spring easing function. 5121 */ 5122 const numKeyframes = valueKeyframesAsList.length; 5123 const createGenerator = isGenerator(type) 5124 ? type 5125 : generators?.[type || "keyframes"]; 5126 if (numKeyframes <= 2 && createGenerator) { 5127 /** 5128 * As we're creating an easing function from a spring, 5129 * ideally we want to generate it using the real distance 5130 * between the two keyframes. However this isn't always 5131 * possible - in these situations we use 0-100. 5132 */ 5133 let absoluteDelta = 100; 5134 if (numKeyframes === 2 && 5135 isNumberKeyframesArray(valueKeyframesAsList)) { 5136 const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0]; 5137 absoluteDelta = Math.abs(delta); 5138 } 5139 const springTransition = { ...remainingTransition }; 5140 if (duration !== undefined) { 5141 springTransition.duration = secondsToMilliseconds(duration); 5142 } 5143 const springEasing = createGeneratorEasing(springTransition, absoluteDelta, createGenerator); 5144 ease = springEasing.ease; 5145 duration = springEasing.duration; 5146 } 5147 duration ?? (duration = defaultDuration); 5148 const startTime = currentTime + calculatedDelay; 5149 /** 5150 * If there's only one time offset of 0, fill in a second with length 1 5151 */ 5152 if (times.length === 1 && times[0] === 0) { 5153 times[1] = 1; 5154 } 5155 /** 5156 * Fill out if offset if fewer offsets than keyframes 5157 */ 5158 const remainder = times.length - valueKeyframesAsList.length; 5159 remainder > 0 && fillOffset(times, remainder); 5160 /** 5161 * If only one value has been set, ie [1], push a null to the start of 5162 * the keyframe array. This will let us mark a keyframe at this point 5163 * that will later be hydrated with the previous value. 5164 */ 5165 valueKeyframesAsList.length === 1 && 5166 valueKeyframesAsList.unshift(null); 5167 /** 5168 * Handle repeat options 5169 */ 5170 if (repeat) { 5171 exports.invariant(repeat < MAX_REPEAT, "Repeat count too high, must be less than 20", "repeat-count-high"); 5172 duration = calculateRepeatDuration(duration, repeat); 5173 const originalKeyframes = [...valueKeyframesAsList]; 5174 const originalTimes = [...times]; 5175 ease = Array.isArray(ease) ? [...ease] : [ease]; 5176 const originalEase = [...ease]; 5177 for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) { 5178 valueKeyframesAsList.push(...originalKeyframes); 5179 for (let keyframeIndex = 0; keyframeIndex < originalKeyframes.length; keyframeIndex++) { 5180 times.push(originalTimes[keyframeIndex] + (repeatIndex + 1)); 5181 ease.push(keyframeIndex === 0 5182 ? "linear" 5183 : getEasingForSegment(originalEase, keyframeIndex - 1)); 5184 } 5185 } 5186 normalizeTimes(times, repeat); 5187 } 5188 const targetTime = startTime + duration; 5189 /** 5190 * Add keyframes, mapping offsets to absolute time. 5191 */ 5192 addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime); 5193 maxDuration = Math.max(calculatedDelay + duration, maxDuration); 5194 totalDuration = Math.max(targetTime, totalDuration); 5195 }; 5196 if (isMotionValue(subject)) { 5197 const subjectSequence = getSubjectSequence(subject, sequences); 5198 resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence)); 5199 } 5200 else { 5201 const subjects = resolveSubjects(subject, keyframes, scope, elementCache); 5202 const numSubjects = subjects.length; 5203 /** 5204 * For every element in this segment, process the defined values. 5205 */ 5206 for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) { 5207 /** 5208 * Cast necessary, but we know these are of this type 5209 */ 5210 keyframes = keyframes; 5211 transition = transition; 5212 const thisSubject = subjects[subjectIndex]; 5213 const subjectSequence = getSubjectSequence(thisSubject, sequences); 5214 for (const key in keyframes) { 5215 resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects); 5216 } 5217 } 5218 } 5219 prevTime = currentTime; 5220 currentTime += maxDuration; 5221 } 5222 /** 5223 * For every element and value combination create a new animation. 5224 */
vendor: 8,083 bytes, lines 5225-5432
5225 sequences.forEach((valueSequences, element) => { 5226 for (const key in valueSequences) { 5227 const valueSequence = valueSequences[key]; 5228 /** 5229 * Arrange all the keyframes in ascending time order. 5230 */ 5231 valueSequence.sort(compareByTime); 5232 const keyframes = []; 5233 const valueOffset = []; 5234 const valueEasing = []; 5235 /** 5236 * For each keyframe, translate absolute times into 5237 * relative offsets based on the total duration of the timeline. 5238 */ 5239 for (let i = 0; i < valueSequence.length; i++) { 5240 const { at, value, easing } = valueSequence[i]; 5241 keyframes.push(value); 5242 valueOffset.push(progress(0, totalDuration, at)); 5243 valueEasing.push(easing || "easeOut"); 5244 } 5245 /** 5246 * If the first keyframe doesn't land on offset: 0 5247 * provide one by duplicating the initial keyframe. This ensures 5248 * it snaps to the first keyframe when the animation starts. 5249 */ 5250 if (valueOffset[0] !== 0) { 5251 valueOffset.unshift(0); 5252 keyframes.unshift(keyframes[0]); 5253 valueEasing.unshift(defaultSegmentEasing); 5254 } 5255 /** 5256 * If the last keyframe doesn't land on offset: 1 5257 * provide one with a null wildcard value. This will ensure it 5258 * stays static until the end of the animation. 5259 */ 5260 if (valueOffset[valueOffset.length - 1] !== 1) { 5261 valueOffset.push(1); 5262 keyframes.push(null); 5263 } 5264 if (!animationDefinitions.has(element)) { 5265 animationDefinitions.set(element, { 5266 keyframes: {}, 5267 transition: {}, 5268 }); 5269 } 5270 const definition = animationDefinitions.get(element); 5271 definition.keyframes[key] = keyframes; 5272 definition.transition[key] = { 5273 ...defaultTransition, 5274 duration: totalDuration, 5275 ease: valueEasing, 5276 times: valueOffset, 5277 ...sequenceTransition, 5278 }; 5279 } 5280 }); 5281 return animationDefinitions; 5282 } 5283 function getSubjectSequence(subject, sequences) { 5284 !sequences.has(subject) && sequences.set(subject, {}); 5285 return sequences.get(subject); 5286 } 5287 function getValueSequence(name, sequences) { 5288 if (!sequences[name]) 5289 sequences[name] = []; 5290 return sequences[name]; 5291 } 5292 function keyframesAsList(keyframes) { 5293 return Array.isArray(keyframes) ? keyframes : [keyframes]; 5294 } 5295 function getValueTransition(transition, key) { 5296 return transition && transition[key] 5297 ? { 5298 ...transition, 5299 ...transition[key], 5300 } 5301 : { ...transition }; 5302 } 5303 const isNumber = (keyframe) => typeof keyframe === "number"; 5304 const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber); 5305 5306 const visualElementStore = new WeakMap(); 5307 5308 const isKeyframesTarget = (v) => { 5309 return Array.isArray(v); 5310 }; 5311 5312 function getValueState(visualElement) { 5313 const state = [{}, {}]; 5314 visualElement?.values.forEach((value, key) => { 5315 state[0][key] = value.get(); 5316 state[1][key] = value.getVelocity(); 5317 }); 5318 return state; 5319 } 5320 function resolveVariantFromProps(props, definition, custom, visualElement) { 5321 /** 5322 * If the variant definition is a function, resolve. 5323 */ 5324 if (typeof definition === "function") { 5325 const [current, velocity] = getValueState(visualElement); 5326 definition = definition(custom !== undefined ? custom : props.custom, current, velocity); 5327 } 5328 /** 5329 * If the variant definition is a variant label, or 5330 * the function returned a variant label, resolve. 5331 */ 5332 if (typeof definition === "string") { 5333 definition = props.variants && props.variants[definition]; 5334 } 5335 /** 5336 * At this point we've resolved both functions and variant labels, 5337 * but the resolved variant label might itself have been a function. 5338 * If so, resolve. This can only have returned a valid target object. 5339 */ 5340 if (typeof definition === "function") { 5341 const [current, velocity] = getValueState(visualElement); 5342 definition = definition(custom !== undefined ? custom : props.custom, current, velocity); 5343 } 5344 return definition; 5345 } 5346 5347 function resolveVariant(visualElement, definition, custom) { 5348 const props = visualElement.getProps(); 5349 return resolveVariantFromProps(props, definition, props.custom, visualElement); 5350 } 5351 5352 /** 5353 * Set VisualElement's MotionValue, creating a new MotionValue for it if 5354 * it doesn't exist. 5355 */ 5356 function setMotionValue(visualElement, key, value) { 5357 if (visualElement.hasValue(key)) { 5358 visualElement.getValue(key).set(value); 5359 } 5360 else { 5361 visualElement.addValue(key, motionValue(value)); 5362 } 5363 } 5364 function resolveFinalValueInKeyframes(v) { 5365 // TODO maybe throw if v.length - 1 is placeholder token? 5366 return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v; 5367 } 5368 function setTarget(visualElement, definition) { 5369 const resolved = resolveVariant(visualElement, definition); 5370 let { transitionEnd = {}, transition = {}, ...target } = resolved || {}; 5371 target = { ...target, ...transitionEnd }; 5372 for (const key in target) { 5373 const value = resolveFinalValueInKeyframes(target[key]); 5374 setMotionValue(visualElement, key, value); 5375 } 5376 } 5377 5378 function isWillChangeMotionValue(value) { 5379 return Boolean(isMotionValue(value) && value.add); 5380 } 5381 5382 function addValueToWillChange(visualElement, key) { 5383 const willChange = visualElement.getValue("willChange"); 5384 /** 5385 * It could be that a user has set willChange to a regular MotionValue, 5386 * in which case we can't add the value to it. 5387 */ 5388 if (isWillChangeMotionValue(willChange)) { 5389 return willChange.add(key); 5390 } 5391 else if (!willChange && MotionGlobalConfig.WillChange) { 5392 const newWillChange = new MotionGlobalConfig.WillChange("auto"); 5393 visualElement.addValue("willChange", newWillChange); 5394 newWillChange.add(key); 5395 } 5396 } 5397 5398 /** 5399 * Convert camelCase to dash-case properties. 5400 */ 5401 const camelToDash = (str) => str.replace(/([a-z])([A-Z])/gu, "$1-$2").toLowerCase(); 5402 5403 const optimizedAppearDataId = "framerAppearId"; 5404 const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId); 5405 5406 function getOptimisedAppearId(visualElement) { 5407 return visualElement.props[optimizedAppearDataAttribute]; 5408 } 5409 5410 const isNotNull = (value) => value !== null; 5411 function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) { 5412 const resolvedKeyframes = keyframes.filter(isNotNull); 5413 const index = repeat && repeatType !== "loop" && repeat % 2 === 1 5414 ? 0 5415 : resolvedKeyframes.length - 1; 5416 return !index || finalKeyframe === undefined 5417 ? resolvedKeyframes[index] 5418 : finalKeyframe; 5419 } 5420 5421 const underDampedSpring = { 5422 type: "spring", 5423 stiffness: 500, 5424 damping: 25, 5425 restSpeed: 10, 5426 }; 5427 const criticallyDampedSpring = (target) => ({ 5428 type: "spring", 5429 stiffness: 550, 5430 damping: target === 0 ? 2 * Math.sqrt(550) : 30, 5431 restSpeed: 10, 5432 });
vendor: 35,986 bytes, lines 5433-6324
5433 const keyframesTransition = { 5434 type: "keyframes", 5435 duration: 0.8, 5436 }; 5437 /** 5438 * Default easing curve is a slightly shallower version of 5439 * the default browser easing curve. 5440 */ 5441 const ease = { 5442 type: "keyframes", 5443 ease: [0.25, 0.1, 0.35, 1], 5444 duration: 0.3, 5445 }; 5446 const getDefaultTransition = (valueKey, { keyframes }) => { 5447 if (keyframes.length > 2) { 5448 return keyframesTransition; 5449 } 5450 else if (transformProps.has(valueKey)) { 5451 return valueKey.startsWith("scale") 5452 ? criticallyDampedSpring(keyframes[1]) 5453 : underDampedSpring; 5454 } 5455 return ease; 5456 }; 5457 5458 /** 5459 * Decide whether a transition is defined on a given Transition. 5460 * This filters out orchestration options and returns true 5461 * if any options are left. 5462 */ 5463 function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, elapsed, ...transition }) { 5464 return !!Object.keys(transition).length; 5465 } 5466 5467 const animateMotionValue = (name, value, target, transition = {}, element, isHandoff) => (onComplete) => { 5468 const valueTransition = getValueTransition$1(transition, name) || {}; 5469 /** 5470 * Most transition values are currently completely overwritten by value-specific 5471 * transitions. In the future it'd be nicer to blend these transitions. But for now 5472 * delay actually does inherit from the root transition if not value-specific. 5473 */ 5474 const delay = valueTransition.delay || transition.delay || 0; 5475 /** 5476 * Elapsed isn't a public transition option but can be passed through from 5477 * optimized appear effects in milliseconds. 5478 */ 5479 let { elapsed = 0 } = transition; 5480 elapsed = elapsed - secondsToMilliseconds(delay); 5481 const options = { 5482 keyframes: Array.isArray(target) ? target : [null, target], 5483 ease: "easeOut", 5484 velocity: value.getVelocity(), 5485 ...valueTransition, 5486 delay: -elapsed, 5487 onUpdate: (v) => { 5488 value.set(v); 5489 valueTransition.onUpdate && valueTransition.onUpdate(v); 5490 }, 5491 onComplete: () => { 5492 onComplete(); 5493 valueTransition.onComplete && valueTransition.onComplete(); 5494 }, 5495 name, 5496 motionValue: value, 5497 element: isHandoff ? undefined : element, 5498 }; 5499 /** 5500 * If there's no transition defined for this value, we can generate 5501 * unique transition settings for this value. 5502 */ 5503 if (!isTransitionDefined(valueTransition)) { 5504 Object.assign(options, getDefaultTransition(name, options)); 5505 } 5506 /** 5507 * Both WAAPI and our internal animation functions use durations 5508 * as defined by milliseconds, while our external API defines them 5509 * as seconds. 5510 */ 5511 options.duration && (options.duration = secondsToMilliseconds(options.duration)); 5512 options.repeatDelay && (options.repeatDelay = secondsToMilliseconds(options.repeatDelay)); 5513 /** 5514 * Support deprecated way to set initial value. Prefer keyframe syntax. 5515 */ 5516 if (options.from !== undefined) { 5517 options.keyframes[0] = options.from; 5518 } 5519 let shouldSkip = false; 5520 if (options.type === false || 5521 (options.duration === 0 && !options.repeatDelay)) { 5522 makeAnimationInstant(options); 5523 if (options.delay === 0) { 5524 shouldSkip = true; 5525 } 5526 } 5527 if (MotionGlobalConfig.instantAnimations || 5528 MotionGlobalConfig.skipAnimations) { 5529 shouldSkip = true; 5530 makeAnimationInstant(options); 5531 options.delay = 0; 5532 } 5533 /** 5534 * If the transition type or easing has been explicitly set by the user 5535 * then we don't want to allow flattening the animation. 5536 */ 5537 options.allowFlatten = !valueTransition.type && !valueTransition.ease; 5538 /** 5539 * If we can or must skip creating the animation, and apply only 5540 * the final keyframe, do so. We also check once keyframes are resolved but 5541 * this early check prevents the need to create an animation at all. 5542 */ 5543 if (shouldSkip && !isHandoff && value.get() !== undefined) { 5544 const finalKeyframe = getFinalKeyframe(options.keyframes, valueTransition); 5545 if (finalKeyframe !== undefined) { 5546 frame.update(() => { 5547 options.onUpdate(finalKeyframe); 5548 options.onComplete(); 5549 }); 5550 return; 5551 } 5552 } 5553 return valueTransition.isSync 5554 ? new JSAnimation(options) 5555 : new AsyncMotionValueAnimation(options); 5556 }; 5557 5558 /** 5559 * Decide whether we should block this animation. Previously, we achieved this 5560 * just by checking whether the key was listed in protectedKeys, but this 5561 * posed problems if an animation was triggered by afterChildren and protectedKeys 5562 * had been set to true in the meantime. 5563 */ 5564 function shouldBlockAnimation({ protectedKeys, needsAnimating }, key) { 5565 const shouldBlock = protectedKeys.hasOwnProperty(key) && needsAnimating[key] !== true; 5566 needsAnimating[key] = false; 5567 return shouldBlock; 5568 } 5569 function animateTarget(visualElement, targetAndTransition, { delay = 0, transitionOverride, type } = {}) { 5570 let { transition = visualElement.getDefaultTransition(), transitionEnd, ...target } = targetAndTransition; 5571 if (transitionOverride) 5572 transition = transitionOverride; 5573 const animations = []; 5574 const animationTypeState = type && 5575 visualElement.animationState && 5576 visualElement.animationState.getState()[type]; 5577 for (const key in target) { 5578 const value = visualElement.getValue(key, visualElement.latestValues[key] ?? null); 5579 const valueTarget = target[key]; 5580 if (valueTarget === undefined || 5581 (animationTypeState && 5582 shouldBlockAnimation(animationTypeState, key))) { 5583 continue; 5584 } 5585 const valueTransition = { 5586 delay, 5587 ...getValueTransition$1(transition || {}, key), 5588 }; 5589 /** 5590 * If the value is already at the defined target, skip the animation. 5591 */ 5592 const currentValue = value.get(); 5593 if (currentValue !== undefined && 5594 !value.isAnimating && 5595 !Array.isArray(valueTarget) && 5596 valueTarget === currentValue && 5597 !valueTransition.velocity) { 5598 continue; 5599 } 5600 /** 5601 * If this is the first time a value is being animated, check 5602 * to see if we're handling off from an existing animation. 5603 */ 5604 let isHandoff = false; 5605 if (window.MotionHandoffAnimation) { 5606 const appearId = getOptimisedAppearId(visualElement); 5607 if (appearId) { 5608 const startTime = window.MotionHandoffAnimation(appearId, key, frame); 5609 if (startTime !== null) { 5610 valueTransition.startTime = startTime; 5611 isHandoff = true; 5612 } 5613 } 5614 } 5615 addValueToWillChange(visualElement, key); 5616 value.start(animateMotionValue(key, value, valueTarget, visualElement.shouldReduceMotion && positionalKeys.has(key) 5617 ? { type: false } 5618 : valueTransition, visualElement, isHandoff)); 5619 const animation = value.animation; 5620 if (animation) { 5621 animations.push(animation); 5622 } 5623 } 5624 if (transitionEnd) { 5625 Promise.all(animations).then(() => { 5626 frame.update(() => { 5627 transitionEnd && setTarget(visualElement, transitionEnd); 5628 }); 5629 }); 5630 } 5631 return animations; 5632 } 5633 5634 /** 5635 * Bounding boxes tend to be defined as top, left, right, bottom. For various operations 5636 * it's easier to consider each axis individually. This function returns a bounding box 5637 * as a map of single-axis min/max values. 5638 */ 5639 function convertBoundingBoxToBox({ top, left, right, bottom, }) { 5640 return { 5641 x: { min: left, max: right }, 5642 y: { min: top, max: bottom }, 5643 }; 5644 } 5645 /** 5646 * Applies a TransformPoint function to a bounding box. TransformPoint is usually a function 5647 * provided by Framer to allow measured points to be corrected for device scaling. This is used 5648 * when measuring DOM elements and DOM event points. 5649 */ 5650 function transformBoxPoints(point, transformPoint) { 5651 if (!transformPoint) 5652 return point; 5653 const topLeft = transformPoint({ x: point.left, y: point.top }); 5654 const bottomRight = transformPoint({ x: point.right, y: point.bottom }); 5655 return { 5656 top: topLeft.y, 5657 left: topLeft.x, 5658 bottom: bottomRight.y, 5659 right: bottomRight.x, 5660 }; 5661 } 5662 5663 function measureViewportBox(instance, transformPoint) { 5664 return convertBoundingBoxToBox(transformBoxPoints(instance.getBoundingClientRect(), transformPoint)); 5665 } 5666 5667 const featureProps = { 5668 animation: [ 5669 "animate", 5670 "variants", 5671 "whileHover", 5672 "whileTap", 5673 "exit", 5674 "whileInView", 5675 "whileFocus", 5676 "whileDrag", 5677 ], 5678 exit: ["exit"], 5679 drag: ["drag", "dragControls"], 5680 focus: ["whileFocus"], 5681 hover: ["whileHover", "onHoverStart", "onHoverEnd"], 5682 tap: ["whileTap", "onTap", "onTapStart", "onTapCancel"], 5683 pan: ["onPan", "onPanStart", "onPanSessionStart", "onPanEnd"], 5684 inView: ["whileInView", "onViewportEnter", "onViewportLeave"], 5685 layout: ["layout", "layoutId"], 5686 }; 5687 const featureDefinitions = {}; 5688 for (const key in featureProps) { 5689 featureDefinitions[key] = { 5690 isEnabled: (props) => featureProps[key].some((name) => !!props[name]), 5691 }; 5692 } 5693 5694 const createAxis = () => ({ min: 0, max: 0 }); 5695 const createBox = () => ({ 5696 x: createAxis(), 5697 y: createAxis(), 5698 }); 5699 5700 const isBrowser = typeof window !== "undefined"; 5701 5702 // Does this device prefer reduced motion? Returns `null` server-side. 5703 const prefersReducedMotion = { current: null }; 5704 const hasReducedMotionListener = { current: false }; 5705 5706 function initPrefersReducedMotion() { 5707 hasReducedMotionListener.current = true; 5708 if (!isBrowser) 5709 return; 5710 if (window.matchMedia) { 5711 const motionMediaQuery = window.matchMedia("(prefers-reduced-motion)"); 5712 const setReducedMotionPreferences = () => (prefersReducedMotion.current = motionMediaQuery.matches); 5713 motionMediaQuery.addEventListener("change", setReducedMotionPreferences); 5714 setReducedMotionPreferences(); 5715 } 5716 else { 5717 prefersReducedMotion.current = false; 5718 } 5719 } 5720 5721 function isAnimationControls(v) { 5722 return (v !== null && 5723 typeof v === "object" && 5724 typeof v.start === "function"); 5725 } 5726 5727 /** 5728 * Decides if the supplied variable is variant label 5729 */ 5730 function isVariantLabel(v) { 5731 return typeof v === "string" || Array.isArray(v); 5732 } 5733 5734 const variantPriorityOrder = [ 5735 "animate", 5736 "whileInView", 5737 "whileFocus", 5738 "whileHover", 5739 "whileTap", 5740 "whileDrag", 5741 "exit", 5742 ]; 5743 const variantProps = ["initial", ...variantPriorityOrder]; 5744 5745 function isControllingVariants(props) { 5746 return (isAnimationControls(props.animate) || 5747 variantProps.some((name) => isVariantLabel(props[name]))); 5748 } 5749 function isVariantNode(props) { 5750 return Boolean(isControllingVariants(props) || props.variants); 5751 } 5752 5753 function updateMotionValuesFromProps(element, next, prev) { 5754 for (const key in next) { 5755 const nextValue = next[key]; 5756 const prevValue = prev[key]; 5757 if (isMotionValue(nextValue)) { 5758 /** 5759 * If this is a motion value found in props or style, we want to add it 5760 * to our visual element's motion value map. 5761 */ 5762 element.addValue(key, nextValue); 5763 } 5764 else if (isMotionValue(prevValue)) { 5765 /** 5766 * If we're swapping from a motion value to a static value, 5767 * create a new motion value from that 5768 */ 5769 element.addValue(key, motionValue(nextValue, { owner: element })); 5770 } 5771 else if (prevValue !== nextValue) { 5772 /** 5773 * If this is a flat value that has changed, update the motion value 5774 * or create one if it doesn't exist. We only want to do this if we're 5775 * not handling the value with our animation state. 5776 */ 5777 if (element.hasValue(key)) { 5778 const existingValue = element.getValue(key); 5779 if (existingValue.liveStyle === true) { 5780 existingValue.jump(nextValue); 5781 } 5782 else if (!existingValue.hasAnimated) { 5783 existingValue.set(nextValue); 5784 } 5785 } 5786 else { 5787 const latestValue = element.getStaticValue(key); 5788 element.addValue(key, motionValue(latestValue !== undefined ? latestValue : nextValue, { owner: element })); 5789 } 5790 } 5791 } 5792 // Handle removed values 5793 for (const key in prev) { 5794 if (next[key] === undefined) 5795 element.removeValue(key); 5796 } 5797 return next; 5798 } 5799 5800 const propEventHandlers = [ 5801 "AnimationStart", 5802 "AnimationComplete", 5803 "Update", 5804 "BeforeLayoutMeasure", 5805 "LayoutMeasure", 5806 "LayoutAnimationStart", 5807 "LayoutAnimationComplete", 5808 ]; 5809 /** 5810 * A VisualElement is an imperative abstraction around UI elements such as 5811 * HTMLElement, SVGElement, Three.Object3D etc. 5812 */ 5813 class VisualElement { 5814 /** 5815 * This method takes React props and returns found MotionValues. For example, HTML 5816 * MotionValues will be found within the style prop, whereas for Three.js within attribute arrays. 5817 * 5818 * This isn't an abstract method as it needs calling in the constructor, but it is 5819 * intended to be one. 5820 */ 5821 scrapeMotionValuesFromProps(_props, _prevProps, _visualElement) { 5822 return {}; 5823 } 5824 constructor({ parent, props, presenceContext, reducedMotionConfig, blockInitialAnimation, visualState, }, options = {}) { 5825 /** 5826 * A reference to the current underlying Instance, e.g. a HTMLElement 5827 * or Three.Mesh etc. 5828 */ 5829 this.current = null; 5830 /** 5831 * A set containing references to this VisualElement's children. 5832 */ 5833 this.children = new Set(); 5834 /** 5835 * Determine what role this visual element should take in the variant tree. 5836 */ 5837 this.isVariantNode = false; 5838 this.isControllingVariants = false; 5839 /** 5840 * Decides whether this VisualElement should animate in reduced motion 5841 * mode. 5842 * 5843 * TODO: This is currently set on every individual VisualElement but feels 5844 * like it could be set globally. 5845 */ 5846 this.shouldReduceMotion = null; 5847 /** 5848 * A map of all motion values attached to this visual element. Motion 5849 * values are source of truth for any given animated value. A motion 5850 * value might be provided externally by the component via props. 5851 */ 5852 this.values = new Map(); 5853 this.KeyframeResolver = KeyframeResolver; 5854 /** 5855 * Cleanup functions for active features (hover/tap/exit etc) 5856 */ 5857 this.features = {}; 5858 /** 5859 * A map of every subscription that binds the provided or generated 5860 * motion values onChange listeners to this visual element. 5861 */ 5862 this.valueSubscriptions = new Map(); 5863 /** 5864 * A reference to the previously-provided motion values as returned 5865 * from scrapeMotionValuesFromProps. We use the keys in here to determine 5866 * if any motion values need to be removed after props are updated. 5867 */ 5868 this.prevMotionValues = {}; 5869 /** 5870 * An object containing a SubscriptionManager for each active event. 5871 */ 5872 this.events = {}; 5873 /** 5874 * An object containing an unsubscribe function for each prop event subscription. 5875 * For example, every "Update" event can have multiple subscribers via 5876 * VisualElement.on(), but only one of those can be defined via the onUpdate prop. 5877 */ 5878 this.propEventSubscriptions = {}; 5879 this.notifyUpdate = () => this.notify("Update", this.latestValues); 5880 this.render = () => { 5881 if (!this.current) 5882 return; 5883 this.triggerBuild(); 5884 this.renderInstance(this.current, this.renderState, this.props.style, this.projection); 5885 }; 5886 this.renderScheduledAt = 0.0; 5887 this.scheduleRender = () => { 5888 const now = time.now(); 5889 if (this.renderScheduledAt < now) { 5890 this.renderScheduledAt = now; 5891 frame.render(this.render, false, true); 5892 } 5893 }; 5894 const { latestValues, renderState } = visualState; 5895 this.latestValues = latestValues; 5896 this.baseTarget = { ...latestValues }; 5897 this.initialValues = props.initial ? { ...latestValues } : {}; 5898 this.renderState = renderState; 5899 this.parent = parent; 5900 this.props = props; 5901 this.presenceContext = presenceContext; 5902 this.depth = parent ? parent.depth + 1 : 0; 5903 this.reducedMotionConfig = reducedMotionConfig; 5904 this.options = options; 5905 this.blockInitialAnimation = Boolean(blockInitialAnimation); 5906 this.isControllingVariants = isControllingVariants(props); 5907 this.isVariantNode = isVariantNode(props); 5908 if (this.isVariantNode) { 5909 this.variantChildren = new Set(); 5910 } 5911 this.manuallyAnimateOnMount = Boolean(parent && parent.current); 5912 /** 5913 * Any motion values that are provided to the element when created 5914 * aren't yet bound to the element, as this would technically be impure. 5915 * However, we iterate through the motion values and set them to the 5916 * initial values for this component. 5917 * 5918 * TODO: This is impure and we should look at changing this to run on mount. 5919 * Doing so will break some tests but this isn't necessarily a breaking change, 5920 * more a reflection of the test. 5921 */ 5922 const { willChange, ...initialMotionValues } = this.scrapeMotionValuesFromProps(props, {}, this); 5923 for (const key in initialMotionValues) { 5924 const value = initialMotionValues[key]; 5925 if (latestValues[key] !== undefined && isMotionValue(value)) { 5926 value.set(latestValues[key]); 5927 } 5928 } 5929 } 5930 mount(instance) { 5931 this.current = instance; 5932 visualElementStore.set(instance, this); 5933 if (this.projection && !this.projection.instance) { 5934 this.projection.mount(instance); 5935 } 5936 if (this.parent && this.isVariantNode && !this.isControllingVariants) { 5937 this.removeFromVariantTree = this.parent.addVariantChild(this); 5938 } 5939 this.values.forEach((value, key) => this.bindToMotionValue(key, value)); 5940 if (!hasReducedMotionListener.current) { 5941 initPrefersReducedMotion(); 5942 } 5943 this.shouldReduceMotion = 5944 this.reducedMotionConfig === "never" 5945 ? false 5946 : this.reducedMotionConfig === "always" 5947 ? true 5948 : prefersReducedMotion.current; 5949 { 5950 warnOnce(this.shouldReduceMotion !== true, "You have Reduced Motion enabled on your device. Animations may not appear as expected.", "reduced-motion-disabled"); 5951 } 5952 this.parent?.addChild(this); 5953 this.update(this.props, this.presenceContext); 5954 } 5955 unmount() { 5956 this.projection && this.projection.unmount(); 5957 cancelFrame(this.notifyUpdate); 5958 cancelFrame(this.render); 5959 this.valueSubscriptions.forEach((remove) => remove()); 5960 this.valueSubscriptions.clear(); 5961 this.removeFromVariantTree && this.removeFromVariantTree(); 5962 this.parent?.removeChild(this); 5963 for (const key in this.events) { 5964 this.events[key].clear(); 5965 } 5966 for (const key in this.features) { 5967 const feature = this.features[key]; 5968 if (feature) { 5969 feature.unmount(); 5970 feature.isMounted = false; 5971 } 5972 } 5973 this.current = null; 5974 } 5975 addChild(child) { 5976 this.children.add(child); 5977 this.enteringChildren ?? (this.enteringChildren = new Set()); 5978 this.enteringChildren.add(child); 5979 } 5980 removeChild(child) { 5981 this.children.delete(child); 5982 this.enteringChildren && this.enteringChildren.delete(child); 5983 } 5984 bindToMotionValue(key, value) { 5985 if (this.valueSubscriptions.has(key)) { 5986 this.valueSubscriptions.get(key)(); 5987 } 5988 const valueIsTransform = transformProps.has(key); 5989 if (valueIsTransform && this.onBindTransform) { 5990 this.onBindTransform(); 5991 } 5992 const removeOnChange = value.on("change", (latestValue) => { 5993 this.latestValues[key] = latestValue; 5994 this.props.onUpdate && frame.preRender(this.notifyUpdate); 5995 if (valueIsTransform && this.projection) { 5996 this.projection.isTransformDirty = true; 5997 } 5998 this.scheduleRender(); 5999 }); 6000 let removeSyncCheck; 6001 if (window.MotionCheckAppearSync) { 6002 removeSyncCheck = window.MotionCheckAppearSync(this, key, value); 6003 } 6004 this.valueSubscriptions.set(key, () => { 6005 removeOnChange(); 6006 if (removeSyncCheck) 6007 removeSyncCheck(); 6008 if (value.owner) 6009 value.stop(); 6010 }); 6011 } 6012 sortNodePosition(other) { 6013 /** 6014 * If these nodes aren't even of the same type we can't compare their depth. 6015 */ 6016 if (!this.current || 6017 !this.sortInstanceNodePosition || 6018 this.type !== other.type) { 6019 return 0; 6020 } 6021 return this.sortInstanceNodePosition(this.current, other.current); 6022 } 6023 updateFeatures() { 6024 let key = "animation"; 6025 for (key in featureDefinitions) { 6026 const featureDefinition = featureDefinitions[key]; 6027 if (!featureDefinition) 6028 continue; 6029 const { isEnabled, Feature: FeatureConstructor } = featureDefinition; 6030 /** 6031 * If this feature is enabled but not active, make a new instance. 6032 */ 6033 if (!this.features[key] && 6034 FeatureConstructor && 6035 isEnabled(this.props)) { 6036 this.features[key] = new FeatureConstructor(this); 6037 } 6038 /** 6039 * If we have a feature, mount or update it. 6040 */ 6041 if (this.features[key]) { 6042 const feature = this.features[key]; 6043 if (feature.isMounted) { 6044 feature.update(); 6045 } 6046 else { 6047 feature.mount(); 6048 feature.isMounted = true; 6049 } 6050 } 6051 } 6052 } 6053 triggerBuild() { 6054 this.build(this.renderState, this.latestValues, this.props); 6055 } 6056 /** 6057 * Measure the current viewport box with or without transforms. 6058 * Only measures axis-aligned boxes, rotate and skew must be manually 6059 * removed with a re-render to work. 6060 */ 6061 measureViewportBox() { 6062 return this.current 6063 ? this.measureInstanceViewportBox(this.current, this.props) 6064 : createBox(); 6065 } 6066 getStaticValue(key) { 6067 return this.latestValues[key]; 6068 } 6069 setStaticValue(key, value) { 6070 this.latestValues[key] = value; 6071 } 6072 /** 6073 * Update the provided props. Ensure any newly-added motion values are 6074 * added to our map, old ones removed, and listeners updated. 6075 */ 6076 update(props, presenceContext) { 6077 if (props.transformTemplate || this.props.transformTemplate) { 6078 this.scheduleRender(); 6079 } 6080 this.prevProps = this.props; 6081 this.props = props; 6082 this.prevPresenceContext = this.presenceContext; 6083 this.presenceContext = presenceContext; 6084 /** 6085 * Update prop event handlers ie onAnimationStart, onAnimationComplete 6086 */ 6087 for (let i = 0; i < propEventHandlers.length; i++) { 6088 const key = propEventHandlers[i]; 6089 if (this.propEventSubscriptions[key]) { 6090 this.propEventSubscriptions[key](); 6091 delete this.propEventSubscriptions[key]; 6092 } 6093 const listenerName = ("on" + key); 6094 const listener = props[listenerName]; 6095 if (listener) { 6096 this.propEventSubscriptions[key] = this.on(key, listener); 6097 } 6098 } 6099 this.prevMotionValues = updateMotionValuesFromProps(this, this.scrapeMotionValuesFromProps(props, this.prevProps, this), this.prevMotionValues); 6100 if (this.handleChildMotionValue) { 6101 this.handleChildMotionValue(); 6102 } 6103 } 6104 getProps() { 6105 return this.props; 6106 } 6107 /** 6108 * Returns the variant definition with a given name. 6109 */ 6110 getVariant(name) { 6111 return this.props.variants ? this.props.variants[name] : undefined; 6112 } 6113 /** 6114 * Returns the defined default transition on this component. 6115 */ 6116 getDefaultTransition() { 6117 return this.props.transition; 6118 } 6119 getTransformPagePoint() { 6120 return this.props.transformPagePoint; 6121 } 6122 getClosestVariantNode() { 6123 return this.isVariantNode 6124 ? this 6125 : this.parent 6126 ? this.parent.getClosestVariantNode() 6127 : undefined; 6128 } 6129 /** 6130 * Add a child visual element to our set of children. 6131 */ 6132 addVariantChild(child) { 6133 const closestVariantNode = this.getClosestVariantNode(); 6134 if (closestVariantNode) { 6135 closestVariantNode.variantChildren && 6136 closestVariantNode.variantChildren.add(child); 6137 return () => closestVariantNode.variantChildren.delete(child); 6138 } 6139 } 6140 /** 6141 * Add a motion value and bind it to this visual element. 6142 */ 6143 addValue(key, value) { 6144 // Remove existing value if it exists 6145 const existingValue = this.values.get(key); 6146 if (value !== existingValue) { 6147 if (existingValue) 6148 this.removeValue(key); 6149 this.bindToMotionValue(key, value); 6150 this.values.set(key, value); 6151 this.latestValues[key] = value.get(); 6152 } 6153 } 6154 /** 6155 * Remove a motion value and unbind any active subscriptions. 6156 */ 6157 removeValue(key) { 6158 this.values.delete(key); 6159 const unsubscribe = this.valueSubscriptions.get(key); 6160 if (unsubscribe) { 6161 unsubscribe(); 6162 this.valueSubscriptions.delete(key); 6163 } 6164 delete this.latestValues[key]; 6165 this.removeValueFromRenderState(key, this.renderState); 6166 } 6167 /** 6168 * Check whether we have a motion value for this key 6169 */ 6170 hasValue(key) { 6171 return this.values.has(key); 6172 } 6173 getValue(key, defaultValue) { 6174 if (this.props.values && this.props.values[key]) { 6175 return this.props.values[key]; 6176 } 6177 let value = this.values.get(key); 6178 if (value === undefined && defaultValue !== undefined) { 6179 value = motionValue(defaultValue === null ? undefined : defaultValue, { owner: this }); 6180 this.addValue(key, value); 6181 } 6182 return value; 6183 } 6184 /** 6185 * If we're trying to animate to a previously unencountered value, 6186 * we need to check for it in our state and as a last resort read it 6187 * directly from the instance (which might have performance implications). 6188 */ 6189 readValue(key, target) { 6190 let value = this.latestValues[key] !== undefined || !this.current 6191 ? this.latestValues[key] 6192 : this.getBaseTargetFromProps(this.props, key) ?? 6193 this.readValueFromInstance(this.current, key, this.options); 6194 if (value !== undefined && value !== null) { 6195 if (typeof value === "string" && 6196 (isNumericalString(value) || isZeroValueString(value))) { 6197 // If this is a number read as a string, ie "0" or "200", convert it to a number 6198 value = parseFloat(value); 6199 } 6200 else if (!findValueType(value) && complex.test(target)) { 6201 value = getAnimatableNone(key, target); 6202 } 6203 this.setBaseTarget(key, isMotionValue(value) ? value.get() : value); 6204 } 6205 return isMotionValue(value) ? value.get() : value; 6206 } 6207 /** 6208 * Set the base target to later animate back to. This is currently 6209 * only hydrated on creation and when we first read a value. 6210 */ 6211 setBaseTarget(key, value) { 6212 this.baseTarget[key] = value; 6213 } 6214 /** 6215 * Find the base target for a value thats been removed from all animation 6216 * props. 6217 */ 6218 getBaseTarget(key) { 6219 const { initial } = this.props; 6220 let valueFromInitial; 6221 if (typeof initial === "string" || typeof initial === "object") { 6222 const variant = resolveVariantFromProps(this.props, initial, this.presenceContext?.custom); 6223 if (variant) { 6224 valueFromInitial = variant[key]; 6225 } 6226 } 6227 /** 6228 * If this value still exists in the current initial variant, read that. 6229 */ 6230 if (initial && valueFromInitial !== undefined) { 6231 return valueFromInitial; 6232 } 6233 /** 6234 * Alternatively, if this VisualElement config has defined a getBaseTarget 6235 * so we can read the value from an alternative source, try that. 6236 */ 6237 const target = this.getBaseTargetFromProps(this.props, key); 6238 if (target !== undefined && !isMotionValue(target)) 6239 return target; 6240 /** 6241 * If the value was initially defined on initial, but it doesn't any more, 6242 * return undefined. Otherwise return the value as initially read from the DOM. 6243 */ 6244 return this.initialValues[key] !== undefined && 6245 valueFromInitial === undefined 6246 ? undefined 6247 : this.baseTarget[key]; 6248 } 6249 on(eventName, callback) { 6250 if (!this.events[eventName]) { 6251 this.events[eventName] = new SubscriptionManager(); 6252 } 6253 return this.events[eventName].add(callback); 6254 } 6255 notify(eventName, ...args) { 6256 if (this.events[eventName]) { 6257 this.events[eventName].notify(...args); 6258 } 6259 } 6260 scheduleRenderMicrotask() { 6261 microtask.render(this.render); 6262 } 6263 } 6264 6265 class DOMVisualElement extends VisualElement { 6266 constructor() { 6267 super(...arguments); 6268 this.KeyframeResolver = DOMKeyframesResolver; 6269 } 6270 sortInstanceNodePosition(a, b) { 6271 /** 6272 * compareDocumentPosition returns a bitmask, by using the bitwise & 6273 * we're returning true if 2 in that bitmask is set to true. 2 is set 6274 * to true if b preceeds a. 6275 */ 6276 return a.compareDocumentPosition(b) & 2 ? 1 : -1; 6277 } 6278 getBaseTargetFromProps(props, key) { 6279 return props.style 6280 ? props.style[key] 6281 : undefined; 6282 } 6283 removeValueFromRenderState(key, { vars, style }) { 6284 delete vars[key]; 6285 delete style[key]; 6286 } 6287 handleChildMotionValue() { 6288 if (this.childSubscription) { 6289 this.childSubscription(); 6290 delete this.childSubscription; 6291 } 6292 const { children } = this.props; 6293 if (isMotionValue(children)) { 6294 this.childSubscription = children.on("change", (latest) => { 6295 if (this.current) { 6296 this.current.textContent = `${latest}`; 6297 } 6298 }); 6299 } 6300 } 6301 } 6302 6303 const translateAlias = { 6304 x: "translateX", 6305 y: "translateY", 6306 z: "translateZ", 6307 transformPerspective: "perspective", 6308 }; 6309 const numTransforms = transformPropOrder.length; 6310 /** 6311 * Build a CSS transform style from individual x/y/scale etc properties. 6312 * 6313 * This outputs with a default order of transforms/scales/rotations, this can be customised by 6314 * providing a transformTemplate function. 6315 */ 6316 function buildTransform(latestValues, transform, transformTemplate) { 6317 // The transform string we're going to build into. 6318 let transformString = ""; 6319 let transformIsDefault = true; 6320 /** 6321 * Loop over all possible transforms in order, adding the ones that 6322 * are present to the transform string. 6323 */ 6324 for (let i = 0; i < numTransforms; i++) {
vendor: 8,600 bytes, lines 6325-6548
6325 const key = transformPropOrder[i]; 6326 const value = latestValues[key]; 6327 if (value === undefined) 6328 continue; 6329 let valueIsDefault = true; 6330 if (typeof value === "number") { 6331 valueIsDefault = value === (key.startsWith("scale") ? 1 : 0); 6332 } 6333 else { 6334 valueIsDefault = parseFloat(value) === 0; 6335 } 6336 if (!valueIsDefault || transformTemplate) { 6337 const valueAsType = getValueAsType(value, numberValueTypes[key]); 6338 if (!valueIsDefault) { 6339 transformIsDefault = false; 6340 const transformName = translateAlias[key] || key; 6341 transformString += `${transformName}(${valueAsType}) `; 6342 } 6343 if (transformTemplate) { 6344 transform[key] = valueAsType; 6345 } 6346 } 6347 } 6348 transformString = transformString.trim(); 6349 // If we have a custom `transform` template, pass our transform values and 6350 // generated transformString to that before returning 6351 if (transformTemplate) { 6352 transformString = transformTemplate(transform, transformIsDefault ? "" : transformString); 6353 } 6354 else if (transformIsDefault) { 6355 transformString = "none"; 6356 } 6357 return transformString; 6358 } 6359 6360 function buildHTMLStyles(state, latestValues, transformTemplate) { 6361 const { style, vars, transformOrigin } = state; 6362 // Track whether we encounter any transform or transformOrigin values. 6363 let hasTransform = false; 6364 let hasTransformOrigin = false; 6365 /** 6366 * Loop over all our latest animated values and decide whether to handle them 6367 * as a style or CSS variable. 6368 * 6369 * Transforms and transform origins are kept separately for further processing. 6370 */ 6371 for (const key in latestValues) { 6372 const value = latestValues[key]; 6373 if (transformProps.has(key)) { 6374 // If this is a transform, flag to enable further transform processing 6375 hasTransform = true; 6376 continue; 6377 } 6378 else if (isCSSVariableName(key)) { 6379 vars[key] = value; 6380 continue; 6381 } 6382 else { 6383 // Convert the value to its default value type, ie 0 -> "0px" 6384 const valueAsType = getValueAsType(value, numberValueTypes[key]); 6385 if (key.startsWith("origin")) { 6386 // If this is a transform origin, flag and enable further transform-origin processing 6387 hasTransformOrigin = true; 6388 transformOrigin[key] = 6389 valueAsType; 6390 } 6391 else { 6392 style[key] = valueAsType; 6393 } 6394 } 6395 } 6396 if (!latestValues.transform) { 6397 if (hasTransform || transformTemplate) { 6398 style.transform = buildTransform(latestValues, state.transform, transformTemplate); 6399 } 6400 else if (style.transform) { 6401 /** 6402 * If we have previously created a transform but currently don't have any, 6403 * reset transform style to none. 6404 */ 6405 style.transform = "none"; 6406 } 6407 } 6408 /** 6409 * Build a transformOrigin style. Uses the same defaults as the browser for 6410 * undefined origins. 6411 */ 6412 if (hasTransformOrigin) { 6413 const { originX = "50%", originY = "50%", originZ = 0, } = transformOrigin; 6414 style.transformOrigin = `${originX} ${originY} ${originZ}`; 6415 } 6416 } 6417 6418 function renderHTML(element, { style, vars }, styleProp, projection) { 6419 const elementStyle = element.style; 6420 let key; 6421 for (key in style) { 6422 // CSSStyleDeclaration has [index: number]: string; in the types, so we use that as key type. 6423 elementStyle[key] = style[key]; 6424 } 6425 // Write projection styles directly to element style 6426 projection?.applyProjectionStyles(elementStyle, styleProp); 6427 for (key in vars) { 6428 // Loop over any CSS variables and assign those. 6429 // They can only be assigned using `setProperty`. 6430 elementStyle.setProperty(key, vars[key]); 6431 } 6432 } 6433 6434 const scaleCorrectors = {}; 6435 6436 function isForcedMotionValue(key, { layout, layoutId }) { 6437 return (transformProps.has(key) || 6438 key.startsWith("origin") || 6439 ((layout || layoutId !== undefined) && 6440 (!!scaleCorrectors[key] || key === "opacity"))); 6441 } 6442 6443 function scrapeMotionValuesFromProps$1(props, prevProps, visualElement) { 6444 const { style } = props; 6445 const newValues = {}; 6446 for (const key in style) { 6447 if (isMotionValue(style[key]) || 6448 (prevProps.style && 6449 isMotionValue(prevProps.style[key])) || 6450 isForcedMotionValue(key, props) || 6451 visualElement?.getValue(key)?.liveStyle !== undefined) { 6452 newValues[key] = style[key]; 6453 } 6454 } 6455 return newValues; 6456 } 6457 6458 function getComputedStyle$1(element) { 6459 return window.getComputedStyle(element); 6460 } 6461 class HTMLVisualElement extends DOMVisualElement { 6462 constructor() { 6463 super(...arguments); 6464 this.type = "html"; 6465 this.renderInstance = renderHTML; 6466 } 6467 readValueFromInstance(instance, key) { 6468 if (transformProps.has(key)) { 6469 return this.projection?.isProjecting 6470 ? defaultTransformValue(key) 6471 : readTransformValue(instance, key); 6472 } 6473 else { 6474 const computedStyle = getComputedStyle$1(instance); 6475 const value = (isCSSVariableName(key) 6476 ? computedStyle.getPropertyValue(key) 6477 : computedStyle[key]) || 0; 6478 return typeof value === "string" ? value.trim() : value; 6479 } 6480 } 6481 measureInstanceViewportBox(instance, { transformPagePoint }) { 6482 return measureViewportBox(instance, transformPagePoint); 6483 } 6484 build(renderState, latestValues, props) { 6485 buildHTMLStyles(renderState, latestValues, props.transformTemplate); 6486 } 6487 scrapeMotionValuesFromProps(props, prevProps, visualElement) { 6488 return scrapeMotionValuesFromProps$1(props, prevProps, visualElement); 6489 } 6490 } 6491 6492 function isObjectKey(key, object) { 6493 return key in object; 6494 } 6495 class ObjectVisualElement extends VisualElement { 6496 constructor() { 6497 super(...arguments); 6498 this.type = "object"; 6499 } 6500 readValueFromInstance(instance, key) { 6501 if (isObjectKey(key, instance)) { 6502 const value = instance[key]; 6503 if (typeof value === "string" || typeof value === "number") { 6504 return value; 6505 } 6506 } 6507 return undefined; 6508 } 6509 getBaseTargetFromProps() { 6510 return undefined; 6511 } 6512 removeValueFromRenderState(key, renderState) { 6513 delete renderState.output[key]; 6514 } 6515 measureInstanceViewportBox() { 6516 return createBox(); 6517 } 6518 build(renderState, latestValues) { 6519 Object.assign(renderState.output, latestValues); 6520 } 6521 renderInstance(instance, { output }) { 6522 Object.assign(instance, output); 6523 } 6524 sortInstanceNodePosition() { 6525 return 0; 6526 } 6527 } 6528 6529 const dashKeys = { 6530 offset: "stroke-dashoffset", 6531 array: "stroke-dasharray", 6532 }; 6533 const camelKeys = { 6534 offset: "strokeDashoffset", 6535 array: "strokeDasharray", 6536 }; 6537 /** 6538 * Build SVG path properties. Uses the path's measured length to convert 6539 * our custom pathLength, pathSpacing and pathOffset into stroke-dashoffset 6540 * and stroke-dasharray attributes. 6541 * 6542 * This function is mutative to reduce per-frame GC. 6543 */ 6544 function buildSVGPath(attrs, length, spacing = 1, offset = 0, useDashCase = true) { 6545 // Normalise path length by setting SVG attribute pathLength to 1 6546 attrs.pathLength = 1; 6547 // We use dash case when setting attributes directly to the DOM node and camel case 6548 // when defining props on a React component.
vendor: 3,158 bytes, lines 6549-6641
6549 const keys = useDashCase ? dashKeys : camelKeys; 6550 // Build the dash offset 6551 attrs[keys.offset] = px.transform(-offset); 6552 // Build the dash array 6553 const pathLength = px.transform(length); 6554 const pathSpacing = px.transform(spacing); 6555 attrs[keys.array] = `${pathLength} ${pathSpacing}`; 6556 } 6557 6558 /** 6559 * Build SVG visual attributes, like cx and style.transform 6560 */ 6561 function buildSVGAttrs(state, { attrX, attrY, attrScale, pathLength, pathSpacing = 1, pathOffset = 0, 6562 // This is object creation, which we try to avoid per-frame. 6563 ...latest }, isSVGTag, transformTemplate, styleProp) { 6564 buildHTMLStyles(state, latest, transformTemplate); 6565 /** 6566 * For svg tags we just want to make sure viewBox is animatable and treat all the styles 6567 * as normal HTML tags. 6568 */ 6569 if (isSVGTag) { 6570 if (state.style.viewBox) { 6571 state.attrs.viewBox = state.style.viewBox; 6572 } 6573 return; 6574 } 6575 state.attrs = state.style; 6576 state.style = {}; 6577 const { attrs, style } = state; 6578 /** 6579 * However, we apply transforms as CSS transforms. 6580 * So if we detect a transform, transformOrigin we take it from attrs and copy it into style. 6581 */ 6582 if (attrs.transform) { 6583 style.transform = attrs.transform; 6584 delete attrs.transform; 6585 } 6586 if (style.transform || attrs.transformOrigin) { 6587 style.transformOrigin = attrs.transformOrigin ?? "50% 50%"; 6588 delete attrs.transformOrigin; 6589 } 6590 if (style.transform) { 6591 /** 6592 * SVG's element transform-origin uses its own median as a reference. 6593 * Therefore, transformBox becomes a fill-box 6594 */ 6595 style.transformBox = styleProp?.transformBox ?? "fill-box"; 6596 delete attrs.transformBox; 6597 } 6598 // Render attrX/attrY/attrScale as attributes 6599 if (attrX !== undefined) 6600 attrs.x = attrX; 6601 if (attrY !== undefined) 6602 attrs.y = attrY; 6603 if (attrScale !== undefined) 6604 attrs.scale = attrScale; 6605 // Build SVG path if one has been defined 6606 if (pathLength !== undefined) { 6607 buildSVGPath(attrs, pathLength, pathSpacing, pathOffset, false); 6608 } 6609 } 6610 6611 /** 6612 * A set of attribute names that are always read/written as camel case. 6613 */ 6614 const camelCaseAttributes = new Set([ 6615 "baseFrequency", 6616 "diffuseConstant", 6617 "kernelMatrix", 6618 "kernelUnitLength", 6619 "keySplines", 6620 "keyTimes", 6621 "limitingConeAngle", 6622 "markerHeight", 6623 "markerWidth", 6624 "numOctaves", 6625 "targetX", 6626 "targetY", 6627 "surfaceScale", 6628 "specularConstant", 6629 "specularExponent", 6630 "stdDeviation", 6631 "tableValues", 6632 "viewBox", 6633 "gradientTransform", 6634 "pathLength", 6635 "startOffset", 6636 "textLength", 6637 "lengthAdjust", 6638 ]); 6639 6640 const isSVGTag = (tag) => typeof tag === "string" && tag.toLowerCase() === "svg"; 6641
6642 function renderSVG(element, renderState, _styleProp, projection) { 6643 renderHTML(element, renderState, undefined, projection); 6644 for (const key in renderState.attrs) { 6645 element.setAttribute(!camelCaseAttributes.has(key) ? camelToDash(key) : key, renderState.attrs[key]); 6646 } 6647 } 6648 6649 function scrapeMotionValuesFromProps(props, prevProps, visualElement) { 6650 const newValues = scrapeMotionValuesFromProps$1(props, prevProps, visualElement); 6651 for (const key in props) { 6652 if (isMotionValue(props[key]) || 6653 isMotionValue(prevProps[key])) { 6654 const targetKey = transformPropOrder.indexOf(key) !== -1 6655 ? "attr" + key.charAt(0).toUpperCase() + key.substring(1) 6656 : key; 6657 newValues[targetKey] = props[key]; 6658 } 6659 } 6660 return newValues; 6661 } 6662 6663 class SVGVisualElement extends DOMVisualElement { 6664 constructor() { 6665 super(...arguments); 6666 this.type = "svg"; 6667 this.isSVGTag = false; 6668 this.measureInstanceViewportBox = createBox; 6669 } 6670 getBaseTargetFromProps(props, key) { 6671 return props[key]; 6672 } 6673 readValueFromInstance(instance, key) { 6674 if (transformProps.has(key)) { 6675 const defaultType = getDefaultValueType(key); 6676 return defaultType ? defaultType.default || 0 : 0; 6677 } 6678 key = !camelCaseAttributes.has(key) ? camelToDash(key) : key; 6679 return instance.getAttribute(key); 6680 } 6681 scrapeMotionValuesFromProps(props, prevProps, visualElement) { 6682 return scrapeMotionValuesFromProps(props, prevProps, visualElement); 6683 } 6684 build(renderState, latestValues, props) { 6685 buildSVGAttrs(renderState, latestValues, this.isSVGTag, props.transformTemplate, props.style); 6686 } 6687 renderInstance(instance, renderState, styleProp, projection) { 6688 renderSVG(instance, renderState, styleProp, projection); 6689 } 6690 mount(instance) { 6691 this.isSVGTag = isSVGTag(instance.tagName); 6692 super.mount(instance); 6693 } 6694 } 6695 6696 function createDOMVisualElement(element) { 6697 const options = { 6698 presenceContext: null, 6699 props: {}, 6700 visualState: { 6701 renderState: { 6702 transform: {}, 6703 transformOrigin: {}, 6704 style: {}, 6705 vars: {}, 6706 attrs: {}, 6707 }, 6708 latestValues: {}, 6709 }, 6710 }; 6711 const node = isSVGElement(element) && !isSVGSVGElement(element) 6712 ? new SVGVisualElement(options) 6713 : new HTMLVisualElement(options); 6714 node.mount(element); 6715 visualElementStore.set(element, node); 6716 } 6717 function createObjectVisualElement(subject) { 6718 const options = { 6719 presenceContext: null, 6720 props: {}, 6721 visualState: { 6722 renderState: { 6723 output: {}, 6724 }, 6725 latestValues: {}, 6726 }, 6727 }; 6728 const node = new ObjectVisualElement(options); 6729 node.mount(subject); 6730 visualElementStore.set(subject, node); 6731 } 6732 6733 function animateSingleValue(value, keyframes, options) { 6734 const motionValue$1 = isMotionValue(value) ? value : motionValue(value); 6735 motionValue$1.start(animateMotionValue("", motionValue$1, keyframes, options)); 6736 return motionValue$1.animation; 6737 } 6738 6739 function isSingleValue(subject, keyframes) { 6740 return (isMotionValue(subject) || 6741 typeof subject === "number" || 6742 (typeof subject === "string" && !isDOMKeyframes(keyframes))); 6743 } 6744 /** 6745 * Implementation 6746 */ 6747 function animateSubject(subject, keyframes, options, scope) { 6748 const animations = []; 6749 if (isSingleValue(subject, keyframes)) { 6750 animations.push(animateSingleValue(subject, isDOMKeyframes(keyframes) 6751 ? keyframes.default || keyframes
6752 : keyframes, options ? options.default || options : options)); 6753 } 6754 else { 6755 const subjects = resolveSubjects(subject, keyframes, scope); 6756 const numSubjects = subjects.length; 6757 exports.invariant(Boolean(numSubjects), "No valid elements provided.", "no-valid-elements"); 6758 for (let i = 0; i < numSubjects; i++) { 6759 const thisSubject = subjects[i]; 6760 exports.invariant(thisSubject !== null, "You're trying to perform an animation on null. Ensure that selectors are correctly finding elements and refs are correctly hydrated.", "animate-null"); 6761 const createVisualElement = thisSubject instanceof Element 6762 ? createDOMVisualElement 6763 : createObjectVisualElement; 6764 if (!visualElementStore.has(thisSubject)) { 6765 createVisualElement(thisSubject); 6766 } 6767 const visualElement = visualElementStore.get(thisSubject); 6768 const transition = { ...options }; 6769 /** 6770 * Resolve stagger function if provided. 6771 */ 6772 if ("delay" in transition && 6773 typeof transition.delay === "function") { 6774 transition.delay = transition.delay(i, numSubjects); 6775 } 6776 animations.push(...animateTarget(visualElement, { ...keyframes, transition }, {})); 6777 } 6778 } 6779 return animations; 6780 } 6781 6782 function animateSequence(sequence, options, scope) { 6783 const animations = []; 6784 const animationDefinitions = createAnimationsFromSequence(sequence, options, scope, { spring }); 6785 animationDefinitions.forEach(({ keyframes, transition }, subject) => { 6786 animations.push(...animateSubject(subject, keyframes, transition)); 6787 }); 6788 return animations; 6789 } 6790 6791 function isSequence(value) { 6792 return Array.isArray(value) && value.some(Array.isArray); 6793 } 6794 /** 6795 * Creates an animation function that is optionally scoped 6796 * to a specific element. 6797 */ 6798 function createScopedAnimate(scope) { 6799 /** 6800 * Implementation 6801 */ 6802 function scopedAnimate(subjectOrSequence, optionsOrKeyframes, options) { 6803 let animations = []; 6804 let animationOnComplete; 6805 if (isSequence(subjectOrSequence)) { 6806 animations = animateSequence(subjectOrSequence, optionsOrKeyframes, scope); 6807 } 6808 else { 6809 // Extract top-level onComplete so it doesn't get applied per-value 6810 const { onComplete, ...rest } = options || {}; 6811 if (typeof onComplete === "function") { 6812 animationOnComplete = onComplete; 6813 } 6814 animations = animateSubject(subjectOrSequence, optionsOrKeyframes, rest, scope); 6815 } 6816 const animation = new GroupAnimationWithThen(animations); 6817 if (animationOnComplete) { 6818 animation.finished.then(animationOnComplete); 6819 } 6820 if (scope) { 6821 scope.animations.push(animation); 6822 animation.finished.then(() => { 6823 removeItem(scope.animations, animation); 6824 }); 6825 } 6826 return animation; 6827 } 6828 return scopedAnimate; 6829 } 6830 const animate = createScopedAnimate(); 6831 6832 function animateElements(elementOrSelector, keyframes, options, scope) { 6833 const elements = resolveElements(elementOrSelector, scope); 6834 const numElements = elements.length; 6835 exports.invariant(Boolean(numElements), "No valid elements provided.", "no-valid-elements"); 6836 /** 6837 * WAAPI doesn't support interrupting animations. 6838 * 6839 * Therefore, starting animations requires a three-step process: 6840 * 1. Stop existing animations (write styles to DOM) 6841 * 2. Resolve keyframes (read styles from DOM) 6842 * 3. Create new animations (write styles to DOM) 6843 * 6844 * The hybrid `animate()` function uses AsyncAnimation to resolve 6845 * keyframes before creating new animations, which removes style 6846 * thrashing. Here, we have much stricter filesize constraints. 6847 * Therefore we do this in a synchronous way that ensures that 6848 * at least within `animate()` calls there is no style thrashing. 6849 * 6850 * In the motion-native-animate-mini-interrupt benchmark this 6851 * was 80% faster than a single loop. 6852 */ 6853 const animationDefinitions = []; 6854 /** 6855 * Step 1: Build options and stop existing animations (write) 6856 */ 6857 for (let i = 0; i < numElements; i++) { 6858 const element = elements[i]; 6859 const elementTransition = { ...options }; 6860 /** 6861 * Resolve stagger function if provided. 6862 */ 6863 if (typeof elementTransition.delay === "function") { 6864 elementTransition.delay = elementTransition.delay(i, numElements); 6865 } 6866 for (const valueName in keyframes) { 6867 let valueKeyframes = keyframes[valueName]; 6868 if (!Array.isArray(valueKeyframes)) { 6869 valueKeyframes = [valueKeyframes]; 6870 } 6871 const valueOptions = { 6872 ...getValueTransition$1(elementTransition, valueName), 6873 }; 6874 valueOptions.duration && (valueOptions.duration = secondsToMilliseconds(valueOptions.duration)); 6875 valueOptions.delay && (valueOptions.delay = secondsToMilliseconds(valueOptions.delay)); 6876 /** 6877 * If there's an existing animation playing on this element then stop it 6878 * before creating a new one. 6879 */ 6880 const map = getAnimationMap(element);
vendor: 10,918 bytes, lines 6881-7168
6881 const key = animationMapKey(valueName, valueOptions.pseudoElement || ""); 6882 const currentAnimation = map.get(key); 6883 currentAnimation && currentAnimation.stop(); 6884 animationDefinitions.push({ 6885 map, 6886 key, 6887 unresolvedKeyframes: valueKeyframes, 6888 options: { 6889 ...valueOptions, 6890 element, 6891 name: valueName, 6892 allowFlatten: !elementTransition.type && !elementTransition.ease, 6893 }, 6894 }); 6895 } 6896 } 6897 /** 6898 * Step 2: Resolve keyframes (read) 6899 */ 6900 for (let i = 0; i < animationDefinitions.length; i++) { 6901 const { unresolvedKeyframes, options: animationOptions } = animationDefinitions[i]; 6902 const { element, name, pseudoElement } = animationOptions; 6903 if (!pseudoElement && unresolvedKeyframes[0] === null) { 6904 unresolvedKeyframes[0] = getComputedStyle$2(element, name); 6905 } 6906 fillWildcards(unresolvedKeyframes); 6907 applyPxDefaults(unresolvedKeyframes, name); 6908 /** 6909 * If we only have one keyframe, explicitly read the initial keyframe 6910 * from the computed style. This is to ensure consistency with WAAPI behaviour 6911 * for restarting animations, for instance .play() after finish, when it 6912 * has one vs two keyframes. 6913 */ 6914 if (!pseudoElement && unresolvedKeyframes.length < 2) { 6915 unresolvedKeyframes.unshift(getComputedStyle$2(element, name)); 6916 } 6917 animationOptions.keyframes = unresolvedKeyframes; 6918 } 6919 /** 6920 * Step 3: Create new animations (write) 6921 */ 6922 const animations = []; 6923 for (let i = 0; i < animationDefinitions.length; i++) { 6924 const { map, key, options: animationOptions } = animationDefinitions[i]; 6925 const animation = new NativeAnimation(animationOptions); 6926 map.set(key, animation); 6927 animation.finished.finally(() => map.delete(key)); 6928 animations.push(animation); 6929 } 6930 return animations; 6931 } 6932 6933 const createScopedWaapiAnimate = (scope) => { 6934 function scopedAnimate(elementOrSelector, keyframes, options) { 6935 return new GroupAnimationWithThen(animateElements(elementOrSelector, keyframes, options, scope)); 6936 } 6937 return scopedAnimate; 6938 }; 6939 const animateMini = /*@__PURE__*/ createScopedWaapiAnimate(); 6940 6941 /** 6942 * A time in milliseconds, beyond which we consider the scroll velocity to be 0. 6943 */ 6944 const maxElapsed = 50; 6945 const createAxisInfo = () => ({ 6946 current: 0, 6947 offset: [], 6948 progress: 0, 6949 scrollLength: 0, 6950 targetOffset: 0, 6951 targetLength: 0, 6952 containerLength: 0, 6953 velocity: 0, 6954 }); 6955 const createScrollInfo = () => ({ 6956 time: 0, 6957 x: createAxisInfo(), 6958 y: createAxisInfo(), 6959 }); 6960 const keys = { 6961 x: { 6962 length: "Width", 6963 position: "Left", 6964 }, 6965 y: { 6966 length: "Height", 6967 position: "Top", 6968 }, 6969 }; 6970 function updateAxisInfo(element, axisName, info, time) { 6971 const axis = info[axisName]; 6972 const { length, position } = keys[axisName]; 6973 const prev = axis.current; 6974 const prevTime = info.time; 6975 axis.current = element[`scroll${position}`]; 6976 axis.scrollLength = element[`scroll${length}`] - element[`client${length}`]; 6977 axis.offset.length = 0; 6978 axis.offset[0] = 0; 6979 axis.offset[1] = axis.scrollLength; 6980 axis.progress = progress(0, axis.scrollLength, axis.current); 6981 const elapsed = time - prevTime; 6982 axis.velocity = 6983 elapsed > maxElapsed 6984 ? 0 6985 : velocityPerSecond(axis.current - prev, elapsed); 6986 } 6987 function updateScrollInfo(element, info, time) { 6988 updateAxisInfo(element, "x", info, time); 6989 updateAxisInfo(element, "y", info, time); 6990 info.time = time; 6991 } 6992 6993 function calcInset(element, container) { 6994 const inset = { x: 0, y: 0 }; 6995 let current = element; 6996 while (current && current !== container) { 6997 if (isHTMLElement(current)) { 6998 inset.x += current.offsetLeft; 6999 inset.y += current.offsetTop; 7000 current = current.offsetParent; 7001 } 7002 else if (current.tagName === "svg") { 7003 /** 7004 * This isn't an ideal approach to measuring the offset of <svg /> tags. 7005 * It would be preferable, given they behave like HTMLElements in most ways 7006 * to use offsetLeft/Top. But these don't exist on <svg />. Likewise we 7007 * can't use .getBBox() like most SVG elements as these provide the offset 7008 * relative to the SVG itself, which for <svg /> is usually 0x0. 7009 */ 7010 const svgBoundingBox = current.getBoundingClientRect(); 7011 current = current.parentElement; 7012 const parentBoundingBox = current.getBoundingClientRect(); 7013 inset.x += svgBoundingBox.left - parentBoundingBox.left; 7014 inset.y += svgBoundingBox.top - parentBoundingBox.top; 7015 } 7016 else if (current instanceof SVGGraphicsElement) { 7017 const { x, y } = current.getBBox(); 7018 inset.x += x; 7019 inset.y += y; 7020 let svg = null; 7021 let parent = current.parentNode; 7022 while (!svg) { 7023 if (parent.tagName === "svg") { 7024 svg = parent; 7025 } 7026 parent = current.parentNode; 7027 } 7028 current = svg; 7029 } 7030 else { 7031 break; 7032 } 7033 } 7034 return inset; 7035 } 7036 7037 const namedEdges = { 7038 start: 0, 7039 center: 0.5, 7040 end: 1, 7041 }; 7042 function resolveEdge(edge, length, inset = 0) { 7043 let delta = 0; 7044 /** 7045 * If we have this edge defined as a preset, replace the definition 7046 * with the numerical value. 7047 */ 7048 if (edge in namedEdges) { 7049 edge = namedEdges[edge]; 7050 } 7051 /** 7052 * Handle unit values 7053 */ 7054 if (typeof edge === "string") { 7055 const asNumber = parseFloat(edge); 7056 if (edge.endsWith("px")) { 7057 delta = asNumber; 7058 } 7059 else if (edge.endsWith("%")) { 7060 edge = asNumber / 100; 7061 } 7062 else if (edge.endsWith("vw")) { 7063 delta = (asNumber / 100) * document.documentElement.clientWidth; 7064 } 7065 else if (edge.endsWith("vh")) { 7066 delta = (asNumber / 100) * document.documentElement.clientHeight; 7067 } 7068 else { 7069 edge = asNumber; 7070 } 7071 } 7072 /** 7073 * If the edge is defined as a number, handle as a progress value. 7074 */ 7075 if (typeof edge === "number") { 7076 delta = length * edge; 7077 } 7078 return inset + delta; 7079 } 7080 7081 const defaultOffset = [0, 0]; 7082 function resolveOffset(offset, containerLength, targetLength, targetInset) { 7083 let offsetDefinition = Array.isArray(offset) ? offset : defaultOffset; 7084 let targetPoint = 0; 7085 let containerPoint = 0; 7086 if (typeof offset === "number") { 7087 /** 7088 * If we're provided offset: [0, 0.5, 1] then each number x should become 7089 * [x, x], so we default to the behaviour of mapping 0 => 0 of both target 7090 * and container etc. 7091 */ 7092 offsetDefinition = [offset, offset]; 7093 } 7094 else if (typeof offset === "string") { 7095 offset = offset.trim(); 7096 if (offset.includes(" ")) { 7097 offsetDefinition = offset.split(" "); 7098 } 7099 else { 7100 /** 7101 * If we're provided a definition like "100px" then we want to apply 7102 * that only to the top of the target point, leaving the container at 0. 7103 * Whereas a named offset like "end" should be applied to both. 7104 */ 7105 offsetDefinition = [offset, namedEdges[offset] ? offset : `0`]; 7106 } 7107 } 7108 targetPoint = resolveEdge(offsetDefinition[0], targetLength, targetInset); 7109 containerPoint = resolveEdge(offsetDefinition[1], containerLength); 7110 return targetPoint - containerPoint; 7111 } 7112 7113 const ScrollOffset = { 7114 Enter: [ 7115 [0, 1], 7116 [1, 1], 7117 ], 7118 Exit: [ 7119 [0, 0], 7120 [1, 0], 7121 ], 7122 Any: [ 7123 [1, 0], 7124 [0, 1], 7125 ], 7126 All: [ 7127 [0, 0], 7128 [1, 1], 7129 ], 7130 }; 7131 7132 const point = { x: 0, y: 0 }; 7133 function getTargetSize(target) { 7134 return "getBBox" in target && target.tagName !== "svg" 7135 ? target.getBBox() 7136 : { width: target.clientWidth, height: target.clientHeight }; 7137 } 7138 function resolveOffsets(container, info, options) { 7139 const { offset: offsetDefinition = ScrollOffset.All } = options; 7140 const { target = container, axis = "y" } = options; 7141 const lengthLabel = axis === "y" ? "height" : "width"; 7142 const inset = target !== container ? calcInset(target, container) : point; 7143 /** 7144 * Measure the target and container. If they're the same thing then we 7145 * use the container's scrollWidth/Height as the target, from there 7146 * all other calculations can remain the same. 7147 */ 7148 const targetSize = target === container 7149 ? { width: container.scrollWidth, height: container.scrollHeight } 7150 : getTargetSize(target); 7151 const containerSize = { 7152 width: container.clientWidth, 7153 height: container.clientHeight, 7154 }; 7155 /** 7156 * Reset the length of the resolved offset array rather than creating a new one. 7157 * TODO: More reusable data structures for targetSize/containerSize would also be good. 7158 */ 7159 info[axis].offset.length = 0; 7160 /** 7161 * Populate the offset array by resolving the user's offset definition into 7162 * a list of pixel scroll offets. 7163 */ 7164 let hasChanged = !info[axis].interpolate; 7165 const numOffsets = offsetDefinition.length; 7166 for (let i = 0; i < numOffsets; i++) { 7167 const offset = resolveOffset(offsetDefinition[i], containerSize[lengthLabel], targetSize[lengthLabel], inset[axis]); 7168 if (!hasChanged && offset !== info[axis].interpolatorOff
vendor: 8,356 bytes, lines 7168-7375
7168sets[i]) { 7169 hasChanged = true; 7170 } 7171 info[axis].offset[i] = offset; 7172 } 7173 /** 7174 * If the pixel scroll offsets have changed, create a new interpolator function 7175 * to map scroll value into a progress. 7176 */ 7177 if (hasChanged) { 7178 info[axis].interpolate = interpolate(info[axis].offset, defaultOffset$1(offsetDefinition), { clamp: false }); 7179 info[axis].interpolatorOffsets = [...info[axis].offset]; 7180 } 7181 info[axis].progress = clamp(0, 1, info[axis].interpolate(info[axis].current)); 7182 } 7183 7184 function measure(container, target = container, info) { 7185 /** 7186 * Find inset of target within scrollable container 7187 */ 7188 info.x.targetOffset = 0; 7189 info.y.targetOffset = 0; 7190 if (target !== container) { 7191 let node = target; 7192 while (node && node !== container) { 7193 info.x.targetOffset += node.offsetLeft; 7194 info.y.targetOffset += node.offsetTop; 7195 node = node.offsetParent; 7196 } 7197 } 7198 info.x.targetLength = 7199 target === container ? target.scrollWidth : target.clientWidth; 7200 info.y.targetLength = 7201 target === container ? target.scrollHeight : target.clientHeight; 7202 info.x.containerLength = container.clientWidth; 7203 info.y.containerLength = container.clientHeight; 7204 /** 7205 * In development mode ensure scroll containers aren't position: static as this makes 7206 * it difficult to measure their relative positions. 7207 */ 7208 { 7209 if (container && target && target !== container) { 7210 warnOnce(getComputedStyle(container).position !== "static", "Please ensure that the container has a non-static position, like 'relative', 'fixed', or 'absolute' to ensure scroll offset is calculated correctly."); 7211 } 7212 } 7213 } 7214 function createOnScrollHandler(element, onScroll, info, options = {}) { 7215 return { 7216 measure: (time) => { 7217 measure(element, options.target, info); 7218 updateScrollInfo(element, info, time); 7219 if (options.offset || options.target) { 7220 resolveOffsets(element, info, options); 7221 } 7222 }, 7223 notify: () => onScroll(info), 7224 }; 7225 } 7226 7227 const scrollListeners = new WeakMap(); 7228 const resizeListeners = new WeakMap(); 7229 const onScrollHandlers = new WeakMap(); 7230 const getEventTarget = (element) => element === document.scrollingElement ? window : element; 7231 function scrollInfo(onScroll, { container = document.scrollingElement, ...options } = {}) { 7232 if (!container) 7233 return noop; 7234 let containerHandlers = onScrollHandlers.get(container); 7235 /** 7236 * Get the onScroll handlers for this container. 7237 * If one isn't found, create a new one. 7238 */ 7239 if (!containerHandlers) { 7240 containerHandlers = new Set(); 7241 onScrollHandlers.set(container, containerHandlers); 7242 } 7243 /** 7244 * Create a new onScroll handler for the provided callback. 7245 */ 7246 const info = createScrollInfo(); 7247 const containerHandler = createOnScrollHandler(container, onScroll, info, options); 7248 containerHandlers.add(containerHandler); 7249 /** 7250 * Check if there's a scroll event listener for this container. 7251 * If not, create one. 7252 */ 7253 if (!scrollListeners.has(container)) { 7254 const measureAll = () => { 7255 for (const handler of containerHandlers) { 7256 handler.measure(frameData.timestamp); 7257 } 7258 frame.preUpdate(notifyAll); 7259 }; 7260 const notifyAll = () => { 7261 for (const handler of containerHandlers) { 7262 handler.notify(); 7263 } 7264 }; 7265 const listener = () => frame.read(measureAll); 7266 scrollListeners.set(container, listener); 7267 const target = getEventTarget(container); 7268 window.addEventListener("resize", listener, { passive: true }); 7269 if (container !== document.documentElement) { 7270 resizeListeners.set(container, resize(container, listener)); 7271 } 7272 target.addEventListener("scroll", listener, { passive: true }); 7273 listener(); 7274 } 7275 const listener = scrollListeners.get(container); 7276 frame.read(listener, false, true); 7277 return () => { 7278 cancelFrame(listener); 7279 /** 7280 * Check if we even have any handlers for this container. 7281 */ 7282 const currentHandlers = onScrollHandlers.get(container); 7283 if (!currentHandlers) 7284 return; 7285 currentHandlers.delete(containerHandler); 7286 if (currentHandlers.size) 7287 return; 7288 /** 7289 * If no more handlers, remove the scroll listener too. 7290 */ 7291 const scrollListener = scrollListeners.get(container); 7292 scrollListeners.delete(container); 7293 if (scrollListener) { 7294 getEventTarget(container).removeEventListener("scroll", scrollListener); 7295 resizeListeners.get(container)?.(); 7296 window.removeEventListener("resize", scrollListener); 7297 } 7298 }; 7299 } 7300 7301 const timelineCache = new Map(); 7302 function scrollTimelineFallback(options) { 7303 const currentTime = { value: 0 }; 7304 const cancel = scrollInfo((info) => { 7305 currentTime.value = info[options.axis].progress * 100; 7306 }, options); 7307 return { currentTime, cancel }; 7308 } 7309 function getTimeline({ source, container, ...options }) { 7310 const { axis } = options; 7311 if (source) 7312 container = source; 7313 const containerCache = timelineCache.get(container) ?? new Map(); 7314 timelineCache.set(container, containerCache); 7315 const targetKey = options.target ?? "self"; 7316 const targetCache = containerCache.get(targetKey) ?? {}; 7317 const axisKey = axis + (options.offset ?? []).join(","); 7318 if (!targetCache[axisKey]) { 7319 targetCache[axisKey] = 7320 !options.target && supportsScrollTimeline() 7321 ? new ScrollTimeline({ source: container, axis }) 7322 : scrollTimelineFallback({ container, ...options }); 7323 } 7324 return targetCache[axisKey]; 7325 } 7326 7327 function attachToAnimation(animation, options) { 7328 const timeline = getTimeline(options); 7329 return animation.attachTimeline({ 7330 timeline: options.target ? undefined : timeline, 7331 observe: (valueAnimation) => { 7332 valueAnimation.pause(); 7333 return observeTimeline((progress) => { 7334 valueAnimation.time = 7335 valueAnimation.iterationDuration * progress; 7336 }, timeline); 7337 }, 7338 }); 7339 } 7340 7341 /** 7342 * If the onScroll function has two arguments, it's expecting 7343 * more specific information about the scroll from scrollInfo. 7344 */ 7345 function isOnScrollWithInfo(onScroll) { 7346 return onScroll.length === 2; 7347 } 7348 function attachToFunction(onScroll, options) { 7349 if (isOnScrollWithInfo(onScroll)) { 7350 return scrollInfo((info) => { 7351 onScroll(info[options.axis].progress, info); 7352 }, options); 7353 } 7354 else { 7355 return observeTimeline(onScroll, getTimeline(options)); 7356 } 7357 } 7358 7359 function scroll(onScroll, { axis = "y", container = document.scrollingElement, ...options } = {}) { 7360 if (!container) 7361 return noop; 7362 const optionsWithDefaults = { axis, container, ...options }; 7363 return typeof onScroll === "function" 7364 ? attachToFunction(onScroll, optionsWithDefaults) 7365 : attachToAnimation(onScroll, optionsWithDefaults); 7366 } 7367 7368 const thresholds = { 7369 some: 0, 7370 all: 1, 7371 }; 7372 function inView(elementOrSelector, onStart, { root, margin: rootMargin, amount = "some" } = {}) { 7373 const elements = resolveElements(elementOrSelector); 7374 const activeIntersections = new WeakMap(); 7375 const onIntersectionChange = (entries) => {
vendor: 4,586 bytes, lines 7376-7489
7376 entries.forEach((entry) => { 7377 const onEnd = activeIntersections.get(entry.target); 7378 /** 7379 * If there's no change to the intersection, we don't need to 7380 * do anything here. 7381 */ 7382 if (entry.isIntersecting === Boolean(onEnd)) 7383 return; 7384 if (entry.isIntersecting) { 7385 const newOnEnd = onStart(entry.target, entry); 7386 if (typeof newOnEnd === "function") { 7387 activeIntersections.set(entry.target, newOnEnd); 7388 } 7389 else { 7390 observer.unobserve(entry.target); 7391 } 7392 } 7393 else if (typeof onEnd === "function") { 7394 onEnd(entry); 7395 activeIntersections.delete(entry.target); 7396 } 7397 }); 7398 }; 7399 const observer = new IntersectionObserver(onIntersectionChange, { 7400 root, 7401 rootMargin, 7402 threshold: typeof amount === "number" ? amount : thresholds[amount], 7403 }); 7404 elements.forEach((element) => observer.observe(element)); 7405 return () => observer.disconnect(); 7406 } 7407 7408 /** 7409 * Timeout defined in ms 7410 */ 7411 function delay(callback, timeout) { 7412 const start = time.now(); 7413 const checkElapsed = ({ timestamp }) => { 7414 const elapsed = timestamp - start; 7415 if (elapsed >= timeout) { 7416 cancelFrame(checkElapsed); 7417 callback(elapsed - timeout); 7418 } 7419 }; 7420 frame.setup(checkElapsed, true); 7421 return () => cancelFrame(checkElapsed); 7422 } 7423 function delayInSeconds(callback, timeout) { 7424 return delay(callback, secondsToMilliseconds(timeout)); 7425 } 7426 7427 const distance = (a, b) => Math.abs(a - b); 7428 function distance2D(a, b) { 7429 // Multi-dimensional 7430 const xDelta = distance(a.x, b.x); 7431 const yDelta = distance(a.y, b.y); 7432 return Math.sqrt(xDelta ** 2 + yDelta ** 2); 7433 } 7434 7435 exports.AsyncMotionValueAnimation = AsyncMotionValueAnimation; 7436 exports.DOMKeyframesResolver = DOMKeyframesResolver; 7437 exports.GroupAnimation = GroupAnimation; 7438 exports.GroupAnimationWithThen = GroupAnimationWithThen; 7439 exports.JSAnimation = JSAnimation; 7440 exports.KeyframeResolver = KeyframeResolver; 7441 exports.MotionGlobalConfig = MotionGlobalConfig; 7442 exports.MotionValue = MotionValue; 7443 exports.NativeAnimation = NativeAnimation; 7444 exports.NativeAnimationExtended = NativeAnimationExtended; 7445 exports.NativeAnimationWrapper = NativeAnimationWrapper; 7446 exports.SubscriptionManager = SubscriptionManager; 7447 exports.ViewTransitionBuilder = ViewTransitionBuilder; 7448 exports.acceleratedValues = acceleratedValues; 7449 exports.activeAnimations = activeAnimations; 7450 exports.addAttrValue = addAttrValue; 7451 exports.addStyleValue = addStyleValue; 7452 exports.addUniqueItem = addUniqueItem; 7453 exports.alpha = alpha; 7454 exports.analyseComplexValue = analyseComplexValue; 7455 exports.animate = animate; 7456 exports.animateMini = animateMini; 7457 exports.animateValue = animateValue; 7458 exports.animateView = animateView; 7459 exports.animationMapKey = animationMapKey; 7460 exports.anticipate = anticipate; 7461 exports.applyGeneratorOptions = applyGeneratorOptions; 7462 exports.applyPxDefaults = applyPxDefaults; 7463 exports.attachSpring = attachSpring; 7464 exports.attrEffect = attrEffect; 7465 exports.backIn = backIn; 7466 exports.backInOut = backInOut; 7467 exports.backOut = backOut; 7468 exports.calcGeneratorDuration = calcGeneratorDuration; 7469 exports.cancelFrame = cancelFrame; 7470 exports.cancelMicrotask = cancelMicrotask; 7471 exports.cancelSync = cancelSync; 7472 exports.circIn = circIn; 7473 exports.circInOut = circInOut; 7474 exports.circOut = circOut; 7475 exports.clamp = clamp; 7476 exports.collectMotionValues = collectMotionValues; 7477 exports.color = color; 7478 exports.complex = complex; 7479 exports.convertOffsetToTimes = convertOffsetToTimes; 7480 exports.createGeneratorEasing = createGeneratorEasing; 7481 exports.createRenderBatcher = createRenderBatcher; 7482 exports.createScopedAnimate = createScopedAnimate; 7483 exports.cubicBezier = cubicBezier; 7484 exports.cubicBezierAsString = cubicBezierAsString; 7485 exports.defaultEasing = defaultEasing; 7486 exports.defaultOffset = defaultOffset$1; 7487 exports.defaultTransformValue = defaultTransformValue; 7488 exports.defaultValueTypes = defaultValueTypes; 7489 exports.degrees = degrees;
7490 exports.delay = delayInSeconds; 7491 exports.dimensionValueTypes = dimensionValueTypes; 7492 exports.distance = distance; 7493 exports.distance2D = distance2D; 7494 exports.easeIn = easeIn; 7495 exports.easeInOut = easeInOut; 7496 exports.easeOut = easeOut; 7497 exports.easingDefinitionToFunction = easingDefinitionToFunction; 7498 exports.fillOffset = fillOffset; 7499 exports.fillWildcards = fillWildcards; 7500 exports.findDimensionValueType = findDimensionValueType; 7501 exports.findValueType = findValueType; 7502 exports.flushKeyframeResolvers = flushKeyframeResolvers; 7503 exports.frame = frame; 7504 exports.frameData = frameData; 7505 exports.frameSteps = frameSteps; 7506 exports.generateLinearEasing = generateLinearEasing; 7507 exports.getAnimatableNone = getAnimatableNone; 7508 exports.getAnimationMap = getAnimationMap; 7509 exports.getComputedStyle = getComputedStyle$2; 7510 exports.getDefaultValueType = getDefaultValueType; 7511 exports.getEasingForSegment = getEasingForSegment; 7512 exports.getMixer = getMixer; 7513 exports.getOriginIndex = getOriginIndex; 7514 exports.getValueAsType = getValueAsType; 7515 exports.getValueTransition = getValueTransition$1; 7516 exports.getVariableValue = getVariableValue; 7517 exports.getViewAnimationLayerInfo = getViewAnimationLayerInfo; 7518 exports.getViewAnimations = getViewAnimations; 7519 exports.hasWarned = hasWarned; 7520 exports.hex = hex; 7521 exports.hover = hover; 7522 exports.hsla = hsla; 7523 exports.hslaToRgba = hslaToRgba; 7524 exports.inView = inView; 7525 exports.inertia = inertia; 7526 exports.interpolate = interpolate; 7527 exports.invisibleValues = invisibleValues; 7528 exports.isBezierDefinition = isBezierDefinition; 7529 exports.isCSSVariableName = isCSSVariableName; 7530 exports.isCSSVariableToken = isCSSVariableToken; 7531 exports.isDragActive = isDragActive; 7532 exports.isDragging = isDragging; 7533 exports.isEasingArray = isEasingArray; 7534 exports.isGenerator = isGenerator; 7535 exports.isHTMLElement = isHTMLElement; 7536 exports.isMotionValue = isMotionValue; 7537 exports.isNodeOrChild = isNodeOrChild; 7538 exports.isNumericalString = isNumericalString; 7539 exports.isObject = isObject; 7540 exports.isPrimaryPointer = isPrimaryPointer; 7541 exports.isSVGElement = isSVGElement; 7542 exports.isSVGSVGElement = isSVGSVGElement; 7543 exports.isWaapiSupportedEasing = isWaapiSupportedEasing; 7544 exports.isZeroValueString = isZeroValueString; 7545 exports.keyframes = keyframes; 7546 exports.makeAnimationInstant = makeAnimationInstant; 7547 exports.mapEasingToNativeEasing = mapEasingToNativeEasing; 7548 exports.mapValue = mapValue; 7549 exports.maxGeneratorDuration = maxGeneratorDuration; 7550 exports.memo = memo; 7551 exports.microtask = microtask; 7552 exports.millisecondsToSeconds = millisecondsToSeconds; 7553 exports.mirrorEasing = mirrorEasing; 7554 exports.mix = mix; 7555 exports.mixArray = mixArray; 7556 exports.mixColor = mixColor; 7557 exports.mixComplex = mixComplex; 7558 exports.mixImmediate = mixImmediate; 7559 exports.mixLinearColor = mixLinearColor; 7560 exports.mixNumber = mixNumber$1; 7561 exports.mixObject = mixObject; 7562 exports.mixVisibility = mixVisibility; 7563 exports.motionValue = motionValue; 7564 exports.moveItem = moveItem; 7565 exports.noop = noop; 7566 exports.number = number; 7567 exports.numberValueTypes = numberValueTypes; 7568 exports.observeTimeline = observeTimeline; 7569 exports.parseCSSVariable = parseCSSVariable; 7570 exports.parseValueFromTransform = parseValueFromTransform; 7571 exports.percent = percent; 7572 exports.pipe = pipe; 7573 exports.positionalKeys = positionalKeys; 7574 exports.press = press; 7575 exports.progress = progress; 7576 exports.progressPercentage = progressPercentage; 7577 exports.propEffect = propEffect; 7578 exports.px = px; 7579 exports.readTransformValue = readTransformValue; 7580 exports.recordStats = recordStats; 7581 exports.removeItem = removeItem; 7582 exports.resize = resize; 7583 exports.resolveElements = resolveElements; 7584 exports.reverseEasing = reverseEasing; 7585 exports.rgbUnit = rgbUnit; 7586 exports.rgba = rgba; 7587 exports.scale = scale; 7588 exports.scroll = scroll; 7589 exports.scrollInfo = scrollInfo; 7590 exports.secondsToMilliseconds = secondsToMilliseconds; 7591 exports.setDragLock = setDragLock; 7592 exports.setStyle = setStyle; 7593 exports.spring = spring; 7594 exports.springValue = springValue; 7595 exports.stagger = stagger; 7596 exports.startWaapiAnimation = startWaapiAnimation; 7597 exports.statsBuffer = statsBuffer; 7598 exports.steps = steps; 7599 exports.styleEffect = styleEffect; 7600 exports.supportedWaapiEasing = supportedWaapiEasing; 7601 exports.supportsBrowserAnimation = supportsBrowserAnimation; 7602 exports.supportsFlags = supportsFlags; 7603 exports.supportsLinearEasing = supportsLinearEasing; 7604 exports.supportsPartialKeyframes = supportsPartialKeyframes; 7605 exports.supportsScrollTimeline = supportsScrollTimeline; 7606 exports.svgEffect = svgEffect; 7607 exports.sync = sync; 7608 exports.testValueType = testValueType; 7609 exports.time = time; 7610 exports.transform = transform; 7611 exports.transformPropOrder = transformPropOrder; 7612 exports.transformProps = transformProps; 7613 exports.transformValue = transformValue; 7614 exports.transformValueTypes = transformValueTypes; 7615 exports.velocityPerSecond = velocityPerSecond; 7616 exports.vh = vh; 7617 exports.vw = vw; 7618 exports.warnOnce = warnOnce; 7619 exports.wrap = wrap; 7620 7621}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.