@react-spring/animated
Version:
Animated component props for React
353 lines (341 loc) • 9.43 kB
JavaScript
// src/Animated.ts
import { defineHidden } from "@react-spring/shared";
var $node = Symbol.for("Animated:node");
var isAnimated = (value) => !!value && value[$node] === value;
var getAnimated = (owner) => owner && owner[$node];
var setAnimated = (owner, node) => defineHidden(owner, $node, node);
var getPayload = (owner) => owner && owner[$node] && owner[$node].getPayload();
var Animated = class {
constructor() {
setAnimated(this, this);
}
/** Get every `AnimatedValue` used by this node. */
getPayload() {
return this.payload || [];
}
};
// src/AnimatedValue.ts
import { is } from "@react-spring/shared";
var AnimatedValue = class _AnimatedValue extends Animated {
constructor(_value) {
super();
this._value = _value;
this.done = true;
this.durationProgress = 0;
if (is.num(this._value)) {
this.lastPosition = this._value;
}
}
/** @internal */
static create(value) {
return new _AnimatedValue(value);
}
getPayload() {
return [this];
}
getValue() {
return this._value;
}
setValue(value, step) {
if (is.num(value)) {
this.lastPosition = value;
if (step) {
value = Math.round(value / step) * step;
if (this.done) {
this.lastPosition = value;
}
}
}
if (this._value === value) {
return false;
}
this._value = value;
return true;
}
reset() {
const { done } = this;
this.done = false;
if (is.num(this._value)) {
this.elapsedTime = 0;
this.durationProgress = 0;
this.lastPosition = this._value;
if (done) this.lastVelocity = null;
this.v0 = null;
}
}
};
// src/AnimatedString.ts
import { is as is2, createInterpolator } from "@react-spring/shared";
var AnimatedString = class _AnimatedString extends AnimatedValue {
constructor(value) {
super(0);
this._string = null;
this._toString = createInterpolator({
output: [value, value]
});
}
/** @internal */
static create(value) {
return new _AnimatedString(value);
}
getValue() {
const value = this._string;
return value == null ? this._string = this._toString(this._value) : value;
}
setValue(value) {
if (is2.str(value)) {
if (value == this._string) {
return false;
}
this._string = value;
this._value = 1;
} else if (super.setValue(value)) {
this._string = null;
} else {
return false;
}
return true;
}
reset(goal) {
if (goal) {
this._toString = createInterpolator({
output: [this.getValue(), goal]
});
}
this._value = 0;
super.reset();
}
};
// src/AnimatedArray.ts
import { isAnimatedString } from "@react-spring/shared";
// src/AnimatedObject.ts
import {
each,
eachProp,
getFluidValue,
hasFluidValue
} from "@react-spring/shared";
// src/context.ts
var TreeContext = { dependencies: null };
// src/AnimatedObject.ts
var AnimatedObject = class extends Animated {
constructor(source) {
super();
this.source = source;
this.setValue(source);
}
getValue(animated) {
const values = {};
eachProp(this.source, (source, key) => {
if (isAnimated(source)) {
values[key] = source.getValue(animated);
} else if (hasFluidValue(source)) {
values[key] = getFluidValue(source);
} else if (!animated) {
values[key] = source;
}
});
return values;
}
/** Replace the raw object data */
setValue(source) {
this.source = source;
this.payload = this._makePayload(source);
}
reset() {
if (this.payload) {
each(this.payload, (node) => node.reset());
}
}
/** Create a payload set. */
_makePayload(source) {
if (source) {
const payload = /* @__PURE__ */ new Set();
eachProp(source, this._addToPayload, payload);
return Array.from(payload);
}
}
/** Add to a payload set. */
_addToPayload(source) {
if (TreeContext.dependencies && hasFluidValue(source)) {
TreeContext.dependencies.add(source);
}
const payload = getPayload(source);
if (payload) {
each(payload, (node) => this.add(node));
}
}
};
// src/AnimatedArray.ts
var AnimatedArray = class _AnimatedArray extends AnimatedObject {
constructor(source) {
super(source);
}
/** @internal */
static create(source) {
return new _AnimatedArray(source);
}
getValue() {
return this.source.map((node) => node.getValue());
}
setValue(source) {
const payload = this.getPayload();
if (source.length == payload.length) {
return payload.map((node, i) => node.setValue(source[i])).some(Boolean);
}
super.setValue(source.map(makeAnimated));
return true;
}
};
function makeAnimated(value) {
const nodeType = isAnimatedString(value) ? AnimatedString : AnimatedValue;
return nodeType.create(value);
}
// src/getAnimatedType.ts
import { is as is3, isAnimatedString as isAnimatedString2 } from "@react-spring/shared";
function getAnimatedType(value) {
const parentNode = getAnimated(value);
return parentNode ? parentNode.constructor : is3.arr(value) ? AnimatedArray : isAnimatedString2(value) ? AnimatedString : AnimatedValue;
}
// src/createHost.ts
import { is as is5, eachProp as eachProp2 } from "@react-spring/shared";
// src/withAnimated.tsx
import * as React from "react";
import { forwardRef, useRef, useCallback, useEffect } from "react";
import {
is as is4,
each as each2,
raf,
useForceUpdate,
useOnce,
addFluidObserver,
removeFluidObserver,
useIsomorphicLayoutEffect
} from "@react-spring/shared";
var withAnimated = (Component, host) => {
const hasInstance = (
// Function components must use "forwardRef" to avoid being
// re-rendered on every animation frame.
!is4.fun(Component) || Component.prototype && Component.prototype.isReactComponent
);
return forwardRef((givenProps, givenRef) => {
const instanceRef = useRef(null);
const ref = hasInstance && // eslint-disable-next-line react-hooks/rules-of-hooks
useCallback(
(value) => {
instanceRef.current = updateRef(givenRef, value);
},
[givenRef]
);
const [props, deps] = getAnimatedState(givenProps, host);
const forceUpdate = useForceUpdate();
const callback = () => {
const instance = instanceRef.current;
if (hasInstance && !instance) {
return;
}
const didUpdate = instance ? host.applyAnimatedValues(instance, props.getValue(true)) : false;
if (didUpdate === false) {
forceUpdate();
}
};
const observer = new PropsObserver(callback, deps);
const observerRef = useRef(void 0);
useIsomorphicLayoutEffect(() => {
observerRef.current = observer;
each2(deps, (dep) => addFluidObserver(dep, observer));
return () => {
if (observerRef.current) {
each2(
observerRef.current.deps,
(dep) => removeFluidObserver(dep, observerRef.current)
);
raf.cancel(observerRef.current.update);
}
};
});
useEffect(callback, []);
useOnce(() => () => {
const observer2 = observerRef.current;
each2(observer2.deps, (dep) => removeFluidObserver(dep, observer2));
});
const usedProps = host.getComponentProps(props.getValue());
return /* @__PURE__ */ React.createElement(Component, { ...usedProps, ref });
});
};
var PropsObserver = class {
constructor(update, deps) {
this.update = update;
this.deps = deps;
}
eventObserved(event) {
if (event.type == "change") {
raf.write(this.update);
}
}
};
function getAnimatedState(props, host) {
const dependencies = /* @__PURE__ */ new Set();
TreeContext.dependencies = dependencies;
if (props.style)
props = {
...props,
style: host.createAnimatedStyle(props.style)
};
props = new AnimatedObject(props);
TreeContext.dependencies = null;
return [props, dependencies];
}
function updateRef(ref, value) {
if (ref) {
if (is4.fun(ref)) ref(value);
else ref.current = value;
}
return value;
}
// src/createHost.ts
var cacheKey = Symbol.for("AnimatedComponent");
var createHost = (components, {
applyAnimatedValues = () => false,
createAnimatedStyle = (style) => new AnimatedObject(style),
getComponentProps = (props) => props
} = {}) => {
const hostConfig = {
applyAnimatedValues,
createAnimatedStyle,
getComponentProps
};
const animated = (Component) => {
const displayName = getDisplayName(Component) || "Anonymous";
if (is5.str(Component)) {
Component = animated[Component] || (animated[Component] = withAnimated(Component, hostConfig));
} else {
Component = Component[cacheKey] || (Component[cacheKey] = withAnimated(Component, hostConfig));
}
Component.displayName = `Animated(${displayName})`;
return Component;
};
eachProp2(components, (Component, key) => {
if (is5.arr(components)) {
key = getDisplayName(Component);
}
animated[key] = animated(Component);
});
return {
animated
};
};
var getDisplayName = (arg) => is5.str(arg) ? arg : arg && is5.str(arg.displayName) ? arg.displayName : is5.fun(arg) && arg.name || null;
export {
Animated,
AnimatedArray,
AnimatedObject,
AnimatedString,
AnimatedValue,
createHost,
getAnimated,
getAnimatedType,
getPayload,
isAnimated,
setAnimated
};