UNPKG

@react-spring/core

Version:

The platform-agnostic core of `react-spring`

1,593 lines (1,574 loc) 76.3 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to2, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to2, key) && key !== except) __defProp(to2, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to2; }; var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { BailSignal: () => BailSignal, Controller: () => Controller, FrameValue: () => FrameValue, Globals: () => import_shared21.Globals, Interpolation: () => Interpolation, Spring: () => Spring, SpringContext: () => SpringContext, SpringRef: () => SpringRef, SpringValue: () => SpringValue, Trail: () => Trail, Transition: () => Transition, config: () => config, createInterpolator: () => import_shared22.createInterpolator, easings: () => import_shared22.easings, inferTo: () => inferTo, interpolate: () => interpolate, to: () => to, update: () => update, useChain: () => useChain, useInView: () => useInView, useIsomorphicLayoutEffect: () => import_shared22.useIsomorphicLayoutEffect, useReducedMotion: () => import_shared22.useReducedMotion, useResize: () => useResize, useScroll: () => useScroll, useSpring: () => useSpring, useSpringRef: () => useSpringRef, useSpringValue: () => useSpringValue, useSprings: () => useSprings, useTrail: () => useTrail, useTransition: () => useTransition }); module.exports = __toCommonJS(src_exports); // src/hooks/useChain.ts var import_shared2 = require("@react-spring/shared"); // src/helpers.ts var import_shared = require("@react-spring/shared"); function callProp(value, ...args) { return import_shared.is.fun(value) ? value(...args) : value; } var matchProp = (value, key) => value === true || !!(key && value && (import_shared.is.fun(value) ? value(key) : (0, import_shared.toArray)(value).includes(key))); var resolveProp = (prop, key) => import_shared.is.obj(prop) ? key && prop[key] : prop; var getDefaultProp = (props, key) => props.default === true ? props[key] : props.default ? props.default[key] : void 0; var noopTransform = (value) => value; var getDefaultProps = (props, transform = noopTransform) => { let keys = DEFAULT_PROPS; if (props.default && props.default !== true) { props = props.default; keys = Object.keys(props); } const defaults2 = {}; for (const key of keys) { const value = transform(props[key], key); if (!import_shared.is.und(value)) { defaults2[key] = value; } } return defaults2; }; var DEFAULT_PROPS = [ "config", "onProps", "onStart", "onChange", "onPause", "onResume", "onRest" ]; var RESERVED_PROPS = { config: 1, from: 1, to: 1, ref: 1, loop: 1, reset: 1, pause: 1, cancel: 1, reverse: 1, immediate: 1, default: 1, delay: 1, onProps: 1, onStart: 1, onChange: 1, onPause: 1, onResume: 1, onRest: 1, onResolve: 1, // Transition props items: 1, trail: 1, sort: 1, expires: 1, initial: 1, enter: 1, update: 1, leave: 1, children: 1, onDestroyed: 1, // Internal props keys: 1, callId: 1, parentId: 1 }; function getForwardProps(props) { const forward = {}; let count = 0; (0, import_shared.eachProp)(props, (value, prop) => { if (!RESERVED_PROPS[prop]) { forward[prop] = value; count++; } }); if (count) { return forward; } } function inferTo(props) { const to2 = getForwardProps(props); if (to2) { const out = { to: to2 }; (0, import_shared.eachProp)(props, (val, key) => key in to2 || (out[key] = val)); return out; } return { ...props }; } function computeGoal(value) { value = (0, import_shared.getFluidValue)(value); return import_shared.is.arr(value) ? value.map(computeGoal) : (0, import_shared.isAnimatedString)(value) ? import_shared.Globals.createStringInterpolator({ range: [0, 1], output: [value, value] })(1) : value; } function hasProps(props) { for (const _ in props) return true; return false; } function isAsyncTo(to2) { return import_shared.is.fun(to2) || import_shared.is.arr(to2) && import_shared.is.obj(to2[0]); } function detachRefs(ctrl, ref) { ctrl.ref?.delete(ctrl); ref?.delete(ctrl); } function replaceRef(ctrl, ref) { if (ref && ctrl.ref !== ref) { ctrl.ref?.delete(ctrl); ref.add(ctrl); ctrl.ref = ref; } } // src/hooks/useChain.ts function useChain(refs, timeSteps, timeFrame = 1e3) { (0, import_shared2.useIsomorphicLayoutEffect)(() => { if (timeSteps) { let prevDelay = 0; (0, import_shared2.each)(refs, (ref, i) => { const controllers = ref.current; if (controllers.length) { let delay = timeFrame * timeSteps[i]; if (isNaN(delay)) delay = prevDelay; else prevDelay = delay; (0, import_shared2.each)(controllers, (ctrl) => { (0, import_shared2.each)(ctrl.queue, (props) => { const memoizedDelayProp = props.delay; props.delay = (key) => delay + callProp(memoizedDelayProp || 0, key); }); }); ref.start(); } }); } else { let p = Promise.resolve(); (0, import_shared2.each)(refs, (ref) => { const controllers = ref.current; if (controllers.length) { const queues = controllers.map((ctrl) => { const q = ctrl.queue; ctrl.queue = []; return q; }); p = p.then(() => { (0, import_shared2.each)( controllers, (ctrl, i) => (0, import_shared2.each)(queues[i] || [], (update2) => ctrl.queue.push(update2)) ); return Promise.all(ref.start()); }); } }); } }); } // src/hooks/useSpring.ts var import_shared11 = require("@react-spring/shared"); // src/hooks/useSprings.ts var import_react2 = require("react"); var import_shared10 = require("@react-spring/shared"); // src/SpringValue.ts var import_shared7 = require("@react-spring/shared"); var import_animated2 = require("@react-spring/animated"); // src/AnimationConfig.ts var import_shared3 = require("@react-spring/shared"); // src/constants.ts var config = { default: { tension: 170, friction: 26 }, gentle: { tension: 120, friction: 14 }, wobbly: { tension: 180, friction: 12 }, stiff: { tension: 210, friction: 20 }, slow: { tension: 280, friction: 60 }, molasses: { tension: 280, friction: 120 } }; // src/AnimationConfig.ts var defaults = { ...config.default, mass: 1, damping: 1, easing: import_shared3.easings.linear, clamp: false }; var AnimationConfig = class { constructor() { /** * The initial velocity of one or more values. * * @default 0 */ this.velocity = 0; Object.assign(this, defaults); } }; function mergeConfig(config2, newConfig, defaultConfig) { if (defaultConfig) { defaultConfig = { ...defaultConfig }; sanitizeConfig(defaultConfig, newConfig); newConfig = { ...defaultConfig, ...newConfig }; } sanitizeConfig(config2, newConfig); Object.assign(config2, newConfig); for (const key in defaults) { if (config2[key] == null) { config2[key] = defaults[key]; } } let { frequency, damping } = config2; const { mass } = config2; if (!import_shared3.is.und(frequency)) { if (frequency < 0.01) frequency = 0.01; if (damping < 0) damping = 0; config2.tension = Math.pow(2 * Math.PI / frequency, 2) * mass; config2.friction = 4 * Math.PI * damping * mass / frequency; } return config2; } function sanitizeConfig(config2, props) { if (!import_shared3.is.und(props.decay)) { config2.duration = void 0; } else { const isTensionConfig = !import_shared3.is.und(props.tension) || !import_shared3.is.und(props.friction); if (isTensionConfig || !import_shared3.is.und(props.frequency) || !import_shared3.is.und(props.damping) || !import_shared3.is.und(props.mass)) { config2.duration = void 0; config2.decay = void 0; } if (isTensionConfig) { config2.frequency = void 0; } } } // src/Animation.ts var emptyArray = []; var Animation = class { constructor() { this.changed = false; this.values = emptyArray; this.toValues = null; this.fromValues = emptyArray; this.config = new AnimationConfig(); this.immediate = false; } }; // src/scheduleProps.ts var import_shared4 = require("@react-spring/shared"); function scheduleProps(callId, { key, props, defaultProps, state, actions }) { return new Promise((resolve, reject) => { let delay; let timeout; let cancel = matchProp(props.cancel ?? defaultProps?.cancel, key); if (cancel) { onStart(); } else { if (!import_shared4.is.und(props.pause)) { state.paused = matchProp(props.pause, key); } let pause = defaultProps?.pause; if (pause !== true) { pause = state.paused || matchProp(pause, key); } delay = callProp(props.delay || 0, key); if (pause) { state.resumeQueue.add(onResume); actions.pause(); } else { actions.resume(); onResume(); } } function onPause() { state.resumeQueue.add(onResume); state.timeouts.delete(timeout); timeout.cancel(); delay = timeout.time - import_shared4.raf.now(); } function onResume() { if (delay > 0 && !import_shared4.Globals.skipAnimation) { state.delayed = true; timeout = import_shared4.raf.setTimeout(onStart, delay); state.pauseQueue.add(onPause); state.timeouts.add(timeout); } else { onStart(); } } function onStart() { if (state.delayed) { state.delayed = false; } state.pauseQueue.delete(onPause); state.timeouts.delete(timeout); if (callId <= (state.cancelId || 0)) { cancel = true; } try { actions.start({ ...props, callId, cancel }, resolve); } catch (err) { reject(err); } } }); } // src/runAsync.ts var import_shared5 = require("@react-spring/shared"); // src/AnimationResult.ts var getCombinedResult = (target, results) => results.length == 1 ? results[0] : results.some((result) => result.cancelled) ? getCancelledResult(target.get()) : results.every((result) => result.noop) ? getNoopResult(target.get()) : getFinishedResult( target.get(), results.every((result) => result.finished) ); var getNoopResult = (value) => ({ value, noop: true, finished: true, cancelled: false }); var getFinishedResult = (value, finished, cancelled = false) => ({ value, finished, cancelled }); var getCancelledResult = (value) => ({ value, cancelled: true, finished: false }); // src/runAsync.ts function runAsync(to2, props, state, target) { const { callId, parentId, onRest } = props; const { asyncTo: prevTo, promise: prevPromise } = state; if (!parentId && to2 === prevTo && !props.reset) { return prevPromise; } return state.promise = (async () => { state.asyncId = callId; state.asyncTo = to2; const defaultProps = getDefaultProps( props, (value, key) => ( // The `onRest` prop is only called when the `runAsync` promise is resolved. key === "onRest" ? void 0 : value ) ); let preventBail; let bail; const bailPromise = new Promise( (resolve, reject) => (preventBail = resolve, bail = reject) ); const bailIfEnded = (bailSignal) => { const bailResult = ( // The `cancel` prop or `stop` method was used. callId <= (state.cancelId || 0) && getCancelledResult(target) || // The async `to` prop was replaced. callId !== state.asyncId && getFinishedResult(target, false) ); if (bailResult) { bailSignal.result = bailResult; bail(bailSignal); throw bailSignal; } }; const animate = (arg1, arg2) => { const bailSignal = new BailSignal(); const skipAnimationSignal = new SkipAnimationSignal(); return (async () => { if (import_shared5.Globals.skipAnimation) { stopAsync(state); skipAnimationSignal.result = getFinishedResult(target, false); bail(skipAnimationSignal); throw skipAnimationSignal; } bailIfEnded(bailSignal); const props2 = import_shared5.is.obj(arg1) ? { ...arg1 } : { ...arg2, to: arg1 }; props2.parentId = callId; (0, import_shared5.eachProp)(defaultProps, (value, key) => { if (import_shared5.is.und(props2[key])) { props2[key] = value; } }); const result2 = await target.start(props2); bailIfEnded(bailSignal); if (state.paused) { await new Promise((resume) => { state.resumeQueue.add(resume); }); } return result2; })(); }; let result; if (import_shared5.Globals.skipAnimation) { stopAsync(state); return getFinishedResult(target, false); } try { let animating; if (import_shared5.is.arr(to2)) { animating = (async (queue) => { for (const props2 of queue) { await animate(props2); } })(to2); } else { animating = Promise.resolve(to2(animate, target.stop.bind(target))); } await Promise.all([animating.then(preventBail), bailPromise]); result = getFinishedResult(target.get(), true, false); } catch (err) { if (err instanceof BailSignal) { result = err.result; } else if (err instanceof SkipAnimationSignal) { result = err.result; } else { throw err; } } finally { if (callId == state.asyncId) { state.asyncId = parentId; state.asyncTo = parentId ? prevTo : void 0; state.promise = parentId ? prevPromise : void 0; } } if (import_shared5.is.fun(onRest)) { import_shared5.raf.batchedUpdates(() => { onRest(result, target, target.item); }); } return result; })(); } function stopAsync(state, cancelId) { (0, import_shared5.flush)(state.timeouts, (t) => t.cancel()); state.pauseQueue.clear(); state.resumeQueue.clear(); state.asyncId = state.asyncTo = state.promise = void 0; if (cancelId) state.cancelId = cancelId; } var BailSignal = class extends Error { constructor() { super( "An async animation has been interrupted. You see this error because you forgot to use `await` or `.catch(...)` on its returned promise." ); } }; var SkipAnimationSignal = class extends Error { constructor() { super("SkipAnimationSignal"); } }; // src/FrameValue.ts var import_shared6 = require("@react-spring/shared"); var import_animated = require("@react-spring/animated"); var isFrameValue = (value) => value instanceof FrameValue; var nextId = 1; var FrameValue = class extends import_shared6.FluidValue { constructor() { super(...arguments); this.id = nextId++; this._priority = 0; } get priority() { return this._priority; } set priority(priority) { if (this._priority != priority) { this._priority = priority; this._onPriorityChange(priority); } } /** Get the current value */ get() { const node = (0, import_animated.getAnimated)(this); return node && node.getValue(); } /** Create a spring that maps our value to another value */ to(...args) { return import_shared6.Globals.to(this, args); } /** @deprecated Use the `to` method instead. */ interpolate(...args) { (0, import_shared6.deprecateInterpolate)(); return import_shared6.Globals.to(this, args); } toJSON() { return this.get(); } observerAdded(count) { if (count == 1) this._attach(); } observerRemoved(count) { if (count == 0) this._detach(); } /** Called when the first child is added. */ _attach() { } /** Called when the last child is removed. */ _detach() { } /** Tell our children about our new value */ _onChange(value, idle = false) { (0, import_shared6.callFluidObservers)(this, { type: "change", parent: this, value, idle }); } /** Tell our children about our new priority */ _onPriorityChange(priority) { if (!this.idle) { import_shared6.frameLoop.sort(this); } (0, import_shared6.callFluidObservers)(this, { type: "priority", parent: this, priority }); } }; // src/SpringPhase.ts var $P = Symbol.for("SpringPhase"); var HAS_ANIMATED = 1; var IS_ANIMATING = 2; var IS_PAUSED = 4; var hasAnimated = (target) => (target[$P] & HAS_ANIMATED) > 0; var isAnimating = (target) => (target[$P] & IS_ANIMATING) > 0; var isPaused = (target) => (target[$P] & IS_PAUSED) > 0; var setActiveBit = (target, active) => active ? target[$P] |= IS_ANIMATING | HAS_ANIMATED : target[$P] &= ~IS_ANIMATING; var setPausedBit = (target, paused) => paused ? target[$P] |= IS_PAUSED : target[$P] &= ~IS_PAUSED; // src/SpringValue.ts var SpringValue = class extends FrameValue { constructor(arg1, arg2) { super(); /** The animation state */ this.animation = new Animation(); /** Some props have customizable default values */ this.defaultProps = {}; /** The state for `runAsync` calls */ this._state = { paused: false, delayed: false, pauseQueue: /* @__PURE__ */ new Set(), resumeQueue: /* @__PURE__ */ new Set(), timeouts: /* @__PURE__ */ new Set() }; /** The promise resolvers of pending `start` calls */ this._pendingCalls = /* @__PURE__ */ new Set(); /** The counter for tracking `scheduleProps` calls */ this._lastCallId = 0; /** The last `scheduleProps` call that changed the `to` prop */ this._lastToId = 0; this._memoizedDuration = 0; if (!import_shared7.is.und(arg1) || !import_shared7.is.und(arg2)) { const props = import_shared7.is.obj(arg1) ? { ...arg1 } : { ...arg2, from: arg1 }; if (import_shared7.is.und(props.default)) { props.default = true; } this.start(props); } } /** Equals true when not advancing on each frame. */ get idle() { return !(isAnimating(this) || this._state.asyncTo) || isPaused(this); } get goal() { return (0, import_shared7.getFluidValue)(this.animation.to); } get velocity() { const node = (0, import_animated2.getAnimated)(this); return node instanceof import_animated2.AnimatedValue ? node.lastVelocity || 0 : node.getPayload().map((node2) => node2.lastVelocity || 0); } /** * When true, this value has been animated at least once. */ get hasAnimated() { return hasAnimated(this); } /** * When true, this value has an unfinished animation, * which is either active or paused. */ get isAnimating() { return isAnimating(this); } /** * When true, all current and future animations are paused. */ get isPaused() { return isPaused(this); } /** * * */ get isDelayed() { return this._state.delayed; } /** Advance the current animation by a number of milliseconds */ advance(dt) { let idle = true; let changed = false; const anim = this.animation; let { toValues } = anim; const { config: config2 } = anim; const payload = (0, import_animated2.getPayload)(anim.to); if (!payload && (0, import_shared7.hasFluidValue)(anim.to)) { toValues = (0, import_shared7.toArray)((0, import_shared7.getFluidValue)(anim.to)); } anim.values.forEach((node2, i) => { if (node2.done) return; const to2 = ( // Animated strings always go from 0 to 1. node2.constructor == import_animated2.AnimatedString ? 1 : payload ? payload[i].lastPosition : toValues[i] ); let finished = anim.immediate; let position = to2; if (!finished) { position = node2.lastPosition; if (config2.tension <= 0) { node2.done = true; return; } let elapsed = node2.elapsedTime += dt; const from = anim.fromValues[i]; const v0 = node2.v0 != null ? node2.v0 : node2.v0 = import_shared7.is.arr(config2.velocity) ? config2.velocity[i] : config2.velocity; let velocity; const precision = config2.precision || (from == to2 ? 5e-3 : Math.min(1, Math.abs(to2 - from) * 1e-3)); if (!import_shared7.is.und(config2.duration)) { let p = 1; if (config2.duration > 0) { if (this._memoizedDuration !== config2.duration) { this._memoizedDuration = config2.duration; if (node2.durationProgress > 0) { node2.elapsedTime = config2.duration * node2.durationProgress; elapsed = node2.elapsedTime += dt; } } p = (config2.progress || 0) + elapsed / this._memoizedDuration; p = p > 1 ? 1 : p < 0 ? 0 : p; node2.durationProgress = p; } position = from + config2.easing(p) * (to2 - from); velocity = (position - node2.lastPosition) / dt; finished = p == 1; } else if (config2.decay) { const decay = config2.decay === true ? 0.998 : config2.decay; const e = Math.exp(-(1 - decay) * elapsed); position = from + v0 / (1 - decay) * (1 - e); finished = Math.abs(node2.lastPosition - position) <= precision; velocity = v0 * e; } else { velocity = node2.lastVelocity == null ? v0 : node2.lastVelocity; const restVelocity = config2.restVelocity || precision / 10; const bounceFactor = config2.clamp ? 0 : config2.bounce; const canBounce = !import_shared7.is.und(bounceFactor); const isGrowing = from == to2 ? node2.v0 > 0 : from < to2; let isMoving; let isBouncing = false; const step = 1; const numSteps = Math.ceil(dt / step); for (let n = 0; n < numSteps; ++n) { isMoving = Math.abs(velocity) > restVelocity; if (!isMoving) { finished = Math.abs(to2 - position) <= precision; if (finished) { break; } } if (canBounce) { isBouncing = position == to2 || position > to2 == isGrowing; if (isBouncing) { velocity = -velocity * bounceFactor; position = to2; } } const springForce = -config2.tension * 1e-6 * (position - to2); const dampingForce = -config2.friction * 1e-3 * velocity; const acceleration = (springForce + dampingForce) / config2.mass; velocity = velocity + acceleration * step; position = position + velocity * step; } } node2.lastVelocity = velocity; if (Number.isNaN(position)) { console.warn(`Got NaN while animating:`, this); finished = true; } } if (payload && !payload[i].done) { finished = false; } if (finished) { node2.done = true; } else { idle = false; } if (node2.setValue(position, config2.round)) { changed = true; } }); const node = (0, import_animated2.getAnimated)(this); const currVal = node.getValue(); if (idle) { const finalVal = (0, import_shared7.getFluidValue)(anim.to); if ((currVal !== finalVal || changed) && !config2.decay) { node.setValue(finalVal); this._onChange(finalVal); } else if (changed && config2.decay) { this._onChange(currVal); } this._stop(); } else if (changed) { this._onChange(currVal); } } /** Set the current value, while stopping the current animation */ set(value) { import_shared7.raf.batchedUpdates(() => { this._stop(); this._focus(value); this._set(value); }); return this; } /** * Freeze the active animation in time, as well as any updates merged * before `resume` is called. */ pause() { this._update({ pause: true }); } /** Resume the animation if paused. */ resume() { this._update({ pause: false }); } /** Skip to the end of the current animation. */ finish() { if (isAnimating(this)) { const { to: to2, config: config2 } = this.animation; import_shared7.raf.batchedUpdates(() => { this._onStart(); if (!config2.decay) { this._set(to2, false); } this._stop(); }); } return this; } /** Push props into the pending queue. */ update(props) { const queue = this.queue || (this.queue = []); queue.push(props); return this; } start(to2, arg2) { let queue; if (!import_shared7.is.und(to2)) { queue = [import_shared7.is.obj(to2) ? to2 : { ...arg2, to: to2 }]; } else { queue = this.queue || []; this.queue = []; } return Promise.all( queue.map((props) => { const up = this._update(props); return up; }) ).then((results) => getCombinedResult(this, results)); } /** * Stop the current animation, and cancel any delayed updates. * * Pass `true` to call `onRest` with `cancelled: true`. */ stop(cancel) { const { to: to2 } = this.animation; this._focus(this.get()); stopAsync(this._state, cancel && this._lastCallId); import_shared7.raf.batchedUpdates(() => this._stop(to2, cancel)); return this; } /** Restart the animation. */ reset() { this._update({ reset: true }); } /** @internal */ eventObserved(event) { if (event.type == "change") { this._start(); } else if (event.type == "priority") { this.priority = event.priority + 1; } } /** * Parse the `to` and `from` range from the given `props` object. * * This also ensures the initial value is available to animated components * during the render phase. */ _prepareNode(props) { const key = this.key || ""; let { to: to2, from } = props; to2 = import_shared7.is.obj(to2) ? to2[key] : to2; if (to2 == null || isAsyncTo(to2)) { to2 = void 0; } from = import_shared7.is.obj(from) ? from[key] : from; if (from == null) { from = void 0; } const range = { to: to2, from }; if (!hasAnimated(this)) { if (props.reverse) [to2, from] = [from, to2]; from = (0, import_shared7.getFluidValue)(from); if (!import_shared7.is.und(from)) { this._set(from); } else if (!(0, import_animated2.getAnimated)(this)) { this._set(to2); } } return range; } /** Every update is processed by this method before merging. */ _update({ ...props }, isLoop) { const { key, defaultProps } = this; if (props.default) Object.assign( defaultProps, getDefaultProps( props, (value, prop) => /^on/.test(prop) ? resolveProp(value, key) : value ) ); mergeActiveFn(this, props, "onProps"); sendEvent(this, "onProps", props, this); const range = this._prepareNode(props); if (Object.isFrozen(this)) { throw Error( "Cannot animate a `SpringValue` object that is frozen. Did you forget to pass your component to `animated(...)` before animating its props?" ); } const state = this._state; return scheduleProps(++this._lastCallId, { key, props, defaultProps, state, actions: { pause: () => { if (!isPaused(this)) { setPausedBit(this, true); (0, import_shared7.flushCalls)(state.pauseQueue); sendEvent( this, "onPause", getFinishedResult(this, checkFinished(this, this.animation.to)), this ); } }, resume: () => { if (isPaused(this)) { setPausedBit(this, false); if (isAnimating(this)) { this._resume(); } (0, import_shared7.flushCalls)(state.resumeQueue); sendEvent( this, "onResume", getFinishedResult(this, checkFinished(this, this.animation.to)), this ); } }, start: this._merge.bind(this, range) } }).then((result) => { if (props.loop && result.finished && !(isLoop && result.noop)) { const nextProps = createLoopUpdate(props); if (nextProps) { return this._update(nextProps, true); } } return result; }); } /** Merge props into the current animation */ _merge(range, props, resolve) { if (props.cancel) { this.stop(true); return resolve(getCancelledResult(this)); } const hasToProp = !import_shared7.is.und(range.to); const hasFromProp = !import_shared7.is.und(range.from); if (hasToProp || hasFromProp) { if (props.callId > this._lastToId) { this._lastToId = props.callId; } else { return resolve(getCancelledResult(this)); } } const { key, defaultProps, animation: anim } = this; const { to: prevTo, from: prevFrom } = anim; let { to: to2 = prevTo, from = prevFrom } = range; if (hasFromProp && !hasToProp && (!props.default || import_shared7.is.und(to2))) { to2 = from; } if (props.reverse) [to2, from] = [from, to2]; const hasFromChanged = !(0, import_shared7.isEqual)(from, prevFrom); if (hasFromChanged) { anim.from = from; } from = (0, import_shared7.getFluidValue)(from); const hasToChanged = !(0, import_shared7.isEqual)(to2, prevTo); if (hasToChanged) { this._focus(to2); } const hasAsyncTo = isAsyncTo(props.to); const { config: config2 } = anim; const { decay, velocity } = config2; if (hasToProp || hasFromProp) { config2.velocity = 0; } if (props.config && !hasAsyncTo) { mergeConfig( config2, callProp(props.config, key), // Avoid calling the same "config" prop twice. props.config !== defaultProps.config ? callProp(defaultProps.config, key) : void 0 ); } let node = (0, import_animated2.getAnimated)(this); if (!node || import_shared7.is.und(to2)) { return resolve(getFinishedResult(this, true)); } const reset = ( // When `reset` is undefined, the `from` prop implies `reset: true`, // except for declarative updates. When `reset` is defined, there // must exist a value to animate from. import_shared7.is.und(props.reset) ? hasFromProp && !props.default : !import_shared7.is.und(from) && matchProp(props.reset, key) ); const value = reset ? from : this.get(); const goal = computeGoal(to2); const isAnimatable = import_shared7.is.num(goal) || import_shared7.is.arr(goal) || (0, import_shared7.isAnimatedString)(goal); const immediate = !hasAsyncTo && (!isAnimatable || matchProp(defaultProps.immediate || props.immediate, key)); if (hasToChanged) { const nodeType = (0, import_animated2.getAnimatedType)(to2); if (nodeType !== node.constructor) { if (immediate) { node = this._set(goal); } else throw Error( `Cannot animate between ${node.constructor.name} and ${nodeType.name}, as the "to" prop suggests` ); } } const goalType = node.constructor; let started = (0, import_shared7.hasFluidValue)(to2); let finished = false; if (!started) { const hasValueChanged = reset || !hasAnimated(this) && hasFromChanged; if (hasToChanged || hasValueChanged) { finished = (0, import_shared7.isEqual)(computeGoal(value), goal); started = !finished; } if (!(0, import_shared7.isEqual)(anim.immediate, immediate) && !immediate || !(0, import_shared7.isEqual)(config2.decay, decay) || !(0, import_shared7.isEqual)(config2.velocity, velocity)) { started = true; } } if (finished && isAnimating(this)) { if (anim.changed && !reset) { started = true; } else if (!started) { this._stop(prevTo); } } if (!hasAsyncTo) { if (started || (0, import_shared7.hasFluidValue)(prevTo)) { anim.values = node.getPayload(); anim.toValues = (0, import_shared7.hasFluidValue)(to2) ? null : goalType == import_animated2.AnimatedString ? [1] : (0, import_shared7.toArray)(goal); } if (anim.immediate != immediate) { anim.immediate = immediate; if (!immediate && !reset) { this._set(prevTo); } } if (started) { const { onRest } = anim; (0, import_shared7.each)(ACTIVE_EVENTS, (type) => mergeActiveFn(this, props, type)); const result = getFinishedResult(this, checkFinished(this, prevTo)); (0, import_shared7.flushCalls)(this._pendingCalls, result); this._pendingCalls.add(resolve); if (anim.changed) import_shared7.raf.batchedUpdates(() => { anim.changed = !reset; onRest?.(result, this); if (reset) { callProp(defaultProps.onRest, result); } else { anim.onStart?.(result, this); } }); } } if (reset) { this._set(value); } if (hasAsyncTo) { resolve(runAsync(props.to, props, this._state, this)); } else if (started) { this._start(); } else if (isAnimating(this) && !hasToChanged) { this._pendingCalls.add(resolve); } else { resolve(getNoopResult(value)); } } /** Update the `animation.to` value, which might be a `FluidValue` */ _focus(value) { const anim = this.animation; if (value !== anim.to) { if ((0, import_shared7.getFluidObservers)(this)) { this._detach(); } anim.to = value; if ((0, import_shared7.getFluidObservers)(this)) { this._attach(); } } } _attach() { let priority = 0; const { to: to2 } = this.animation; if ((0, import_shared7.hasFluidValue)(to2)) { (0, import_shared7.addFluidObserver)(to2, this); if (isFrameValue(to2)) { priority = to2.priority + 1; } } this.priority = priority; } _detach() { const { to: to2 } = this.animation; if ((0, import_shared7.hasFluidValue)(to2)) { (0, import_shared7.removeFluidObserver)(to2, this); } } /** * Update the current value from outside the frameloop, * and return the `Animated` node. */ _set(arg, idle = true) { const value = (0, import_shared7.getFluidValue)(arg); if (!import_shared7.is.und(value)) { const oldNode = (0, import_animated2.getAnimated)(this); if (!oldNode || !(0, import_shared7.isEqual)(value, oldNode.getValue())) { const nodeType = (0, import_animated2.getAnimatedType)(value); if (!oldNode || oldNode.constructor != nodeType) { (0, import_animated2.setAnimated)(this, nodeType.create(value)); } else { oldNode.setValue(value); } if (oldNode) { import_shared7.raf.batchedUpdates(() => { this._onChange(value, idle); }); } } } return (0, import_animated2.getAnimated)(this); } _onStart() { const anim = this.animation; if (!anim.changed) { anim.changed = true; sendEvent( this, "onStart", getFinishedResult(this, checkFinished(this, anim.to)), this ); } } _onChange(value, idle) { if (!idle) { this._onStart(); callProp(this.animation.onChange, value, this); } callProp(this.defaultProps.onChange, value, this); super._onChange(value, idle); } // This method resets the animation state (even if already animating) to // ensure the latest from/to range is used, and it also ensures this spring // is added to the frameloop. _start() { const anim = this.animation; (0, import_animated2.getAnimated)(this).reset((0, import_shared7.getFluidValue)(anim.to)); if (!anim.immediate) { anim.fromValues = anim.values.map((node) => node.lastPosition); } if (!isAnimating(this)) { setActiveBit(this, true); if (!isPaused(this)) { this._resume(); } } } _resume() { if (import_shared7.Globals.skipAnimation) { this.finish(); } else { import_shared7.frameLoop.start(this); } } /** * Exit the frameloop and notify `onRest` listeners. * * Always wrap `_stop` calls with `batchedUpdates`. */ _stop(goal, cancel) { if (isAnimating(this)) { setActiveBit(this, false); const anim = this.animation; (0, import_shared7.each)(anim.values, (node) => { node.done = true; }); if (anim.toValues) { anim.onChange = anim.onPause = anim.onResume = void 0; } (0, import_shared7.callFluidObservers)(this, { type: "idle", parent: this }); const result = cancel ? getCancelledResult(this.get()) : getFinishedResult(this.get(), checkFinished(this, goal ?? anim.to)); (0, import_shared7.flushCalls)(this._pendingCalls, result); if (anim.changed) { anim.changed = false; sendEvent(this, "onRest", result, this); } } } }; function checkFinished(target, to2) { const goal = computeGoal(to2); const value = computeGoal(target.get()); return (0, import_shared7.isEqual)(value, goal); } function createLoopUpdate(props, loop = props.loop, to2 = props.to) { const loopRet = callProp(loop); if (loopRet) { const overrides = loopRet !== true && inferTo(loopRet); const reverse = (overrides || props).reverse; const reset = !overrides || overrides.reset; return createUpdate({ ...props, loop, // Avoid updating default props when looping. default: false, // Never loop the `pause` prop. pause: void 0, // For the "reverse" prop to loop as expected, the "to" prop // must be undefined. The "reverse" prop is ignored when the // "to" prop is an array or function. to: !reverse || isAsyncTo(to2) ? to2 : void 0, // Ignore the "from" prop except on reset. from: reset ? props.from : void 0, reset, // The "loop" prop can return a "useSpring" props object to // override any of the original props. ...overrides }); } } function createUpdate(props) { const { to: to2, from } = props = inferTo(props); const keys = /* @__PURE__ */ new Set(); if (import_shared7.is.obj(to2)) findDefined(to2, keys); if (import_shared7.is.obj(from)) findDefined(from, keys); props.keys = keys.size ? Array.from(keys) : null; return props; } function declareUpdate(props) { const update2 = createUpdate(props); if (import_shared7.is.und(update2.default)) { update2.default = getDefaultProps(update2); } return update2; } function findDefined(values, keys) { (0, import_shared7.eachProp)(values, (value, key) => value != null && keys.add(key)); } var ACTIVE_EVENTS = [ "onStart", "onRest", "onChange", "onPause", "onResume" ]; function mergeActiveFn(target, props, type) { target.animation[type] = props[type] !== getDefaultProp(props, type) ? resolveProp(props[type], target.key) : void 0; } function sendEvent(target, type, ...args) { target.animation[type]?.(...args); target.defaultProps[type]?.(...args); } // src/Controller.ts var import_shared8 = require("@react-spring/shared"); var BATCHED_EVENTS = ["onStart", "onChange", "onRest"]; var nextId2 = 1; var Controller = class { constructor(props, flush3) { this.id = nextId2++; /** The animated values */ this.springs = {}; /** The queue of props passed to the `update` method. */ this.queue = []; /** The counter for tracking `scheduleProps` calls */ this._lastAsyncId = 0; /** The values currently being animated */ this._active = /* @__PURE__ */ new Set(); /** The values that changed recently */ this._changed = /* @__PURE__ */ new Set(); /** Equals false when `onStart` listeners can be called */ this._started = false; /** State used by the `runAsync` function */ this._state = { paused: false, pauseQueue: /* @__PURE__ */ new Set(), resumeQueue: /* @__PURE__ */ new Set(), timeouts: /* @__PURE__ */ new Set() }; /** The event queues that are flushed once per frame maximum */ this._events = { onStart: /* @__PURE__ */ new Map(), onChange: /* @__PURE__ */ new Map(), onRest: /* @__PURE__ */ new Map() }; this._onFrame = this._onFrame.bind(this); if (flush3) { this._flush = flush3; } if (props) { this.start({ default: true, ...props }); } } /** * Equals `true` when no spring values are in the frameloop, and * no async animation is currently active. */ get idle() { return !this._state.asyncTo && Object.values(this.springs).every((spring) => { return spring.idle && !spring.isDelayed && !spring.isPaused; }); } get item() { return this._item; } set item(item) { this._item = item; } /** Get the current values of our springs */ get() { const values = {}; this.each((spring, key) => values[key] = spring.get()); return values; } /** Set the current values without animating. */ set(values) { for (const key in values) { const value = values[key]; if (!import_shared8.is.und(value)) { this.springs[key].set(value); } } } /** Push an update onto the queue of each value. */ update(props) { if (props) { this.queue.push(createUpdate(props)); } return this; } /** * Start the queued animations for every spring, and resolve the returned * promise once all queued animations have finished or been cancelled. * * When you pass a queue (instead of nothing), that queue is used instead of * the queued animations added with the `update` method, which are left alone. */ start(props) { let { queue } = this; if (props) { queue = (0, import_shared8.toArray)(props).map(createUpdate); } else { this.queue = []; } if (this._flush) { return this._flush(this, queue); } prepareKeys(this, queue); return flushUpdateQueue(this, queue); } /** @internal */ stop(arg, keys) { if (arg !== !!arg) { keys = arg; } if (keys) { const springs = this.springs; (0, import_shared8.each)((0, import_shared8.toArray)(keys), (key) => springs[key].stop(!!arg)); } else { stopAsync(this._state, this._lastAsyncId); this.each((spring) => spring.stop(!!arg)); } return this; } /** Freeze the active animation in time */ pause(keys) { if (import_shared8.is.und(keys)) { this.start({ pause: true }); } else { const springs = this.springs; (0, import_shared8.each)((0, import_shared8.toArray)(keys), (key) => springs[key].pause()); } return this; } /** Resume the animation if paused. */ resume(keys) { if (import_shared8.is.und(keys)) { this.start({ pause: false }); } else { const springs = this.springs; (0, import_shared8.each)((0, import_shared8.toArray)(keys), (key) => springs[key].resume()); } return this; } /** Call a function once per spring value */ each(iterator) { (0, import_shared8.eachProp)(this.springs, iterator); } /** @internal Called at the end of every animation frame */ _onFrame() { const { onStart, onChange, onRest } = this._events; const active = this._active.size > 0; const changed = this._changed.size > 0; if (active && !this._started || changed && !this._started) { this._started = true; (0, import_shared8.flush)(onStart, ([onStart2, result]) => { result.value = this.get(); onStart2(result, this, this._item); }); } const idle = !active && this._started; const values = changed || idle && onRest.size ? this.get() : null; if (changed && onChange.size) { (0, import_shared8.flush)(onChange, ([onChange2, result]) => { result.value = values; onChange2(result, this, this._item); }); } if (idle) { this._started = false; (0, import_shared8.flush)(onRest, ([onRest2, result]) => { result.value = values; onRest2(result, this, this._item); }); } } /** @internal */ eventObserved(event) { if (event.type == "change") { this._changed.add(event.parent); if (!event.idle) { this._active.add(event.parent); } } else if (event.type == "idle") { this._active.delete(event.parent); } else return; import_shared8.raf.onFrame(this._onFrame); } }; function flushUpdateQueue(ctrl, queue) { return Promise.all(queue.map((props) => flushUpdate(ctrl, props))).then( (results) => getCombinedResult(ctrl, results) ); } async function flushUpdate(ctrl, props, isLoop) { const { keys, to: to2, from, loop, onRest, onResolve } = props; const defaults2 = import_shared8.is.obj(props.default) && props.default; if (loop) { props.loop = false; } if (to2 === false) props.to = null; if (from === false) props.from = null; const asyncTo = import_shared8.is.arr(to2) || import_shared8.is.fun(to2) ? to2 : void 0; if (asyncTo) { props.to = void 0; props.onRest = void 0; if (defaults2) { defaults2.onRest = void 0; } } else { (0, import_shared8.each)(BATCHED_EVENTS, (key) => { const handler = props[key]; if (import_shared8.is.fun(handler)) { const queue = ctrl["_events"][key]; props[key] = ({ finished, cancelled }) => { const result2 = queue.get(handler); if (result2) { if (!finished) result2.finished = false; if (cancelled) result2.cancelled = true; } else { queue.set(handler, { value: null, finished: finished || false, cancelled: cancelled || false }); } }; if (defaults2) { defaults2[key] = props[key]; } } }); } const state = ctrl["_state"]; if (props.pause === !state.paused) { state.paused = props.pause; (0, import_shared8.flushCalls)(props.pause ? state.pauseQueue : state.resumeQueue); } else if (state.paused) { props.pause = true; } const promises = (keys || Object.keys(ctrl.springs)).map( (key) => ctrl.springs[key].start(props) ); const cancel = props.cancel === true || getDefaultProp(props, "cancel") === true; if (asyncTo || cancel && state.asyncId) { promises.push( scheduleProps(++ctrl["_lastAsyncId"], { props, state, actions: { pause: import_shared8.noop, resume: import_shared8.noop, start(props2, resolve) { if (cancel) { stopAsync(state, ctrl["_lastAsyncId"]); resolve(getCancelledResult(ctrl)); } else { props2.onRest = onRest; resolve( runAsync( asyncTo, props2, state, ctrl ) ); } } } }) ); } if (state.paused) { await new Promise((resume) => { state.resumeQueue.add(resume); }); } const result = getCombinedResult(ctrl, await Promise.all(promises)); if (loop && result.finished && !(isLoop && result.noop)) { const nextProps = createLoopUpdate(props, loop, to2); if (nextProps) { prepareKeys(ctrl, [nextProps]); return flushUpdate(ctrl, nextProps, true); } } if (onResolve) { import_shared8.raf.batchedUpdates(() => onResolve(result, ctrl, ctrl.item)); } return result; } function getSprings(ctrl, props) { const springs = { ...ctrl.springs }; if (props) { (0, import_shared8.each)((0, import_shared8.toArray)(props), (props2) => { if (import_shared8.is.und(props2.keys)) { props2 = createUpdate(props2); } if (!import_shared8.is.obj(props2.to)) { props2 = { ...props2, to: void 0 }; } prepareSprings(springs, props2, (key) => { return createSpring(key); }); }); } setSprings(ctrl, springs); return springs; } function setSprings(ctrl, springs) { (0, import_shared8.eachProp)(springs, (spring, key) => { if (!ctrl.springs[key]) { ctrl.springs[key] = spring; (0, i