'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var tslib = require('tslib'); var heyListen = require('hey-listen'); var styleValueTypes = require('style-value-types'); var sync = require('framesync'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var sync__default = /*#__PURE__*/_interopDefaultLegacy(sync); const clamp = (min, max, v) => Math.min(Math.max(v, min), max); const safeMin = 0.001; const minDuration = 0.01; const maxDuration = 10.0; const minDamping = 0.05; const maxDamping = 1; function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) { let envelope; let derivative; heyListen.warning(duration <= maxDuration * 1000, "Spring duration must be 10 seconds or less"); let dampingRatio = 1 - bounce; dampingRatio = clamp(minDamping, maxDamping, dampingRatio); duration = clamp(minDuration, maxDuration, duration / 1000); if (dampingRatio < 1) { envelope = (undampedFreq) => { const exponentialDecay = undampedFreq * dampingRatio; const delta = exponentialDecay * duration; const a = exponentialDecay - velocity; const b = calcAngularFreq(undampedFreq, dampingRatio); const c = Math.exp(-delta); return safeMin - (a / b) * c; }; derivative = (undampedFreq) => { const exponentialDecay = undampedFreq * dampingRatio; const delta = exponentialDecay * duration; const d = delta * velocity + velocity; const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration; const f = Math.exp(-delta); const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio); const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1; return (factor * ((d - e) * f)) / g; }; } else { envelope = (undampedFreq) => { const a = Math.exp(-undampedFreq * duration); const b = (undampedFreq - velocity) * duration + 1; return -safeMin + a * b; }; derivative = (undampedFreq) => { const a = Math.exp(-undampedFreq * duration); const b = (velocity - undampedFreq) * (duration * duration); return a * b; }; } const initialGuess = 5 / duration; const undampedFreq = approximateRoot(envelope, derivative, initialGuess); duration = duration * 1000; if (isNaN(undampedFreq)) { return { stiffness: 100, damping: 10, 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, keys) { return keys.some((key) => options[key] !== undefined); } function getSpringOptions(options) { let springOptions = Object.assign({ velocity: 0.0, stiffness: 100, damping: 10, mass: 1.0, isResolvedFromDuration: false }, options); if (!isSpringType(options, physicsKeys) && isSpringType(options, durationKeys)) { const derived = findSpring(options); springOptions = Object.assign(Object.assign(Object.assign({}, springOptions), derived), { velocity: 0.0, mass: 1.0 }); springOptions.isResolvedFromDuration = true; } return springOptions; } function spring(_a) { var { from = 0.0, to = 1.0, restSpeed = 2, restDelta } = _a, options = tslib.__rest(_a, ["from", "to", "restSpeed", "restDelta"]); const state = { done: false, value: from }; let { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options); let resolveSpring = zero; let resolveVelocity = zero; function createSpring() { const initialVelocity = velocity ? -(velocity / 1000) : 0.0; const initialDelta = to - from; const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass)); const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000; if (restDelta === undefined) { restDelta = Math.min(Math.abs(to - from) / 100, 0.4); } if (dampingRatio < 1) { const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio); resolveSpring = (t) => { const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t); return (to - envelope * (((initialVelocity + dampingRatio * undampedAngularFreq * initialDelta) / angularFreq) * Math.sin(angularFreq * t) + initialDelta * Math.cos(angularFreq * t))); }; resolveVelocity = (t) => { const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t); return (dampingRatio * undampedAngularFreq * envelope * ((Math.sin(angularFreq * t) * (initialVelocity + dampingRatio * undampedAngularFreq * initialDelta)) / angularFreq + initialDelta * Math.cos(angularFreq * t)) - envelope * (Math.cos(angularFreq * t) * (initialVelocity + dampingRatio * undampedAngularFreq * initialDelta) - angularFreq * initialDelta * Math.sin(angularFreq * t))); }; } else if (dampingRatio === 1) { resolveSpring = (t) => to - 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 (to - (envelope * ((initialVelocity + dampingRatio * undampedAngularFreq * initialDelta) * Math.sinh(freqForT) + dampedAngularFreq * initialDelta * Math.cosh(freqForT))) / dampedAngularFreq); }; } } createSpring(); return { next: (t) => { const current = resolveSpring(t); if (!isResolvedFromDuration) { const currentVelocity = resolveVelocity(t) * 1000; const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed; const isBelowDisplacementThreshold = Math.abs(to - current) <= restDelta; state.done = isBelowVelocityThreshold && isBelowDisplacementThreshold; } else { state.done = t >= duration; } state.value = state.done ? to : current; return state; }, flipTarget: () => { velocity = -velocity; [from, to] = [to, from]; createSpring(); }, }; } spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string"; const zero = (_t) => 0; const progress = (from, to, value) => { const toFromDifference = to - from; return toFromDifference === 0 ? 1 : (value - from) / toFromDifference; }; const mix = (from, to, progress) => -progress * from + progress * to + from; 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 }) { 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, }; } const mixLinearColor = (from, to, v) => { const fromExpo = from * from; const toExpo = to * to; return Math.sqrt(Math.max(0, v * (toExpo - fromExpo) + fromExpo)); }; const colorTypes = [styleValueTypes.hex, styleValueTypes.rgba, styleValueTypes.hsla]; const getColorType = (v) => colorTypes.find((type) => type.test(v)); const notAnimatable = (color) => `'${color}' is not an animatable color. Use the equivalent color code instead.`; const mixColor = (from, to) => { let fromColorType = getColorType(from); let toColorType = getColorType(to); heyListen.invariant(!!fromColorType, notAnimatable(from)); heyListen.invariant(!!toColorType, notAnimatable(to)); let fromColor = fromColorType.parse(from); let toColor = toColorType.parse(to); if (fromColorType === styleValueTypes.hsla) { fromColor = hslaToRgba(fromColor); fromColorType = styleValueTypes.rgba; } if (toColorType === styleValueTypes.hsla) { toColor = hslaToRgba(toColor); toColorType = styleValueTypes.rgba; } const blended = Object.assign({}, fromColor); return (v) => { for (const key in blended) { if (key !== "alpha") { blended[key] = mixLinearColor(fromColor[key], toColor[key], v); } } blended.alpha = mix(fromColor.alpha, toColor.alpha, v); return fromColorType.transform(blended); }; }; const zeroPoint = { x: 0, y: 0, z: 0 }; const isNum = (v) => typeof v === 'number'; const combineFunctions = (a, b) => (v) => b(a(v)); const pipe = (...transformers) => transformers.reduce(combineFunctions); function getMixer(origin, target) { if (isNum(origin)) { return (v) => mix(origin, target, v); } else if (styleValueTypes.color.test(origin)) { return mixColor(origin, target); } else { return mixComplex(origin, target); } } const mixArray = (from, to) => { const output = [...from]; const numValues = output.length; const blendValue = from.map((fromThis, i) => getMixer(fromThis, to[i])); return (v) => { for (let i = 0; i < numValues; i++) { output[i] = blendValue[i](v); } return output; }; }; const mixObject = (origin, target) => { const output = Object.assign(Object.assign({}, origin), target); const blendValue = {}; for (const key in output) { if (origin[key] !== undefined && target[key] !== undefined) { blendValue[key] = getMixer(origin[key], target[key]); } } return (v) => { for (const key in blendValue) { output[key] = blendValue[key](v); } return output; }; }; function analyse(value) { const parsed = styleValueTypes.complex.parse(value); const numValues = parsed.length; let numNumbers = 0; let numRGB = 0; let numHSL = 0; for (let i = 0; i < numValues; i++) { if (numNumbers || typeof parsed[i] === "number") { numNumbers++; } else { if (parsed[i].hue !== undefined) { numHSL++; } else { numRGB++; } } } return { parsed, numNumbers, numRGB, numHSL }; } const mixComplex = (origin, target) => { const template = styleValueTypes.complex.createTransformer(target); const originStats = analyse(origin); const targetStats = analyse(target); const canInterpolate = originStats.numHSL === targetStats.numHSL && originStats.numRGB === targetStats.numRGB && originStats.numNumbers >= targetStats.numNumbers; if (canInterpolate) { return pipe(mixArray(originStats.parsed, targetStats.parsed), template); } else { heyListen.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.`); return (p) => `${p > 0 ? target : origin}`; } }; const mixNumber = (from, to) => (p) => mix(from, to, p); function detectMixerFactory(v) { if (typeof v === 'number') { return mixNumber; } else if (typeof v === 'string') { if (styleValueTypes.color.test(v)) { return mixColor; } else { return mixComplex; } } else if (Array.isArray(v)) { return mixArray; } else if (typeof v === 'object') { return mixObject; } } function createMixers(output, ease, customMixer) { const mixers = []; const mixerFactory = customMixer || detectMixerFactory(output[0]); const numMixers = output.length - 1; for (let i = 0; i < numMixers; i++) { let mixer = mixerFactory(output[i], output[i + 1]); if (ease) { const easingFunction = Array.isArray(ease) ? ease[i] : ease; mixer = pipe(easingFunction, mixer); } mixers.push(mixer); } return mixers; } function fastInterpolate([from, to], [mixer]) { return (v) => mixer(progress(from, to, v)); } function slowInterpolate(input, mixers) { const inputLength = input.length; const lastInputIndex = inputLength - 1; return (v) => { let mixerIndex = 0; let foundMixerIndex = false; if (v <= input[0]) { foundMixerIndex = true; } else if (v >= input[lastInputIndex]) { mixerIndex = lastInputIndex - 1; foundMixerIndex = true; } if (!foundMixerIndex) { let i = 1; for (; i < inputLength; i++) { if (input[i] > v || i === lastInputIndex) { break; } } mixerIndex = i - 1; } const progressInRange = progress(input[mixerIndex], input[mixerIndex + 1], v); return mixers[mixerIndex](progressInRange); }; } function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) { const inputLength = input.length; heyListen.invariant(inputLength === output.length, 'Both input and output ranges must be the same length'); heyListen.invariant(!ease || !Array.isArray(ease) || ease.length === inputLength - 1, 'Array of easing functions must be of length `input.length - 1`, as it applies to the transitions **between** the defined values.'); if (input[0] > input[inputLength - 1]) { input = [].concat(input); output = [].concat(output); input.reverse(); output.reverse(); } const mixers = createMixers(output, ease, mixer); const interpolator = inputLength === 2 ? fastInterpolate(input, mixers) : slowInterpolate(input, mixers); return isClamp ? (v) => interpolator(clamp(input[0], input[inputLength - 1], v)) : interpolator; } const reverseEasing = easing => p => 1 - easing(1 - p); const mirrorEasing = easing => p => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2; const createExpoIn = (power) => p => Math.pow(p, power); const createBackIn = (power) => p => p * p * ((power + 1) * p - power); const createAnticipate = (power) => { const backEasing = createBackIn(power); return p => (p *= 2) < 1 ? 0.5 * backEasing(p) : 0.5 * (2 - Math.pow(2, -10 * (p - 1))); }; const DEFAULT_OVERSHOOT_STRENGTH = 1.525; const BOUNCE_FIRST_THRESHOLD = 4.0 / 11.0; const BOUNCE_SECOND_THRESHOLD = 8.0 / 11.0; const BOUNCE_THIRD_THRESHOLD = 9.0 / 10.0; const linear = p => p; const easeIn = createExpoIn(2); const easeOut = reverseEasing(easeIn); const easeInOut = mirrorEasing(easeIn); const circIn = p => 1 - Math.sin(Math.acos(p)); const circOut = reverseEasing(circIn); const circInOut = mirrorEasing(circOut); const backIn = createBackIn(DEFAULT_OVERSHOOT_STRENGTH); const backOut = reverseEasing(backIn); const backInOut = mirrorEasing(backIn); const anticipate = createAnticipate(DEFAULT_OVERSHOOT_STRENGTH); const ca = 4356.0 / 361.0; const cb = 35442.0 / 1805.0; const cc = 16061.0 / 1805.0; const bounceOut = (p) => { if (p === 1 || p === 0) return p; const p2 = p * p; return p < BOUNCE_FIRST_THRESHOLD ? 7.5625 * p2 : p < BOUNCE_SECOND_THRESHOLD ? 9.075 * p2 - 9.9 * p + 3.4 : p < BOUNCE_THIRD_THRESHOLD ? ca * p2 - cb * p + cc : 10.8 * p * p - 20.52 * p + 10.72; }; const bounceIn = reverseEasing(bounceOut); const bounceInOut = (p) => p < 0.5 ? 0.5 * (1.0 - bounceOut(1.0 - p * 2.0)) : 0.5 * bounceOut(p * 2.0 - 1.0) + 0.5; function defaultEasing(values, easing) { return values.map(() => easing || easeInOut).splice(0, values.length - 1); } function defaultOffset(values) { const numValues = values.length; return values.map((_value, i) => i !== 0 ? i / (numValues - 1) : 0); } function convertOffsetToTimes(offset, duration) { return offset.map((o) => o * duration); } function keyframes({ from = 0, to = 1, ease, offset, duration = 300, }) { const state = { done: false, value: from }; const values = Array.isArray(to) ? to : [from, to]; const times = convertOffsetToTimes(offset && offset.length === values.length ? offset : defaultOffset(values), duration); function createInterpolator() { return interpolate(times, values, { ease: Array.isArray(ease) ? ease : defaultEasing(values, ease), }); } let interpolator = createInterpolator(); return { next: (t) => { state.value = interpolator(t); state.done = t >= duration; return state; }, flipTarget: () => { values.reverse(); interpolator = createInterpolator(); }, }; } function decay({ velocity = 0, from = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) { const state = { done: false, value: from }; let amplitude = power * velocity; const ideal = from + amplitude; const target = modifyTarget === undefined ? ideal : modifyTarget(ideal); if (target !== ideal) amplitude = target - from; return { next: (t) => { const delta = -amplitude * Math.exp(-t / timeConstant); state.done = !(delta > restDelta || delta < -restDelta); state.value = state.done ? target : target + delta; return state; }, flipTarget: () => { }, }; } const types = { keyframes, spring, decay }; function detectAnimationFromOptions(config) { if (Array.isArray(config.to)) { return keyframes; } else if (types[config.type]) { return types[config.type]; } const keys = new Set(Object.keys(config)); if (keys.has("ease") || (keys.has("duration") && !keys.has("dampingRatio"))) { return keyframes; } else if (keys.has("dampingRatio") || keys.has("stiffness") || keys.has("mass") || keys.has("damping") || keys.has("restSpeed") || keys.has("restDelta")) { return spring; } return keyframes; } function loopElapsed(elapsed, duration, delay = 0) { return elapsed - duration - delay; } function reverseElapsed(elapsed, duration, delay = 0, isForwardPlayback = true) { return isForwardPlayback ? loopElapsed(duration + -elapsed, duration, delay) : duration - (elapsed - duration) + delay; } function hasRepeatDelayElapsed(elapsed, duration, delay, isForwardPlayback) { return isForwardPlayback ? elapsed >= duration + delay : elapsed <= -delay; } const framesync = (update) => { const passTimestamp = ({ delta }) => update(delta); return { start: () => sync__default['default'].update(passTimestamp, true), stop: () => sync.cancelSync.update(passTimestamp), }; }; function animate(_a) { var _b, _c; var { from, autoplay = true, driver = framesync, elapsed = 0, repeat: repeatMax = 0, repeatType = "loop", repeatDelay = 0, onPlay, onStop, onComplete, onRepeat, onUpdate } = _a, options = tslib.__rest(_a, ["from", "autoplay", "driver", "elapsed", "repeat", "repeatType", "repeatDelay", "onPlay", "onStop", "onComplete", "onRepeat", "onUpdate"]); let { to } = options; let driverControls; let repeatCount = 0; let computedDuration = options.duration; let latest; let isComplete = false; let isForwardPlayback = true; let interpolateFromNumber; const animator = detectAnimationFromOptions(options); if ((_c = (_b = animator).needsInterpolation) === null || _c === void 0 ? void 0 : _c.call(_b, from, to)) { interpolateFromNumber = interpolate([0, 100], [from, to], { clamp: false, }); from = 0; to = 100; } const animation = animator(Object.assign(Object.assign({}, options), { from, to })); function repeat() { repeatCount++; if (repeatType === "reverse") { isForwardPlayback = repeatCount % 2 === 0; elapsed = reverseElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback); } else { elapsed = loopElapsed(elapsed, computedDuration, repeatDelay); if (repeatType === "mirror") animation.flipTarget(); } isComplete = false; onRepeat && onRepeat(); } function complete() { driverControls.stop(); onComplete && onComplete(); } function update(delta) { if (!isForwardPlayback) delta = -delta; elapsed += delta; if (!isComplete) { const state = animation.next(Math.max(0, elapsed)); latest = state.value; if (interpolateFromNumber) latest = interpolateFromNumber(latest); isComplete = isForwardPlayback ? state.done : elapsed <= 0; } onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(latest); if (isComplete) { if (repeatCount === 0) computedDuration !== null && computedDuration !== void 0 ? computedDuration : (computedDuration = elapsed); if (repeatCount < repeatMax) { hasRepeatDelayElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback) && repeat(); } else { complete(); } } } function play() { onPlay === null || onPlay === void 0 ? void 0 : onPlay(); driverControls = driver(update); driverControls.start(); } autoplay && play(); return { stop: () => { onStop === null || onStop === void 0 ? void 0 : onStop(); driverControls.stop(); }, }; } function velocityPerSecond(velocity, frameDuration) { return frameDuration ? velocity * (1000 / frameDuration) : 0; } function inertia({ from = 0, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) { let currentAnimation; function isOutOfBounds(v) { return (min !== undefined && v < min) || (max !== undefined && v > max); } function boundaryNearest(v) { if (min === undefined) return max; if (max === undefined) return min; return Math.abs(min - v) < Math.abs(max - v) ? min : max; } function startAnimation(options) { currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop(); currentAnimation = animate(Object.assign(Object.assign({}, options), { driver, onUpdate: (v) => { var _a; onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(v); (_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, v); }, onComplete, onStop })); } function startSpring(options) { startAnimation(Object.assign({ type: "spring", stiffness: bounceStiffness, damping: bounceDamping, restDelta }, options)); } if (isOutOfBounds(from)) { startSpring({ from, velocity, to: boundaryNearest(from) }); } else { let target = power * velocity + from; if (typeof modifyTarget !== "undefined") target = modifyTarget(target); const boundary = boundaryNearest(target); const heading = boundary === min ? -1 : 1; let prev; let current; const checkBoundary = (v) => { prev = current; current = v; velocity = velocityPerSecond(v - prev, sync.getFrameData().delta); if ((heading === 1 && v > boundary) || (heading === -1 && v < boundary)) { startSpring({ from: v, to: boundary, velocity }); } }; startAnimation({ type: "decay", from, velocity, timeConstant, power, restDelta, modifyTarget, onUpdate: isOutOfBounds(target) ? checkBoundary : undefined, }); } return { stop: () => currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop(), }; } const radiansToDegrees = (radians) => (radians * 180) / Math.PI; const angle = (a, b = zeroPoint) => radiansToDegrees(Math.atan2(b.y - a.y, b.x - a.x)); const applyOffset = (from, to) => { let hasReceivedFrom = true; if (to === undefined) { to = from; hasReceivedFrom = false; } return (v) => { if (hasReceivedFrom) { return v - from + to; } else { from = v; hasReceivedFrom = true; return to; } }; }; const identity = (v) => v; const createAttractor = (alterDisplacement = identity) => (constant, origin, v) => { const displacement = origin - v; const springModifiedDisplacement = -(0 - constant + 1) * (0 - alterDisplacement(Math.abs(displacement))); return displacement <= 0 ? origin + springModifiedDisplacement : origin - springModifiedDisplacement; }; const attract = createAttractor(); const attractExpo = createAttractor(Math.sqrt); const degreesToRadians = (degrees) => (degrees * Math.PI) / 180; const isPoint = (point) => point.hasOwnProperty('x') && point.hasOwnProperty('y'); const isPoint3D = (point) => isPoint(point) && point.hasOwnProperty('z'); const distance1D = (a, b) => Math.abs(a - b); function distance(a, b) { if (isNum(a) && isNum(b)) { return distance1D(a, b); } else if (isPoint(a) && isPoint(b)) { const xDelta = distance1D(a.x, b.x); const yDelta = distance1D(a.y, b.y); const zDelta = isPoint3D(a) && isPoint3D(b) ? distance1D(a.z, b.z) : 0; return Math.sqrt(Math.pow(xDelta, 2) + Math.pow(yDelta, 2) + Math.pow(zDelta, 2)); } } const pointFromVector = (origin, angle, distance) => { angle = degreesToRadians(angle); return { x: distance * Math.cos(angle) + origin.x, y: distance * Math.sin(angle) + origin.y }; }; const toDecimal = (num, precision = 2) => { precision = Math.pow(10, precision); return Math.round(num * precision) / precision; }; const smoothFrame = (prevValue, nextValue, duration, smoothing = 0) => toDecimal(prevValue + (duration * (nextValue - prevValue)) / Math.max(smoothing, duration)); const smooth = (strength = 50) => { let previousValue = 0; let lastUpdated = 0; return (v) => { const currentFramestamp = sync.getFrameData().timestamp; const timeDelta = currentFramestamp !== lastUpdated ? currentFramestamp - lastUpdated : 0; const newValue = timeDelta ? smoothFrame(previousValue, v, timeDelta, strength) : previousValue; lastUpdated = currentFramestamp; previousValue = newValue; return newValue; }; }; const snap = (points) => { if (typeof points === 'number') { return (v) => Math.round(v / points) * points; } else { let i = 0; const numPoints = points.length; return (v) => { let lastDistance = Math.abs(points[0] - v); for (i = 1; i < numPoints; i++) { const point = points[i]; const distance = Math.abs(point - v); if (distance === 0) return point; if (distance > lastDistance) return points[i - 1]; if (i === numPoints - 1) return point; lastDistance = distance; } }; } }; function velocityPerFrame(xps, frameDuration) { return xps / (1000 / frameDuration); } const wrap = (min, max, v) => { const rangeSize = max - min; return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min; }; const a = (a1, a2) => 1.0 - 3.0 * a2 + 3.0 * a1; const b = (a1, a2) => 3.0 * a2 - 6.0 * a1; const c = (a1) => 3.0 * a1; const calcBezier = (t, a1, a2) => ((a(a1, a2) * t + b(a1, a2)) * t + c(a1)) * t; const getSlope = (t, a1, a2) => 3.0 * a(a1, a2) * t * t + 2.0 * b(a1, a2) * t + c(a1); const subdivisionPrecision = 0.0000001; const subdivisionMaxIterations = 10; function binarySubdivide(aX, aA, aB, mX1, mX2) { let currentX; let currentT; let i = 0; do { currentT = aA + (aB - aA) / 2.0; currentX = calcBezier(currentT, mX1, mX2) - aX; if (currentX > 0.0) { aB = currentT; } else { aA = currentT; } } while (Math.abs(currentX) > subdivisionPrecision && ++i < subdivisionMaxIterations); return currentT; } const newtonIterations = 8; const newtonMinSlope = 0.001; function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) { for (let i = 0; i < newtonIterations; ++i) { const currentSlope = getSlope(aGuessT, mX1, mX2); if (currentSlope === 0.0) { return aGuessT; } const currentX = calcBezier(aGuessT, mX1, mX2) - aX; aGuessT -= currentX / currentSlope; } return aGuessT; } const kSplineTableSize = 11; const kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); function cubicBezier(mX1, mY1, mX2, mY2) { if (mX1 === mY1 && mX2 === mY2) return linear; const sampleValues = new Float32Array(kSplineTableSize); for (let i = 0; i < kSplineTableSize; ++i) { sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); } function getTForX(aX) { let intervalStart = 0.0; let currentSample = 1; const lastSample = kSplineTableSize - 1; for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) { intervalStart += kSampleStepSize; } --currentSample; const dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]); const guessForT = intervalStart + dist * kSampleStepSize; const initialSlope = getSlope(guessForT, mX1, mX2); if (initialSlope >= newtonMinSlope) { return newtonRaphsonIterate(aX, guessForT, mX1, mX2); } else if (initialSlope === 0.0) { return guessForT; } else { return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); } } return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2); } const steps = (steps, direction = 'end') => (progress) => { progress = direction === 'end' ? Math.min(progress, 0.999) : Math.max(progress, 0.001); const expanded = progress * steps; const rounded = direction === 'end' ? Math.floor(expanded) : Math.ceil(expanded); return clamp(0, 1, rounded / steps); }; exports.angle = angle; exports.animate = animate; exports.anticipate = anticipate; exports.applyOffset = applyOffset; exports.attract = attract; exports.attractExpo = attractExpo; exports.backIn = backIn; exports.backInOut = backInOut; exports.backOut = backOut; exports.bounceIn = bounceIn; exports.bounceInOut = bounceInOut; exports.bounceOut = bounceOut; exports.circIn = circIn; exports.circInOut = circInOut; exports.circOut = circOut; exports.clamp = clamp; exports.createAnticipate = createAnticipate; exports.createAttractor = createAttractor; exports.createBackIn = createBackIn; exports.createExpoIn = createExpoIn; exports.cubicBezier = cubicBezier; exports.decay = decay; exports.degreesToRadians = degreesToRadians; exports.distance = distance; exports.easeIn = easeIn; exports.easeInOut = easeInOut; exports.easeOut = easeOut; exports.inertia = inertia; exports.interpolate = interpolate; exports.isPoint = isPoint; exports.isPoint3D = isPoint3D; exports.keyframes = keyframes; exports.linear = linear; exports.mirrorEasing = mirrorEasing; exports.mix = mix; exports.mixColor = mixColor; exports.mixComplex = mixComplex; exports.pipe = pipe; exports.pointFromVector = pointFromVector; exports.progress = progress; exports.radiansToDegrees = radiansToDegrees; exports.reverseEasing = reverseEasing; exports.smooth = smooth; exports.smoothFrame = smoothFrame; exports.snap = snap; exports.spring = spring; exports.steps = steps; exports.toDecimal = toDecimal; exports.velocityPerFrame = velocityPerFrame; exports.velocityPerSecond = velocityPerSecond; exports.wrap = wrap;