UNPKG

motion-v

Version:

<p align="center"> <img width="100" height="100" alt="Motion logo" src="https://user-images.githubusercontent.com/7850794/164965523-3eced4c4-6020-467e-acde-f11b7900ad62.png" /> </p> <h1 align="center">Motion for Vue</h1>

1,482 lines 363 kB
"use strict"; Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } }); const vue = require("vue"); const heyListen = require("hey-listen"); const core = require("@vueuse/core"); function addUniqueItem(arr, item) { if (arr.indexOf(item) === -1) arr.push(item); } function removeItem(arr, item) { const index = arr.indexOf(item); if (index > -1) arr.splice(index, 1); } function moveItem$1([...arr], fromIndex, toIndex) { const startIndex = fromIndex < 0 ? arr.length + fromIndex : fromIndex; if (startIndex >= 0 && startIndex < arr.length) { const endIndex = toIndex < 0 ? arr.length + toIndex : toIndex; const [item] = arr.splice(fromIndex, 1); arr.splice(endIndex, 0, item); } return arr; } const clamp = (min, max, v) => { if (v > max) return max; if (v < min) return min; return v; }; function formatErrorMessage(message, errorCode) { return errorCode ? `${message}. For more information and steps for solving, visit https://motion.dev/troubleshooting/${errorCode}` : message; } exports.warning = () => { }; exports.invariant = () => { }; if (process.env.NODE_ENV !== "production") { exports.warning = (check, message, errorCode) => { if (!check && typeof console !== "undefined") { console.warn(formatErrorMessage(message, errorCode)); } }; exports.invariant = (check, message, errorCode) => { if (!check) { throw new Error(formatErrorMessage(message, errorCode)); } }; } const MotionGlobalConfig = {}; const isNumericalString = (v) => /^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(v); function isObject(value) { return typeof value === "object" && value !== null; } const isZeroValueString = (v) => /^0[^.\s]+$/u.test(v); // @__NO_SIDE_EFFECTS__ function memo(callback) { let result; return () => { if (result === void 0) result = callback(); return result; }; } const noop = /* @__NO_SIDE_EFFECTS__ */ (any) => any; const combineFunctions = (a, b) => (v) => b(a(v)); const pipe = (...transformers) => transformers.reduce(combineFunctions); const progress = /* @__NO_SIDE_EFFECTS__ */ (from, to, value) => { const toFromDifference = to - from; return toFromDifference === 0 ? 1 : (value - from) / toFromDifference; }; class SubscriptionManager { constructor() { this.subscriptions = []; } add(handler) { addUniqueItem(this.subscriptions, handler); return () => removeItem(this.subscriptions, handler); } notify(a, b, c) { const numSubscriptions = this.subscriptions.length; if (!numSubscriptions) return; if (numSubscriptions === 1) { this.subscriptions[0](a, b, c); } else { for (let i = 0; i < numSubscriptions; i++) { const handler = this.subscriptions[i]; handler && handler(a, b, c); } } } getSize() { return this.subscriptions.length; } clear() { this.subscriptions.length = 0; } } const secondsToMilliseconds = /* @__NO_SIDE_EFFECTS__ */ (seconds) => seconds * 1e3; const millisecondsToSeconds = /* @__NO_SIDE_EFFECTS__ */ (milliseconds) => milliseconds / 1e3; function velocityPerSecond(velocity, frameDuration) { return frameDuration ? velocity * (1e3 / frameDuration) : 0; } const warned = /* @__PURE__ */ new Set(); function hasWarned(message) { return warned.has(message); } function warnOnce(condition, message, errorCode) { if (condition || warned.has(message)) return; console.warn(formatErrorMessage(message, errorCode)); warned.add(message); } const wrap = (min, max, v) => { const rangeSize = max - min; return ((v - min) % rangeSize + rangeSize) % rangeSize + min; }; const calcBezier = (t, a1, a2) => (((1 - 3 * a2 + 3 * a1) * t + (3 * a2 - 6 * a1)) * t + 3 * a1) * t; const subdivisionPrecision = 1e-7; const subdivisionMaxIterations = 12; function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) { let currentX; let currentT; let i = 0; do { currentT = lowerBound + (upperBound - lowerBound) / 2; currentX = calcBezier(currentT, mX1, mX2) - x; if (currentX > 0) { upperBound = currentT; } else { lowerBound = currentT; } } while (Math.abs(currentX) > subdivisionPrecision && ++i < subdivisionMaxIterations); return currentT; } function cubicBezier(mX1, mY1, mX2, mY2) { if (mX1 === mY1 && mX2 === mY2) return noop; const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2); return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2); } const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2; const reverseEasing = (easing) => (p) => 1 - easing(1 - p); const backOut = /* @__PURE__ */ cubicBezier(0.33, 1.53, 0.69, 0.99); const backIn = /* @__PURE__ */ reverseEasing(backOut); const backInOut = /* @__PURE__ */ mirrorEasing(backIn); const anticipate = (p) => (p *= 2) < 1 ? 0.5 * backIn(p) : 0.5 * (2 - Math.pow(2, -10 * (p - 1))); const circIn = (p) => 1 - Math.sin(Math.acos(p)); const circOut = reverseEasing(circIn); const circInOut = mirrorEasing(circIn); const easeIn = /* @__PURE__ */ cubicBezier(0.42, 0, 1, 1); const easeOut = /* @__PURE__ */ cubicBezier(0, 0, 0.58, 1); const easeInOut = /* @__PURE__ */ cubicBezier(0.42, 0, 0.58, 1); function steps(numSteps, direction = "end") { return (progress2) => { progress2 = direction === "end" ? Math.min(progress2, 0.999) : Math.max(progress2, 1e-3); const expanded = progress2 * numSteps; const rounded = direction === "end" ? Math.floor(expanded) : Math.ceil(expanded); return clamp(0, 1, rounded / numSteps); }; } const isEasingArray = (ease2) => { return Array.isArray(ease2) && typeof ease2[0] !== "number"; }; function getEasingForSegment(easing, i) { return isEasingArray(easing) ? easing[wrap(0, easing.length, i)] : easing; } const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number"; const easingLookup = { linear: noop, easeIn, easeInOut, easeOut, circIn, circInOut, circOut, backIn, backInOut, backOut, anticipate }; const isValidEasing = (easing) => { return typeof easing === "string"; }; const easingDefinitionToFunction = (definition) => { if (isBezierDefinition(definition)) { exports.invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`, "cubic-bezier-length"); const [x1, y1, x2, y2] = definition; return cubicBezier(x1, y1, x2, y2); } else if (isValidEasing(definition)) { exports.invariant(easingLookup[definition] !== void 0, `Invalid easing type '${definition}'`, "invalid-easing-type"); return easingLookup[definition]; } return definition; }; const stepsOrder = [ "setup", // Compute "read", // Read "resolveKeyframes", // Write/Read/Write/Read "preUpdate", // Compute "update", // Compute "preRender", // Compute "render", // Write "postRender" // Compute ]; const statsBuffer = { value: null, addProjectionMetrics: null }; function createRenderStep(runNextFrame, stepName) { let thisFrame = /* @__PURE__ */ new Set(); let nextFrame = /* @__PURE__ */ new Set(); let isProcessing = false; let flushNextFrame = false; const toKeepAlive = /* @__PURE__ */ new WeakSet(); let latestFrameData = { delta: 0, timestamp: 0, isProcessing: false }; let numCalls = 0; function triggerCallback(callback) { if (toKeepAlive.has(callback)) { step.schedule(callback); runNextFrame(); } numCalls++; callback(latestFrameData); } const step = { /** * Schedule a process to run on the next frame. */ schedule: (callback, keepAlive = false, immediate = false) => { const addToCurrentFrame = immediate && isProcessing; const queue = addToCurrentFrame ? thisFrame : nextFrame; if (keepAlive) toKeepAlive.add(callback); if (!queue.has(callback)) queue.add(callback); return callback; }, /** * Cancel the provided callback from running on the next frame. */ cancel: (callback) => { nextFrame.delete(callback); toKeepAlive.delete(callback); }, /** * Execute all schedule callbacks. */ process: (frameData2) => { latestFrameData = frameData2; if (isProcessing) { flushNextFrame = true; return; } isProcessing = true; [thisFrame, nextFrame] = [nextFrame, thisFrame]; thisFrame.forEach(triggerCallback); if (stepName && statsBuffer.value) { statsBuffer.value.frameloop[stepName].push(numCalls); } numCalls = 0; thisFrame.clear(); isProcessing = false; if (flushNextFrame) { flushNextFrame = false; step.process(frameData2); } } }; return step; } const maxElapsed$1 = 40; function createRenderBatcher(scheduleNextBatch, allowKeepAlive) { let runNextFrame = false; let useDefaultElapsed = true; const state = { delta: 0, timestamp: 0, isProcessing: false }; const flagRunNextFrame = () => runNextFrame = true; const steps2 = stepsOrder.reduce((acc, key) => { acc[key] = createRenderStep(flagRunNextFrame, allowKeepAlive ? key : void 0); return acc; }, {}); const { setup, read, resolveKeyframes, preUpdate, update, preRender, render, postRender } = steps2; const processBatch = () => { const timestamp = MotionGlobalConfig.useManualTiming ? state.timestamp : performance.now(); runNextFrame = false; if (!MotionGlobalConfig.useManualTiming) { state.delta = useDefaultElapsed ? 1e3 / 60 : Math.max(Math.min(timestamp - state.timestamp, maxElapsed$1), 1); } state.timestamp = timestamp; state.isProcessing = true; setup.process(state); read.process(state); resolveKeyframes.process(state); preUpdate.process(state); update.process(state); preRender.process(state); render.process(state); postRender.process(state); state.isProcessing = false; if (runNextFrame && allowKeepAlive) { useDefaultElapsed = false; scheduleNextBatch(processBatch); } }; const wake = () => { runNextFrame = true; useDefaultElapsed = true; if (!state.isProcessing) { scheduleNextBatch(processBatch); } }; const schedule = stepsOrder.reduce((acc, key) => { const step = steps2[key]; acc[key] = (process2, keepAlive = false, immediate = false) => { if (!runNextFrame) wake(); return step.schedule(process2, keepAlive, immediate); }; return acc; }, {}); const cancel = (process2) => { for (let i = 0; i < stepsOrder.length; i++) { steps2[stepsOrder[i]].cancel(process2); } }; return { schedule, cancel, state, steps: steps2 }; } const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true); let now; function clearTime() { now = void 0; } const time = { now: () => { if (now === void 0) { time.set(frameData.isProcessing || MotionGlobalConfig.useManualTiming ? frameData.timestamp : performance.now()); } return now; }, set: (newTime) => { now = newTime; queueMicrotask(clearTime); } }; const activeAnimations = { layout: 0, mainThread: 0, waapi: 0 }; const checkStringStartsWith = (token) => (key) => typeof key === "string" && key.startsWith(token); const isCSSVariableName = /* @__PURE__ */ checkStringStartsWith("--"); const startsAsVariableToken = /* @__PURE__ */ checkStringStartsWith("var(--"); const isCSSVariableToken = (value) => { const startsWithToken = startsAsVariableToken(value); if (!startsWithToken) return false; return singleCssVariableRegex.test(value.split("/*")[0].trim()); }; const singleCssVariableRegex = /var\(--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)$/iu; const number = { test: (v) => typeof v === "number", parse: parseFloat, transform: (v) => v }; const alpha = { ...number, transform: (v) => clamp(0, 1, v) }; const scale = { ...number, default: 1 }; const sanitize = (v) => Math.round(v * 1e5) / 1e5; const floatRegex = /-?(?:\d+(?:\.\d+)?|\.\d+)/gu; function isNullish(v) { return v == null; } const singleColorRegex = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))$/iu; const isColorString = (type, testProp) => (v) => { return Boolean(typeof v === "string" && singleColorRegex.test(v) && v.startsWith(type) || testProp && !isNullish(v) && Object.prototype.hasOwnProperty.call(v, testProp)); }; const splitColor = (aName, bName, cName) => (v) => { if (typeof v !== "string") return v; const [a, b, c, alpha2] = v.match(floatRegex); return { [aName]: parseFloat(a), [bName]: parseFloat(b), [cName]: parseFloat(c), alpha: alpha2 !== void 0 ? parseFloat(alpha2) : 1 }; }; const clampRgbUnit = (v) => clamp(0, 255, v); const rgbUnit = { ...number, transform: (v) => Math.round(clampRgbUnit(v)) }; const rgba = { test: /* @__PURE__ */ isColorString("rgb", "red"), parse: /* @__PURE__ */ splitColor("red", "green", "blue"), transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => "rgba(" + rgbUnit.transform(red) + ", " + rgbUnit.transform(green) + ", " + rgbUnit.transform(blue) + ", " + sanitize(alpha.transform(alpha$1)) + ")" }; function parseHex(v) { let r = ""; let g = ""; let b = ""; let a = ""; if (v.length > 5) { r = v.substring(1, 3); g = v.substring(3, 5); b = v.substring(5, 7); a = v.substring(7, 9); } else { r = v.substring(1, 2); g = v.substring(2, 3); b = v.substring(3, 4); a = v.substring(4, 5); r += r; g += g; b += b; a += a; } return { red: parseInt(r, 16), green: parseInt(g, 16), blue: parseInt(b, 16), alpha: a ? parseInt(a, 16) / 255 : 1 }; } const hex = { test: /* @__PURE__ */ isColorString("#"), parse: parseHex, transform: rgba.transform }; const createUnitType = /* @__NO_SIDE_EFFECTS__ */ (unit) => ({ test: (v) => typeof v === "string" && v.endsWith(unit) && v.split(" ").length === 1, parse: parseFloat, transform: (v) => `${v}${unit}` }); const degrees = /* @__PURE__ */ createUnitType("deg"); const percent = /* @__PURE__ */ createUnitType("%"); const px = /* @__PURE__ */ createUnitType("px"); const vh = /* @__PURE__ */ createUnitType("vh"); const vw = /* @__PURE__ */ createUnitType("vw"); const progressPercentage = /* @__PURE__ */ (() => ({ ...percent, parse: (v) => percent.parse(v) / 100, transform: (v) => percent.transform(v * 100) }))(); const hsla = { test: /* @__PURE__ */ isColorString("hsl", "hue"), parse: /* @__PURE__ */ splitColor("hue", "saturation", "lightness"), transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => { return "hsla(" + Math.round(hue) + ", " + percent.transform(sanitize(saturation)) + ", " + percent.transform(sanitize(lightness)) + ", " + sanitize(alpha.transform(alpha$1)) + ")"; } }; const color = { test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v), parse: (v) => { if (rgba.test(v)) { return rgba.parse(v); } else if (hsla.test(v)) { return hsla.parse(v); } else { return hex.parse(v); } }, transform: (v) => { return typeof v === "string" ? v : v.hasOwnProperty("red") ? rgba.transform(v) : hsla.transform(v); }, getAnimatableNone: (v) => { const parsed = color.parse(v); parsed.alpha = 0; return color.transform(parsed); } }; const colorRegex = /(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))/giu; function test(v) { var _a, _b; return isNaN(v) && typeof v === "string" && (((_a = v.match(floatRegex)) == null ? void 0 : _a.length) || 0) + (((_b = v.match(colorRegex)) == null ? void 0 : _b.length) || 0) > 0; } const NUMBER_TOKEN = "number"; const COLOR_TOKEN = "color"; const VAR_TOKEN = "var"; const VAR_FUNCTION_TOKEN = "var("; const SPLIT_TOKEN = "${}"; 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; function analyseComplexValue(value) { const originalValue = value.toString(); const values = []; const indexes = { color: [], number: [], var: [] }; const types = []; let i = 0; const tokenised = originalValue.replace(complexRegex, (parsedValue) => { if (color.test(parsedValue)) { indexes.color.push(i); types.push(COLOR_TOKEN); values.push(color.parse(parsedValue)); } else if (parsedValue.startsWith(VAR_FUNCTION_TOKEN)) { indexes.var.push(i); types.push(VAR_TOKEN); values.push(parsedValue); } else { indexes.number.push(i); types.push(NUMBER_TOKEN); values.push(parseFloat(parsedValue)); } ++i; return SPLIT_TOKEN; }); const split = tokenised.split(SPLIT_TOKEN); return { values, split, indexes, types }; } function parseComplexValue(v) { return analyseComplexValue(v).values; } function createTransformer(source) { const { split, types } = analyseComplexValue(source); const numSections = split.length; return (v) => { let output = ""; for (let i = 0; i < numSections; i++) { output += split[i]; if (v[i] !== void 0) { const type = types[i]; if (type === NUMBER_TOKEN) { output += sanitize(v[i]); } else if (type === COLOR_TOKEN) { output += color.transform(v[i]); } else { output += v[i]; } } } return output; }; } const convertNumbersToZero = (v) => typeof v === "number" ? 0 : color.test(v) ? color.getAnimatableNone(v) : v; function getAnimatableNone$1(v) { const parsed = parseComplexValue(v); const transformer = createTransformer(v); return transformer(parsed.map(convertNumbersToZero)); } const complex = { test, parse: parseComplexValue, createTransformer, getAnimatableNone: getAnimatableNone$1 }; function hueToRgb(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; } function hslaToRgba({ hue, saturation, lightness, alpha: alpha2 }) { hue /= 360; saturation /= 100; lightness /= 100; let red = 0; let green = 0; let blue = 0; if (!saturation) { red = green = blue = lightness; } else { const q = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation; const p = 2 * lightness - q; red = hueToRgb(p, q, hue + 1 / 3); green = hueToRgb(p, q, hue); blue = hueToRgb(p, q, hue - 1 / 3); } return { red: Math.round(red * 255), green: Math.round(green * 255), blue: Math.round(blue * 255), alpha: alpha2 }; } function mixImmediate(a, b) { return (p) => p > 0 ? b : a; } const mixNumber$1 = (from, to, progress2) => { return from + (to - from) * progress2; }; const mixLinearColor = (from, to, v) => { const fromExpo = from * from; const expo = v * (to * to - fromExpo) + fromExpo; return expo < 0 ? 0 : Math.sqrt(expo); }; const colorTypes = [hex, rgba, hsla]; const getColorType = (v) => colorTypes.find((type) => type.test(v)); function asRGBA(color2) { const type = getColorType(color2); exports.warning(Boolean(type), `'${color2}' is not an animatable color. Use the equivalent color code instead.`, "color-not-animatable"); if (!Boolean(type)) return false; let model = type.parse(color2); if (type === hsla) { model = hslaToRgba(model); } return model; } const mixColor = (from, to) => { const fromRGBA = asRGBA(from); const toRGBA = asRGBA(to); if (!fromRGBA || !toRGBA) { return mixImmediate(from, to); } const blended = { ...fromRGBA }; return (v) => { blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v); blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v); blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v); blended.alpha = mixNumber$1(fromRGBA.alpha, toRGBA.alpha, v); return rgba.transform(blended); }; }; const invisibleValues = /* @__PURE__ */ new Set(["none", "hidden"]); function mixVisibility(origin, target) { if (invisibleValues.has(origin)) { return (p) => p <= 0 ? origin : target; } else { return (p) => p >= 1 ? target : origin; } } function mixNumber(a, b) { return (p) => mixNumber$1(a, b, p); } function getMixer(a) { if (typeof a === "number") { return mixNumber; } else if (typeof a === "string") { return isCSSVariableToken(a) ? mixImmediate : color.test(a) ? mixColor : mixComplex; } else if (Array.isArray(a)) { return mixArray; } else if (typeof a === "object") { return color.test(a) ? mixColor : mixObject; } return mixImmediate; } function mixArray(a, b) { const output = [...a]; const numValues = output.length; const blendValue = a.map((v, i) => getMixer(v)(v, b[i])); return (p) => { for (let i = 0; i < numValues; i++) { output[i] = blendValue[i](p); } return output; }; } function mixObject(a, b) { const output = { ...a, ...b }; const blendValue = {}; for (const key in output) { if (a[key] !== void 0 && b[key] !== void 0) { blendValue[key] = getMixer(a[key])(a[key], b[key]); } } return (v) => { for (const key in blendValue) { output[key] = blendValue[key](v); } return output; }; } function matchOrder(origin, target) { const orderedOrigin = []; const pointers = { color: 0, var: 0, number: 0 }; for (let i = 0; i < target.values.length; i++) { const type = target.types[i]; const originIndex = origin.indexes[type][pointers[type]]; const originValue = origin.values[originIndex] ?? 0; orderedOrigin[i] = originValue; pointers[type]++; } return orderedOrigin; } const mixComplex = (origin, target) => { const template = complex.createTransformer(target); const originStats = analyseComplexValue(origin); const targetStats = analyseComplexValue(target); const canInterpolate = originStats.indexes.var.length === targetStats.indexes.var.length && originStats.indexes.color.length === targetStats.indexes.color.length && originStats.indexes.number.length >= targetStats.indexes.number.length; if (canInterpolate) { if (invisibleValues.has(origin) && !targetStats.values.length || invisibleValues.has(target) && !originStats.values.length) { return mixVisibility(origin, target); } return pipe(mixArray(matchOrder(originStats, targetStats), targetStats.values), template); } else { 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"); return mixImmediate(origin, target); } }; function mix(from, to, p) { if (typeof from === "number" && typeof to === "number" && typeof p === "number") { return mixNumber$1(from, to, p); } const mixer = getMixer(from); return mixer(from, to); } const frameloopDriver = (update) => { const passTimestamp = ({ timestamp }) => update(timestamp); return { start: (keepAlive = true) => frame.update(passTimestamp, keepAlive), stop: () => cancelFrame(passTimestamp), /** * If we're processing this frame we can use the * framelocked timestamp to keep things in sync. */ now: () => frameData.isProcessing ? frameData.timestamp : time.now() }; }; const generateLinearEasing = (easing, duration, resolution = 10) => { let points = ""; const numPoints = Math.max(Math.round(duration / resolution), 2); for (let i = 0; i < numPoints; i++) { points += Math.round(easing(i / (numPoints - 1)) * 1e4) / 1e4 + ", "; } return `linear(${points.substring(0, points.length - 2)})`; }; const maxGeneratorDuration = 2e4; function calcGeneratorDuration(generator) { let duration = 0; const timeStep = 50; let state = generator.next(duration); while (!state.done && duration < maxGeneratorDuration) { duration += timeStep; state = generator.next(duration); } return duration >= maxGeneratorDuration ? Infinity : duration; } function createGeneratorEasing(options, scale2 = 100, createGenerator) { const generator = createGenerator({ ...options, keyframes: [0, scale2] }); const duration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration); return { type: "keyframes", ease: (progress2) => { return generator.next(duration * progress2).value / scale2; }, duration: /* @__PURE__ */ millisecondsToSeconds(duration) }; } const velocitySampleDuration = 5; function calcGeneratorVelocity(resolveValue, t, current2) { const prevT = Math.max(t - velocitySampleDuration, 0); return velocityPerSecond(current2 - resolveValue(prevT), t - prevT); } const springDefaults = { // Default spring physics stiffness: 100, damping: 10, mass: 1, velocity: 0, // Default duration/bounce-based options duration: 800, // in ms bounce: 0.3, visualDuration: 0.3, // in seconds // Rest thresholds restSpeed: { granular: 0.01, default: 2 }, restDelta: { granular: 5e-3, default: 0.5 }, // Limits minDuration: 0.01, // in seconds maxDuration: 10, // in seconds minDamping: 0.05, maxDamping: 1 }; const safeMin = 1e-3; function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, velocity = springDefaults.velocity, mass = springDefaults.mass }) { let envelope; let derivative; exports.warning(duration <= /* @__PURE__ */ secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less", "spring-duration-limit"); let dampingRatio = 1 - bounce; dampingRatio = clamp(springDefaults.minDamping, springDefaults.maxDamping, dampingRatio); duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, /* @__PURE__ */ millisecondsToSeconds(duration)); if (dampingRatio < 1) { envelope = (undampedFreq2) => { const exponentialDecay = undampedFreq2 * dampingRatio; const delta = exponentialDecay * duration; const a = exponentialDecay - velocity; const b = calcAngularFreq(undampedFreq2, dampingRatio); const c = Math.exp(-delta); return safeMin - a / b * c; }; derivative = (undampedFreq2) => { const exponentialDecay = undampedFreq2 * dampingRatio; const delta = exponentialDecay * duration; const d = delta * velocity + velocity; const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq2, 2) * duration; const f = Math.exp(-delta); const g = calcAngularFreq(Math.pow(undampedFreq2, 2), dampingRatio); const factor = -envelope(undampedFreq2) + safeMin > 0 ? -1 : 1; return factor * ((d - e) * f) / g; }; } else { envelope = (undampedFreq2) => { const a = Math.exp(-undampedFreq2 * duration); const b = (undampedFreq2 - velocity) * duration + 1; return -safeMin + a * b; }; derivative = (undampedFreq2) => { const a = Math.exp(-undampedFreq2 * duration); const b = (velocity - undampedFreq2) * (duration * duration); return a * b; }; } const initialGuess = 5 / duration; const undampedFreq = approximateRoot(envelope, derivative, initialGuess); duration = /* @__PURE__ */ secondsToMilliseconds(duration); if (isNaN(undampedFreq)) { return { stiffness: springDefaults.stiffness, damping: springDefaults.damping, duration }; } else { const stiffness = Math.pow(undampedFreq, 2) * mass; return { stiffness, damping: dampingRatio * 2 * Math.sqrt(mass * stiffness), duration }; } } const rootIterations = 12; function approximateRoot(envelope, derivative, initialGuess) { let result = initialGuess; for (let i = 1; i < rootIterations; i++) { result = result - envelope(result) / derivative(result); } return result; } function calcAngularFreq(undampedFreq, dampingRatio) { return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio); } const durationKeys = ["duration", "bounce"]; const physicsKeys = ["stiffness", "damping", "mass"]; function isSpringType(options, keys2) { return keys2.some((key) => options[key] !== void 0); } function getSpringOptions(options) { let springOptions = { velocity: springDefaults.velocity, stiffness: springDefaults.stiffness, damping: springDefaults.damping, mass: springDefaults.mass, isResolvedFromDuration: false, ...options }; if (!isSpringType(options, physicsKeys) && isSpringType(options, durationKeys)) { if (options.visualDuration) { const visualDuration = options.visualDuration; const root = 2 * Math.PI / (visualDuration * 1.2); const stiffness = root * root; const damping = 2 * clamp(0.05, 1, 1 - (options.bounce || 0)) * Math.sqrt(stiffness); springOptions = { ...springOptions, mass: springDefaults.mass, stiffness, damping }; } else { const derived = findSpring(options); springOptions = { ...springOptions, ...derived, mass: springDefaults.mass }; springOptions.isResolvedFromDuration = true; } } return springOptions; } function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce = springDefaults.bounce) { const options = typeof optionsOrVisualDuration !== "object" ? { visualDuration: optionsOrVisualDuration, keyframes: [0, 1], bounce } : optionsOrVisualDuration; let { restSpeed, restDelta } = options; const origin = options.keyframes[0]; const target = options.keyframes[options.keyframes.length - 1]; const state = { done: false, value: origin }; const { stiffness, damping, mass, duration, velocity, isResolvedFromDuration } = getSpringOptions({ ...options, velocity: -/* @__PURE__ */ millisecondsToSeconds(options.velocity || 0) }); const initialVelocity = velocity || 0; const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass)); const initialDelta = target - origin; const undampedAngularFreq = /* @__PURE__ */ millisecondsToSeconds(Math.sqrt(stiffness / mass)); const isGranularScale = Math.abs(initialDelta) < 5; restSpeed || (restSpeed = isGranularScale ? springDefaults.restSpeed.granular : springDefaults.restSpeed.default); restDelta || (restDelta = isGranularScale ? springDefaults.restDelta.granular : springDefaults.restDelta.default); let resolveSpring; if (dampingRatio < 1) { const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio); resolveSpring = (t) => { const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t); return target - envelope * ((initialVelocity + dampingRatio * undampedAngularFreq * initialDelta) / angularFreq * Math.sin(angularFreq * t) + initialDelta * Math.cos(angularFreq * t)); }; } else if (dampingRatio === 1) { resolveSpring = (t) => target - Math.exp(-undampedAngularFreq * t) * (initialDelta + (initialVelocity + undampedAngularFreq * initialDelta) * t); } else { const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1); resolveSpring = (t) => { const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t); const freqForT = Math.min(dampedAngularFreq * t, 300); return target - envelope * ((initialVelocity + dampingRatio * undampedAngularFreq * initialDelta) * Math.sinh(freqForT) + dampedAngularFreq * initialDelta * Math.cosh(freqForT)) / dampedAngularFreq; }; } const generator = { calculatedDuration: isResolvedFromDuration ? duration || null : null, next: (t) => { const current2 = resolveSpring(t); if (!isResolvedFromDuration) { let currentVelocity = t === 0 ? initialVelocity : 0; if (dampingRatio < 1) { currentVelocity = t === 0 ? /* @__PURE__ */ secondsToMilliseconds(initialVelocity) : calcGeneratorVelocity(resolveSpring, t, current2); } const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed; const isBelowDisplacementThreshold = Math.abs(target - current2) <= restDelta; state.done = isBelowVelocityThreshold && isBelowDisplacementThreshold; } else { state.done = t >= duration; } state.value = state.done ? target : current2; return state; }, toString: () => { const calculatedDuration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration); const easing = generateLinearEasing((progress2) => generator.next(calculatedDuration * progress2).value, calculatedDuration, 30); return calculatedDuration + "ms " + easing; }, toTransition: () => { } }; return generator; } spring.applyToOptions = (options) => { const generatorOptions = createGeneratorEasing(options, 100, spring); options.ease = generatorOptions.ease; options.duration = /* @__PURE__ */ secondsToMilliseconds(generatorOptions.duration); options.type = "keyframes"; return options; }; function inertia({ keyframes: keyframes2, velocity = 0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed }) { const origin = keyframes2[0]; const state = { done: false, value: origin }; const isOutOfBounds = (v) => min !== void 0 && v < min || max !== void 0 && v > max; const nearestBoundary = (v) => { if (min === void 0) return max; if (max === void 0) return min; return Math.abs(min - v) < Math.abs(max - v) ? min : max; }; let amplitude = power * velocity; const ideal = origin + amplitude; const target = modifyTarget === void 0 ? ideal : modifyTarget(ideal); if (target !== ideal) amplitude = target - origin; const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant); const calcLatest = (t) => target + calcDelta(t); const applyFriction = (t) => { const delta = calcDelta(t); const latest = calcLatest(t); state.done = Math.abs(delta) <= restDelta; state.value = state.done ? target : latest; }; let timeReachedBoundary; let spring$1; const checkCatchBoundary = (t) => { if (!isOutOfBounds(state.value)) return; timeReachedBoundary = t; spring$1 = spring({ keyframes: [state.value, nearestBoundary(state.value)], velocity: calcGeneratorVelocity(calcLatest, t, state.value), // TODO: This should be passing * 1000 damping: bounceDamping, stiffness: bounceStiffness, restDelta, restSpeed }); }; checkCatchBoundary(0); return { calculatedDuration: null, next: (t) => { let hasUpdatedFrame = false; if (!spring$1 && timeReachedBoundary === void 0) { hasUpdatedFrame = true; applyFriction(t); checkCatchBoundary(t); } if (timeReachedBoundary !== void 0 && t >= timeReachedBoundary) { return spring$1.next(t - timeReachedBoundary); } else { !hasUpdatedFrame && applyFriction(t); return state; } } }; } function createMixers(output, ease2, customMixer) { const mixers = []; const mixerFactory = customMixer || MotionGlobalConfig.mix || mix; const numMixers = output.length - 1; for (let i = 0; i < numMixers; i++) { let mixer = mixerFactory(output[i], output[i + 1]); if (ease2) { const easingFunction = Array.isArray(ease2) ? ease2[i] || noop : ease2; mixer = pipe(easingFunction, mixer); } mixers.push(mixer); } return mixers; } function interpolate(input, output, { clamp: isClamp = true, ease: ease2, mixer } = {}) { const inputLength = input.length; exports.invariant(inputLength === output.length, "Both input and output ranges must be the same length", "range-length"); if (inputLength === 1) return () => output[0]; if (inputLength === 2 && output[0] === output[1]) return () => output[1]; const isZeroDeltaRange = input[0] === input[1]; if (input[0] > input[inputLength - 1]) { input = [...input].reverse(); output = [...output].reverse(); } const mixers = createMixers(output, ease2, mixer); const numMixers = mixers.length; const interpolator = (v) => { if (isZeroDeltaRange && v < input[0]) return output[0]; let i = 0; if (numMixers > 1) { for (; i < input.length - 2; i++) { if (v < input[i + 1]) break; } } const progressInRange = /* @__PURE__ */ progress(input[i], input[i + 1], v); return mixers[i](progressInRange); }; return isClamp ? (v) => interpolator(clamp(input[0], input[inputLength - 1], v)) : interpolator; } function fillOffset(offset, remaining) { const min = offset[offset.length - 1]; for (let i = 1; i <= remaining; i++) { const offsetProgress = /* @__PURE__ */ progress(0, remaining, i); offset.push(mixNumber$1(min, 1, offsetProgress)); } } function defaultOffset$1(arr) { const offset = [0]; fillOffset(offset, arr.length - 1); return offset; } function convertOffsetToTimes(offset, duration) { return offset.map((o) => o * duration); } function defaultEasing(values, easing) { return values.map(() => easing || easeInOut).splice(0, values.length - 1); } function keyframes({ duration = 300, keyframes: keyframeValues, times, ease: ease2 = "easeInOut" }) { const easingFunctions = isEasingArray(ease2) ? ease2.map(easingDefinitionToFunction) : easingDefinitionToFunction(ease2); const state = { done: false, value: keyframeValues[0] }; const absoluteTimes = convertOffsetToTimes( // Only use the provided offsets if they're the correct length // TODO Maybe we should warn here if there's a length mismatch times && times.length === keyframeValues.length ? times : defaultOffset$1(keyframeValues), duration ); const mapTimeToKeyframe = interpolate(absoluteTimes, keyframeValues, { ease: Array.isArray(easingFunctions) ? easingFunctions : defaultEasing(keyframeValues, easingFunctions) }); return { calculatedDuration: duration, next: (t) => { state.value = mapTimeToKeyframe(t); state.done = t >= duration; return state; } }; } const isNotNull$1 = (value) => value !== null; function getFinalKeyframe$1(keyframes2, { repeat, repeatType = "loop" }, finalKeyframe, speed = 1) { const resolvedKeyframes = keyframes2.filter(isNotNull$1); const useFirstKeyframe = speed < 0 || repeat && repeatType !== "loop" && repeat % 2 === 1; const index = useFirstKeyframe ? 0 : resolvedKeyframes.length - 1; return !index || finalKeyframe === void 0 ? resolvedKeyframes[index] : finalKeyframe; } const transitionTypeMap = { decay: inertia, inertia, tween: keyframes, keyframes, spring }; function replaceTransitionType(transition) { if (typeof transition.type === "string") { transition.type = transitionTypeMap[transition.type]; } } class WithPromise { constructor() { this.updateFinished(); } get finished() { return this._finished; } updateFinished() { this._finished = new Promise((resolve) => { this.resolve = resolve; }); } notifyFinished() { this.resolve(); } /** * Allows the animation to be awaited. * * @deprecated Use `finished` instead. */ then(onResolve, onReject) { return this.finished.then(onResolve, onReject); } } const percentToProgress = (percent2) => percent2 / 100; class JSAnimation extends WithPromise { constructor(options) { super(); this.state = "idle"; this.startTime = null; this.isStopped = false; this.currentTime = 0; this.holdTime = null; this.playbackSpeed = 1; this.stop = () => { var _a, _b; const { motionValue: motionValue2 } = this.options; if (motionValue2 && motionValue2.updatedAt !== time.now()) { this.tick(time.now()); } this.isStopped = true; if (this.state === "idle") return; this.teardown(); (_b = (_a = this.options).onStop) == null ? void 0 : _b.call(_a); }; activeAnimations.mainThread++; this.options = options; this.initAnimation(); this.play(); if (options.autoplay === false) this.pause(); } initAnimation() { const { options } = this; replaceTransitionType(options); const { type = keyframes, repeat = 0, repeatDelay = 0, repeatType, velocity = 0 } = options; let { keyframes: keyframes$1 } = options; const generatorFactory = type || keyframes; if (process.env.NODE_ENV !== "production" && generatorFactory !== keyframes) { exports.invariant(keyframes$1.length <= 2, `Only two keyframes currently supported with spring and inertia animations. Trying to animate ${keyframes$1}`, "spring-two-frames"); } if (generatorFactory !== keyframes && typeof keyframes$1[0] !== "number") { this.mixKeyframes = pipe(percentToProgress, mix(keyframes$1[0], keyframes$1[1])); keyframes$1 = [0, 100]; } const generator = generatorFactory({ ...options, keyframes: keyframes$1 }); if (repeatType === "mirror") { this.mirroredGenerator = generatorFactory({ ...options, keyframes: [...keyframes$1].reverse(), velocity: -velocity }); } if (generator.calculatedDuration === null) { generator.calculatedDuration = calcGeneratorDuration(generator); } const { calculatedDuration } = generator; this.calculatedDuration = calculatedDuration; this.resolvedDuration = calculatedDuration + repeatDelay; this.totalDuration = this.resolvedDuration * (repeat + 1) - repeatDelay; this.generator = generator; } updateTime(timestamp) { const animationTime = Math.round(timestamp - this.startTime) * this.playbackSpeed; if (this.holdTime !== null) { this.currentTime = this.holdTime; } else { this.currentTime = animationTime; } } tick(timestamp, sample = false) { const { generator, totalDuration, mixKeyframes, mirroredGenerator, resolvedDuration, calculatedDuration } = this; if (this.startTime === null) return generator.next(0); const { delay: delay2 = 0, keyframes: keyframes2, repeat, repeatType, repeatDelay, type, onUpdate, finalKeyframe } = this.options; if (this.speed > 0) { this.startTime = Math.min(this.startTime, timestamp); } else if (this.speed < 0) { this.startTime = Math.min(timestamp - totalDuration / this.speed, this.startTime); } if (sample) { this.currentTime = timestamp; } else { this.updateTime(timestamp); } const timeWithoutDelay = this.currentTime - delay2 * (this.playbackSpeed >= 0 ? 1 : -1); const isInDelayPhase = this.playbackSpeed >= 0 ? timeWithoutDelay < 0 : timeWithoutDelay > totalDuration; this.currentTime = Math.max(timeWithoutDelay, 0); if (this.state === "finished" && this.holdTime === null) { this.currentTime = totalDuration; } let elapsed = this.currentTime; let frameGenerator = generator; if (repeat) { const progress2 = Math.min(this.currentTime, totalDuration) / resolvedDuration; let currentIteration = Math.floor(progress2); let iterationProgress = progress2 % 1; if (!iterationProgress && progress2 >= 1) { iterationProgress = 1; } iterationProgress === 1 && currentIteration--; currentIteration = Math.min(currentIteration, repeat + 1); const isOddIteration = Boolean(currentIteration % 2); if (isOddIteration) { if (repeatType === "reverse") { iterationProgress = 1 - iterationProgress; if (repeatDelay) { iterationProgress -= repeatDelay / resolvedDuration; } } else if (repeatType === "mirror") { frameGenerator = mirroredGenerator; } } elapsed = clamp(0, 1, iterationProgress) * resolvedDuration; } const state = isInDelayPhase ? { done: false, value: keyframes2[0] } : frameGenerator.next(elapsed); if (mixKeyframes) { state.value = mixKeyframes(state.value); } let { done } = state; if (!isInDelayPhase && calculatedDuration !== null) { done = this.playbackSpeed >= 0 ? this.currentTime >= totalDuration : this.currentTime <= 0; } const isAnimationFinished = this.holdTime === null && (this.state === "finished" || this.state === "running" && done); if (isAnimationFinished && type !== inertia) { state.value = getFinalKeyframe$1(keyframes2, this.options, finalKeyframe, this.speed); } if (onUpdate) { onUpdate(state.value); } if (isAnimationFinished) { this.finish(); } return state; } /** * Allows the returned animation to be awaited or promise-chained. Currently * resolves when the animation finishes at all but in a future update could/should * reject if its cancels. */ then(resolve, reject) { return this.finished.then(resolve, reject); } get duration() { return /* @__PURE__ */ millisecondsToSeconds(this.calculatedDuration); } get time() { return /* @__PURE__ */ millisecondsToSeconds(this.currentTime); } set time(newTime) { var _a; newTime = /* @__PURE__ */ secondsToMilliseconds(newTime); this.currentTime = newTime; if (this.startTime === null || this.holdTime !== null || this.playbackSpeed === 0) { this.holdTime = newTime; } else if (this.driver) { this.startTime = this.driver.now() - newTime / this.playbackSpeed; } (_a = this.driver) == null ? void 0 : _a.start(false); } get speed() { return this.playbackSpeed; } set speed(newSpeed) { this.updateTime(time.now()); const hasChanged2 = this.playbackSpeed !== newSpeed; this.playbackSpeed = newSpeed; if (hasChanged2) { this.time = /* @__PURE__ */ millisecondsToSeconds(this.currentTime); } } play() { var _a, _b; if (this.isStopped) return; const { driver = frameloopDriver, startTime } = this.options; if (!this.driver) { this.driver = driver((timestamp) => this.tick(timestamp)); } (_b = (_a = this.options).onPlay) == null ? void 0 : _b.call(_a); const now2 = this.driver.now(); if (this.state === "finished") { this.updateFinished(); this.startTime = now2; } else if (this.holdTime !== null) { this.startTime = now2 - this.holdTime; } else if (!this.startTime) { this.startTime = startTime ?? now2; } if (this.state === "finished" && this.speed < 0) { this.startTime += this.calculatedDuration; } this.holdTime = null; this.state = "running"; this.driver.start(); } pause() { this.state = "paused"; this.updateTime(time.now()); this.holdTime = this.currentTime; } complete() { if (this.state !== "running") { this.play(); } this.state = "finished"; this.holdTime = null; } finish() { var _a, _b; this.notifyFinished(); this.teardown(); this.state = "finished"; (_b = (_a = this.options).onComplete) == null ? void 0 : _b.call(_a); } cancel() { var _a, _b; this.holdTime = null; this.startTime = 0; this.tick(0); this.teardown(); (_b = (_a = this.options).onCancel) == null ? void 0 : _b.call(_a); } teardown() { this.state = "idle"; this.stopDriver(); this.startTime = this.holdTime = null; activeAnimations.mainThread--; } stopDriver() { if (!this.driver) return; this.driver.stop(); this.driver = void 0; } sample(sampleTime) { this.startTime = 0; return this.tick(sampleTime, true); } attachTimeline(timeline) { var _a; if (this.options.allowFlatten) { this.options.type = "keyframes"; this.options.ease = "linear"; this.initAnimation(); } (_a = this.driver) == null ? void 0 : _a.stop(); return timeline.observe(this); } } function animateValue(options) { return new JSAnimation(options); } function fillWildcards(keyframes2) { for (let i = 1; i < keyframes2.length; i++) { keyframes2[i] ?? (keyframes2[i] = keyframes2[i - 1]); } } const radToDeg = (rad) => rad * 180 / Math.PI; const rotate = (v) => { const angle = radToDeg(Math.atan2(v[1], v[0])); return rebaseAngle(angle); }; const matrix2dParsers = { x: 4, y: 5, translateX: 4, translateY: 5, scaleX: 0, scaleY: 3, scale: (v) => (Math.abs(v[0]) + Math.abs(v[3])) / 2, rotate, rotateZ: rotate, skewX: (v) => radToDeg(Math.atan(v[1])), skewY: (v) => radToDeg(Math.atan(v[2])), skew: (v) => (Math.abs(v[1]) + Math.abs(v[2])) / 2 }; const rebaseAngle = (angle) => { angle = angle % 360; if (angle < 0) angle += 360; return angle; }; const rotateZ = rotate; const scaleX = (v) => Math.sqrt(v[0] * v[0] + v[1] * v[1]); const scaleY = (v) => Math.sqrt(v[4] * v[4] + v[5] * v[5]); const matrix3dParsers = { x: 12, y: 13, z: 14, translateX: 12, translateY: 13, translateZ: 14, scaleX, scaleY, scale: (v) => (scaleX(v) + scaleY(v)) / 2, rotateX: (v) => rebaseAngle(radToDeg(Math.atan2(v[6], v[5]))), rotateY: (v) => rebaseAngle(radToDeg(Math.atan2(-v[2], v[0]))), rotateZ, rotate: rotateZ, skewX: (v) => radToDeg(Math.atan(v[4])), skewY: (v) => radToDeg(Math.atan(v[1])), skew: (v) => (Math.abs(v[1]) + Math.abs(v[4])) / 2 }; function defaultTransformValue(name) { return name.includes("scale") ? 1 : 0; } function parseValueFromTransform(transform2, name) { if (!transform2 || transform2 === "none") { return defaultTransformValue(name); } const matrix3dMatch = transform