first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,628 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import type * as React from 'react';
import {ScrollView} from '../Components/ScrollView/ScrollView';
import {View} from '../Components/View/View';
import {Image} from '../Image/Image';
import {FlatListComponent, FlatListProps} from '../Lists/FlatList';
import {
DefaultSectionT,
SectionListComponent,
SectionListProps,
} from '../Lists/SectionList';
import {ColorValue} from '../StyleSheet/StyleSheet';
import {Text} from '../Text/Text';
import {NativeSyntheticEvent} from '../Types/CoreEventTypes';
export namespace Animated {
type AnimatedValue = Value;
type AnimatedValueXY = ValueXY;
class Animated {
// Internal class, no public API.
}
class AnimatedNode {
/**
* Adds an asynchronous listener to the value so you can observe updates from
* animations. This is useful because there is no way to
* synchronously read the value because it might be driven natively.
*
* See https://reactnative.dev/docs/animatedvalue.html#addlistener
*/
addListener(callback: (value: any) => any): string;
/**
* Unregister a listener. The `id` param shall match the identifier
* previously returned by `addListener()`.
*
* See https://reactnative.dev/docs/animatedvalue.html#removelistener
*/
removeListener(id: string): void;
/**
* Remove all registered listeners.
*
* See https://reactnative.dev/docs/animatedvalue.html#removealllisteners
*/
removeAllListeners(): void;
hasListeners(): boolean;
}
class AnimatedWithChildren extends AnimatedNode {
// Internal class, no public API.
}
type RgbaValue = {
readonly r: number;
readonly g: number;
readonly b: number;
readonly a: number;
};
type RgbaAnimatedValue = {
readonly r: AnimatedValue;
readonly g: AnimatedValue;
readonly b: AnimatedValue;
readonly a: AnimatedValue;
};
type AnimatedConfig = {
readonly useNativeDriver: boolean;
};
class AnimatedColor extends AnimatedWithChildren {
r: AnimatedValue;
g: AnimatedValue;
b: AnimatedValue;
a: AnimatedValue;
constructor(
valueIn?: RgbaValue | RgbaAnimatedValue | ColorValue | null,
config?: AnimatedConfig | null,
);
nativeColor: unknown; // Unsure what to do here
setValue: (value: RgbaValue | ColorValue) => void;
setOffset: (offset: RgbaValue) => void;
flattenOffset: () => void;
extractOffset: () => void;
addListener: (callback: (value: ColorValue) => unknown) => string;
removeListener: (id: string) => void;
removeAllListeners: () => void;
stopAnimation: (callback: (value: ColorValue) => unknown) => void;
resetAnimation: (callback: (value: ColorValue) => unknown) => void;
}
class AnimatedInterpolation<
OutputT extends number | string,
> extends AnimatedWithChildren {
interpolate(
config: InterpolationConfigType,
): AnimatedInterpolation<OutputT>;
}
type ExtrapolateType = 'extend' | 'identity' | 'clamp';
type InterpolationConfigType = {
inputRange: number[];
outputRange: number[] | string[];
easing?: ((input: number) => number) | undefined;
extrapolate?: ExtrapolateType | undefined;
extrapolateLeft?: ExtrapolateType | undefined;
extrapolateRight?: ExtrapolateType | undefined;
};
type ValueListenerCallback = (state: {value: number}) => void;
type Animation = {
start(
fromValue: number,
onUpdate: (value: number) => void,
onEnd: EndCallback | null,
previousAnimation: Animation | null,
animatedValue: AnimatedValue,
): void;
stop(): void;
};
/**
* Standard value for driving animations. One `Animated.Value` can drive
* multiple properties in a synchronized fashion, but can only be driven by one
* mechanism at a time. Using a new mechanism (e.g. starting a new animation,
* or calling `setValue`) will stop any previous ones.
*/
export class Value extends AnimatedWithChildren {
constructor(value: number, config?: AnimatedConfig | null);
/**
* Directly set the value. This will stop any animations running on the value
* and update all the bound properties.
*/
setValue(value: number): void;
/**
* Sets an offset that is applied on top of whatever value is set, whether via
* `setValue`, an animation, or `Animated.event`. Useful for compensating
* things like the start of a pan gesture.
*/
setOffset(offset: number): void;
/**
* Merges the offset value into the base value and resets the offset to zero.
* The final output of the value is unchanged.
*/
flattenOffset(): void;
/**
* Sets the offset value to the base value, and resets the base value to zero.
* The final output of the value is unchanged.
*/
extractOffset(): void;
/**
* Adds an asynchronous listener to the value so you can observe updates from
* animations. This is useful because there is no way to
* synchronously read the value because it might be driven natively.
*/
addListener(callback: ValueListenerCallback): string;
removeListener(id: string): void;
removeAllListeners(): void;
/**
* Stops any running animation or tracking. `callback` is invoked with the
* final value after stopping the animation, which is useful for updating
* state to match the animation position with layout.
*/
stopAnimation(callback?: (value: number) => void): void;
/**
* Stops any animation and resets the value to its original.
*
* See https://reactnative.dev/docs/animatedvalue#resetanimation
*/
resetAnimation(callback?: (value: number) => void): void;
/**
* Interpolates the value before updating the property, e.g. mapping 0-1 to
* 0-10.
*/
interpolate<OutputT extends number | string>(
config: InterpolationConfigType,
): AnimatedInterpolation<OutputT>;
/**
* Typically only used internally, but could be used by a custom Animation
* class.
*
* See https://reactnative.dev/docs/animatedvalue#animate
*/
animate(animation: Animation, callback?: EndCallback | null): void;
}
type ValueXYListenerCallback = (value: {x: number; y: number}) => void;
/**
* 2D Value for driving 2D animations, such as pan gestures. Almost identical
* API to normal `Animated.Value`, but multiplexed. Contains two regular
* `Animated.Value`s under the hood.
*/
export class ValueXY extends AnimatedWithChildren {
x: AnimatedValue;
y: AnimatedValue;
constructor(
valueIn?: {x: number | AnimatedValue; y: number | AnimatedValue},
config?: AnimatedConfig | null,
);
setValue(value: {x: number; y: number}): void;
setOffset(offset: {x: number; y: number}): void;
flattenOffset(): void;
extractOffset(): void;
resetAnimation(callback?: (value: {x: number; y: number}) => void): void;
stopAnimation(callback?: (value: {x: number; y: number}) => void): void;
addListener(callback: ValueXYListenerCallback): string;
removeListener(id: string): void;
/**
* Converts `{x, y}` into `{left, top}` for use in style, e.g.
*
*```javascript
* style={this.state.anim.getLayout()}
*```
*/
getLayout(): {[key: string]: AnimatedValue};
/**
* Converts `{x, y}` into a useable translation transform, e.g.
*
*```javascript
* style={{
* transform: this.state.anim.getTranslateTransform()
* }}
*```
*/
getTranslateTransform(): [
{translateX: AnimatedValue},
{translateY: AnimatedValue},
];
}
type EndResult = {finished: boolean};
type EndCallback = (result: EndResult) => void;
export interface CompositeAnimation {
/**
* Animations are started by calling start() on your animation.
* start() takes a completion callback that will be called when the
* animation is done or when the animation is done because stop() was
* called on it before it could finish.
*
* @param callback - Optional function that will be called
* after the animation finished running normally or when the animation
* is done because stop() was called on it before it could finish
*
* @example
* Animated.timing({}).start(({ finished }) => {
* // completion callback
* });
*/
start: (callback?: EndCallback) => void;
/**
* Stops any running animation.
*/
stop: () => void;
/**
* Stops any running animation and resets the value to its original.
*/
reset: () => void;
}
interface AnimationConfig {
isInteraction?: boolean | undefined;
useNativeDriver: boolean;
}
/**
* Animates a value from an initial velocity to zero based on a decay
* coefficient.
*/
export function decay(
value: AnimatedValue | AnimatedValueXY,
config: DecayAnimationConfig,
): CompositeAnimation;
interface DecayAnimationConfig extends AnimationConfig {
velocity: number | {x: number; y: number};
deceleration?: number | undefined;
}
/**
* Animates a value along a timed easing curve. The `Easing` module has tons
* of pre-defined curves, or you can use your own function.
*/
export const timing: (
value: AnimatedValue | AnimatedValueXY,
config: TimingAnimationConfig,
) => CompositeAnimation;
interface TimingAnimationConfig extends AnimationConfig {
toValue:
| number
| AnimatedValue
| {x: number; y: number}
| AnimatedValueXY
| AnimatedInterpolation<number>;
easing?: ((value: number) => number) | undefined;
duration?: number | undefined;
delay?: number | undefined;
}
interface SpringAnimationConfig extends AnimationConfig {
toValue:
| number
| AnimatedValue
| {x: number; y: number}
| AnimatedValueXY
| RgbaValue
| AnimatedColor
| AnimatedInterpolation<number>;
overshootClamping?: boolean | undefined;
restDisplacementThreshold?: number | undefined;
restSpeedThreshold?: number | undefined;
velocity?: number | {x: number; y: number} | undefined;
bounciness?: number | undefined;
speed?: number | undefined;
tension?: number | undefined;
friction?: number | undefined;
stiffness?: number | undefined;
mass?: number | undefined;
damping?: number | undefined;
delay?: number | undefined;
}
interface LoopAnimationConfig {
iterations?: number | undefined; // default -1 for infinite
/**
* Defaults to `true`
*/
resetBeforeIteration?: boolean | undefined;
}
/**
* Creates a new Animated value composed from two Animated values added
* together.
*/
export function add<OutputT extends number | string>(
a: Animated,
b: Animated,
): AnimatedAddition<OutputT>;
class AnimatedAddition<
OutputT extends number | string,
> extends AnimatedInterpolation<OutputT> {}
/**
* Creates a new Animated value composed by subtracting the second Animated
* value from the first Animated value.
*/
export function subtract<OutputT extends number | string>(
a: Animated,
b: Animated,
): AnimatedSubtraction<OutputT>;
class AnimatedSubtraction<
OutputT extends number | string,
> extends AnimatedInterpolation<OutputT> {}
/**
* Creates a new Animated value composed by dividing the first Animated
* value by the second Animated value.
*/
export function divide<OutputT extends number | string>(
a: Animated,
b: Animated,
): AnimatedDivision<OutputT>;
class AnimatedDivision<
OutputT extends number | string,
> extends AnimatedInterpolation<OutputT> {}
/**
* Creates a new Animated value composed from two Animated values multiplied
* together.
*/
export function multiply<OutputT extends number | string>(
a: Animated,
b: Animated,
): AnimatedMultiplication<OutputT>;
class AnimatedMultiplication<
OutputT extends number | string,
> extends AnimatedInterpolation<OutputT> {}
/**
* Creates a new Animated value that is the (non-negative) modulo of the
* provided Animated value
*/
export function modulo<OutputT extends number | string>(
a: Animated,
modulus: number,
): AnimatedModulo<OutputT>;
class AnimatedModulo<
OutputT extends number | string,
> extends AnimatedInterpolation<OutputT> {}
/**
* Create a new Animated value that is limited between 2 values. It uses the
* difference between the last value so even if the value is far from the bounds
* it will start changing when the value starts getting closer again.
* (`value = clamp(value + diff, min, max)`).
*
* This is useful with scroll events, for example, to show the navbar when
* scrolling up and to hide it when scrolling down.
*/
export function diffClamp<OutputT extends number | string>(
a: Animated,
min: number,
max: number,
): AnimatedDiffClamp<OutputT>;
class AnimatedDiffClamp<
OutputT extends number | string,
> extends AnimatedInterpolation<OutputT> {}
/**
* Starts an animation after the given delay.
*/
export function delay(time: number): CompositeAnimation;
/**
* Starts an array of animations in order, waiting for each to complete
* before starting the next. If the current running animation is stopped, no
* following animations will be started.
*/
export function sequence(
animations: Array<CompositeAnimation>,
): CompositeAnimation;
/**
* Array of animations may run in parallel (overlap), but are started in
* sequence with successive delays. Nice for doing trailing effects.
*/
export function stagger(
time: number,
animations: Array<CompositeAnimation>,
): CompositeAnimation;
/**
* Loops a given animation continuously, so that each time it reaches the end,
* it resets and begins again from the start. Can specify number of times to
* loop using the key 'iterations' in the config. Will loop without blocking
* the UI thread if the child animation is set to 'useNativeDriver'.
*/
export function loop(
animation: CompositeAnimation,
config?: LoopAnimationConfig,
): CompositeAnimation;
/**
* Spring animation based on Rebound and Origami. Tracks velocity state to
* create fluid motions as the `toValue` updates, and can be chained together.
*/
export function spring(
value: AnimatedValue | AnimatedValueXY,
config: SpringAnimationConfig,
): CompositeAnimation;
type ParallelConfig = {
stopTogether?: boolean | undefined; // If one is stopped, stop all. default: true
};
/**
* Starts an array of animations all at the same time. By default, if one
* of the animations is stopped, they will all be stopped. You can override
* this with the `stopTogether` flag.
*/
export function parallel(
animations: Array<CompositeAnimation>,
config?: ParallelConfig,
): CompositeAnimation;
type Mapping = {[key: string]: Mapping} | AnimatedValue;
interface EventConfig<T> {
listener?: ((event: NativeSyntheticEvent<T>) => void) | undefined;
useNativeDriver: boolean;
}
/**
* Takes an array of mappings and extracts values from each arg accordingly,
* then calls `setValue` on the mapped outputs. e.g.
*
*```javascript
* onScroll={Animated.event(
* [{nativeEvent: {contentOffset: {x: this._scrollX}}}]
* {listener}, // Optional async listener
* )
* ...
* onPanResponderMove: Animated.event([
* null, // raw event arg ignored
* {dx: this._panX}, // gestureState arg
* ]),
*```
*/
export function event<T>(
argMapping: Array<Mapping | null>,
config?: EventConfig<T>,
): (...args: any[]) => void;
export type ComponentProps<T> = T extends
| React.ComponentType<infer P>
| React.Component<infer P>
? P
: never;
export type LegacyRef<C> = {getNode(): C};
type Nullable = undefined | null;
type Primitive = string | number | boolean | symbol;
type Builtin = Function | Date | Error | RegExp;
interface WithAnimatedArray<P> extends Array<WithAnimatedValue<P>> {}
type WithAnimatedObject<T> = {
[K in keyof T]: WithAnimatedValue<T[K]>;
};
// prettier-ignore
export type WithAnimatedValue<T> = T extends Builtin | Nullable
? T
: T extends Primitive
? T | Value | AnimatedInterpolation<number | string> // add `Value` and `AnimatedInterpolation` but also preserve original T
: T extends Array<infer P>
? WithAnimatedArray<P>
: T extends {}
? WithAnimatedObject<T>
: T; // in case it's something we don't yet know about (for .e.g bigint)
type NonAnimatedProps = 'key' | 'ref';
// prettier-ignore
type TAugmentRef<T> = T extends React.Ref<infer R>
? unknown extends R
? never
: React.Ref<R | LegacyRef<R>>
: never;
export type AnimatedProps<T> = {
[key in keyof T]: key extends NonAnimatedProps
? key extends 'ref'
? TAugmentRef<T[key]>
: T[key]
: WithAnimatedValue<T[key]>;
};
export interface AnimatedComponent<T extends React.ComponentType<any>>
extends React.FC<AnimatedProps<React.ComponentPropsWithRef<T>>> {}
export type AnimatedComponentOptions = {
collapsable?: boolean | undefined;
};
/**
* Make any React component Animatable. Used to create `Animated.View`, etc.
*/
export function createAnimatedComponent<T extends React.ComponentType<any>>(
component: T,
options?: AnimatedComponentOptions,
): AnimatedComponent<T>;
/**
* Animated variants of the basic native views. Accepts Animated.Value for
* props and style.
*/
export const View: AnimatedComponent<typeof _View>;
export const Image: AnimatedComponent<typeof _Image>;
export const Text: AnimatedComponent<typeof _Text>;
export const ScrollView: AnimatedComponent<typeof _ScrollView>;
/**
* FlatList and SectionList infer generic Type defined under their `data` and `section` props.
*/
export class FlatList<ItemT = any> extends FlatListComponent<
ItemT,
AnimatedProps<FlatListProps<ItemT>>
> {}
export class SectionList<
ItemT = any,
SectionT = DefaultSectionT,
> extends SectionListComponent<
AnimatedProps<SectionListProps<ItemT, SectionT>>
> {}
}
// We need to alias these views so we can reference them in the Animated
// namespace where their names are shadowed.
declare const _View: typeof View;
declare const _Image: typeof Image;
declare const _Text: typeof Text;
declare const _ScrollView: typeof ScrollView;

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import typeof * as AnimatedExports from './AnimatedExports';
// The AnimatedExports module is typed as multiple exports to allow
// for an implicit namespace, but underneath is's a single default export.
const Animated: AnimatedExports = (require('./AnimatedExports') as $FlowFixMe)
.default;
export default Animated;

View File

@@ -0,0 +1,12 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
export * as default from './AnimatedExports';
export type {CompositeAnimation, Numeric} from './AnimatedImplementation';

View File

@@ -0,0 +1,258 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {NativeSyntheticEvent} from '../Types/CoreEventTypes';
import type {PlatformConfig} from './AnimatedPlatformConfig';
import type {EventMapping} from './NativeAnimatedModule';
import NativeAnimatedHelper from '../../src/private/animated/NativeAnimatedHelper';
import {findNodeHandle} from '../ReactNative/RendererProxy';
import AnimatedValue from './nodes/AnimatedValue';
import AnimatedValueXY from './nodes/AnimatedValueXY';
import invariant from 'invariant';
export type Mapping =
| {[key: string]: Mapping, ...}
| AnimatedValue
| AnimatedValueXY;
export type EventConfig<T> = {
listener?: ?(NativeSyntheticEvent<T>) => mixed,
useNativeDriver: boolean,
platformConfig?: PlatformConfig,
};
export function attachNativeEventImpl(
viewRef: any,
eventName: string,
argMapping: $ReadOnlyArray<?Mapping>,
platformConfig: ?PlatformConfig,
): {detach: () => void} {
// Find animated values in `argMapping` and create an array representing their
// key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x'].
const eventMappings: Array<EventMapping> = [];
const traverse = (value: mixed, path: Array<string>) => {
if (value instanceof AnimatedValue) {
value.__makeNative(platformConfig);
eventMappings.push({
nativeEventPath: path,
animatedValueTag: value.__getNativeTag(),
});
} else if (value instanceof AnimatedValueXY) {
traverse(value.x, path.concat('x'));
traverse(value.y, path.concat('y'));
} else if (typeof value === 'object') {
for (const key in value) {
traverse(value[key], path.concat(key));
}
}
};
invariant(
argMapping[0] && argMapping[0].nativeEvent,
'Native driven events only support animated values contained inside `nativeEvent`.',
);
// Assume that the event containing `nativeEvent` is always the first argument.
traverse(argMapping[0].nativeEvent, []);
const viewTag = findNodeHandle(viewRef);
if (viewTag != null) {
eventMappings.forEach(mapping => {
NativeAnimatedHelper.API.addAnimatedEventToView(
viewTag,
eventName,
mapping,
);
});
}
return {
detach() {
if (viewTag != null) {
eventMappings.forEach(mapping => {
NativeAnimatedHelper.API.removeAnimatedEventFromView(
viewTag,
eventName,
// $FlowFixMe[incompatible-type]
mapping.animatedValueTag,
);
});
}
},
};
}
function validateMapping(argMapping: $ReadOnlyArray<?Mapping>, args: any) {
const validate = (recMapping: ?Mapping, recEvt: any, key: string) => {
if (recMapping instanceof AnimatedValue) {
invariant(
typeof recEvt === 'number',
'Bad mapping of event key ' +
key +
', should be number but got ' +
typeof recEvt,
);
return;
}
if (recMapping instanceof AnimatedValueXY) {
invariant(
typeof recEvt.x === 'number' && typeof recEvt.y === 'number',
'Bad mapping of event key ' + key + ', should be XY but got ' + recEvt,
);
return;
}
if (typeof recEvt === 'number') {
invariant(
recMapping instanceof AnimatedValue,
'Bad mapping of type ' +
typeof recMapping +
' for key ' +
key +
', event value must map to AnimatedValue',
);
return;
}
invariant(
typeof recMapping === 'object',
'Bad mapping of type ' + typeof recMapping + ' for key ' + key,
);
invariant(
typeof recEvt === 'object',
'Bad event of type ' + typeof recEvt + ' for key ' + key,
);
for (const mappingKey in recMapping) {
validate(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
}
};
invariant(
args.length >= argMapping.length,
'Event has less arguments than mapping',
);
argMapping.forEach((mapping, idx) => {
validate(mapping, args[idx], 'arg' + idx);
});
}
export class AnimatedEvent {
_argMapping: $ReadOnlyArray<?Mapping>;
_listeners: Array<Function> = [];
_attachedEvent: ?{detach: () => void, ...};
__isNative: boolean;
__platformConfig: ?PlatformConfig;
constructor(argMapping: $ReadOnlyArray<?Mapping>, config: EventConfig<any>) {
this._argMapping = argMapping;
if (config == null) {
console.warn('Animated.event now requires a second argument for options');
config = {useNativeDriver: false};
}
if (config.listener) {
this.__addListener(config.listener);
}
this._attachedEvent = null;
this.__isNative = NativeAnimatedHelper.shouldUseNativeDriver(config);
this.__platformConfig = config.platformConfig;
}
__addListener(callback: Function): void {
this._listeners.push(callback);
}
__removeListener(callback: Function): void {
this._listeners = this._listeners.filter(listener => listener !== callback);
}
__attach(viewRef: any, eventName: string): void {
invariant(
this.__isNative,
'Only native driven events need to be attached.',
);
this._attachedEvent = attachNativeEventImpl(
viewRef,
eventName,
this._argMapping,
this.__platformConfig,
);
}
__detach(viewTag: any, eventName: string): void {
invariant(
this.__isNative,
'Only native driven events need to be detached.',
);
this._attachedEvent && this._attachedEvent.detach();
}
__getHandler(): any | ((...args: any) => void) {
if (this.__isNative) {
if (__DEV__) {
let validatedMapping = false;
return (...args: any) => {
if (!validatedMapping) {
validateMapping(this._argMapping, args);
validatedMapping = true;
}
this._callListeners(...args);
};
} else {
return this._callListeners;
}
}
let validatedMapping = false;
return (...args: any) => {
if (__DEV__ && !validatedMapping) {
validateMapping(this._argMapping, args);
validatedMapping = true;
}
const traverse = (
recMapping: ?(Mapping | AnimatedValue),
recEvt: any,
) => {
if (recMapping instanceof AnimatedValue) {
if (typeof recEvt === 'number') {
recMapping.setValue(recEvt);
}
} else if (recMapping instanceof AnimatedValueXY) {
if (typeof recEvt === 'object') {
traverse(recMapping.x, recEvt.x);
traverse(recMapping.y, recEvt.y);
}
} else if (typeof recMapping === 'object') {
for (const mappingKey in recMapping) {
/* $FlowFixMe[prop-missing] (>=0.120.0) This comment suppresses an
* error found when Flow v0.120 was deployed. To see the error,
* delete this comment and run Flow. */
traverse(recMapping[mappingKey], recEvt[mappingKey]);
}
}
};
this._argMapping.forEach((mapping, idx) => {
traverse(mapping, args[idx]);
});
this._callListeners(...args);
};
}
_callListeners = (...args: any) => {
this._listeners.forEach(listener => listener(...args));
};
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
import typeof AnimatedFlatList from './components/AnimatedFlatList';
import typeof AnimatedImage from './components/AnimatedImage';
import typeof AnimatedScrollView from './components/AnimatedScrollView';
import typeof AnimatedSectionList from './components/AnimatedSectionList';
import typeof AnimatedText from './components/AnimatedText';
import typeof AnimatedView from './components/AnimatedView';
import Platform from '../Utilities/Platform';
import AnimatedImplementation from './AnimatedImplementation';
import AnimatedMock from './AnimatedMock';
const Animated: typeof AnimatedImplementation = Platform.isDisableAnimations
? AnimatedMock
: AnimatedImplementation;
export default {
get FlatList(): AnimatedFlatList<any> {
return require('./components/AnimatedFlatList').default;
},
get Image(): AnimatedImage {
return require('./components/AnimatedImage').default;
},
get ScrollView(): AnimatedScrollView {
return require('./components/AnimatedScrollView').default;
},
get SectionList(): AnimatedSectionList<any, any> {
return require('./components/AnimatedSectionList').default;
},
get Text(): AnimatedText {
return require('./components/AnimatedText').default;
},
get View(): AnimatedView {
return require('./components/AnimatedView').default;
},
...Animated,
};

View File

@@ -0,0 +1,193 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
import AnimatedImplementation from './AnimatedImplementation';
export type {CompositeAnimation} from './AnimatedImplementation';
export type {DecayAnimationConfig} from './animations/DecayAnimation';
export type {SpringAnimationConfig} from './animations/SpringAnimation';
export type {TimingAnimationConfig} from './animations/TimingAnimation';
export {default as FlatList} from './components/AnimatedFlatList';
export {default as Image} from './components/AnimatedImage';
export {default as ScrollView} from './components/AnimatedScrollView';
export {default as SectionList} from './components/AnimatedSectionList';
export {default as Text} from './components/AnimatedText';
export {default as View} from './components/AnimatedView';
export {default as Color} from './nodes/AnimatedColor';
export {AnimatedEvent as Event} from './AnimatedEvent';
export {default as Interpolation} from './nodes/AnimatedInterpolation';
export {default as Node} from './nodes/AnimatedNode';
export {default as Value} from './nodes/AnimatedValue';
export {default as ValueXY} from './nodes/AnimatedValueXY';
/** @deprecated Use Animated.Interpolation instead */
export type {default as AnimatedInterpolation} from './nodes/AnimatedInterpolation';
/** @deprecated Use Animated.Color instead */
export type {default as AnimatedColor} from './nodes/AnimatedColor';
export type {AnimatedValueConfig as AnimatedConfig} from './nodes/AnimatedValue';
export type {default as AnimatedNode} from './nodes/AnimatedNode';
export type {default as AnimatedAddition} from './nodes/AnimatedAddition';
export type {default as AnimatedDiffClamp} from './nodes/AnimatedDiffClamp';
export type {default as AnimatedDivision} from './nodes/AnimatedDivision';
export type {InterpolationConfigType as InterpolationConfig} from './nodes/AnimatedInterpolation';
export type {default as AnimatedModulo} from './nodes/AnimatedModulo';
export type {default as AnimatedMultiplication} from './nodes/AnimatedMultiplication';
export type {default as AnimatedSubtraction} from './nodes/AnimatedSubtraction';
export type {WithAnimatedValue, AnimatedProps} from './createAnimatedComponent';
export type {AnimatedComponentType as AnimatedComponent} from './createAnimatedComponent';
/**
* Creates a new Animated value composed from two Animated values added
* together.
*
* See https://reactnative.dev/docs/animated#add
*/
export const add = AnimatedImplementation.add;
/**
* Imperative API to attach an animated value to an event on a view. Prefer
* using `Animated.event` with `useNativeDrive: true` if possible.
*
* See https://reactnative.dev/docs/animated#attachnativeevent
*/
export const attachNativeEvent = AnimatedImplementation.attachNativeEvent;
/**
* Make any React component Animatable. Used to create `Animated.View`, etc.
*
* See https://reactnative.dev/docs/animated#createanimatedcomponent
*/
export const createAnimatedComponent =
AnimatedImplementation.createAnimatedComponent;
/**
* Animates a value from an initial velocity to zero based on a decay
* coefficient.
*
* See https://reactnative.dev/docs/animated#decay
*/
export const decay = AnimatedImplementation.decay;
/**
* Starts an animation after the given delay.
*
* See https://reactnative.dev/docs/animated#delay
*/
export const delay = AnimatedImplementation.delay;
/**
* Create a new Animated value that is limited between 2 values. It uses the
* difference between the last value so even if the value is far from the
* bounds it will start changing when the value starts getting closer again.
*
* See https://reactnative.dev/docs/animated#diffclamp
*/
export const diffClamp = AnimatedImplementation.diffClamp;
/**
* Creates a new Animated value composed by dividing the first Animated value
* by the second Animated value.
*
* See https://reactnative.dev/docs/animated#divide
*/
export const divide = AnimatedImplementation.divide;
/**
* Takes an array of mappings and extracts values from each arg accordingly,
* then calls `setValue` on the mapped outputs.
*
* See https://reactnative.dev/docs/animated#event
*/
export const event = AnimatedImplementation.event;
/**
* Advanced imperative API for snooping on animated events that are passed in
* through props. Use values directly where possible.
*
* See https://reactnative.dev/docs/animated#forkevent
*/
export const forkEvent = AnimatedImplementation.forkEvent;
/**
* Loops a given animation continuously, so that each time it reaches the
* end, it resets and begins again from the start.
*
* See https://reactnative.dev/docs/animated#loop
*/
export const loop = AnimatedImplementation.loop;
/**
* Creates a new Animated value that is the (non-negative) modulo of the
* provided Animated value.
*
* See https://reactnative.dev/docs/animated#modulo
*/
export const modulo = AnimatedImplementation.modulo;
/**
* Creates a new Animated value composed from two Animated values multiplied
* together.
*
* See https://reactnative.dev/docs/animated#multiply
*/
export const multiply = AnimatedImplementation.multiply;
/**
* Starts an array of animations all at the same time. By default, if one
* of the animations is stopped, they will all be stopped. You can override
* this with the `stopTogether` flag.
*
* See https://reactnative.dev/docs/animated#parallel
*/
export const parallel = AnimatedImplementation.parallel;
/**
* Starts an array of animations in order, waiting for each to complete
* before starting the next. If the current running animation is stopped, no
* following animations will be started.
*
* See https://reactnative.dev/docs/animated#sequence
*/
export const sequence = AnimatedImplementation.sequence;
/**
* Animates a value according to an analytical spring model based on
* damped harmonic oscillation.
*
* See https://reactnative.dev/docs/animated#spring
*/
export const spring = AnimatedImplementation.spring;
/**
* Array of animations may run in parallel (overlap), but are started in
* sequence with successive delays. Nice for doing trailing effects.
*
* See https://reactnative.dev/docs/animated#stagger
*/
export const stagger = AnimatedImplementation.stagger;
/**
* Creates a new Animated value composed by subtracting the second Animated
* value from the first Animated value.
*
* See https://reactnative.dev/docs/animated#subtract
*/
export const subtract = AnimatedImplementation.subtract;
/**
* Animates a value along a timed easing curve. The Easing module has tons of
* predefined curves, or you can use your own function.
*
* See https://reactnative.dev/docs/animated#timing
*/
export const timing = AnimatedImplementation.timing;
export const unforkEvent = AnimatedImplementation.unforkEvent;

View File

@@ -0,0 +1,635 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {EventConfig, Mapping} from './AnimatedEvent';
import type {
AnimationConfig,
EndCallback,
EndResult,
} from './animations/Animation';
import type {DecayAnimationConfig} from './animations/DecayAnimation';
import type {SpringAnimationConfig} from './animations/SpringAnimation';
import type {TimingAnimationConfig} from './animations/TimingAnimation';
import {AnimatedEvent, attachNativeEventImpl} from './AnimatedEvent';
import DecayAnimation from './animations/DecayAnimation';
import SpringAnimation from './animations/SpringAnimation';
import TimingAnimation from './animations/TimingAnimation';
import createAnimatedComponent from './createAnimatedComponent';
import AnimatedAddition from './nodes/AnimatedAddition';
import AnimatedColor from './nodes/AnimatedColor';
import AnimatedDiffClamp from './nodes/AnimatedDiffClamp';
import AnimatedDivision from './nodes/AnimatedDivision';
import AnimatedInterpolation from './nodes/AnimatedInterpolation';
import AnimatedModulo from './nodes/AnimatedModulo';
import AnimatedMultiplication from './nodes/AnimatedMultiplication';
import AnimatedNode from './nodes/AnimatedNode';
import AnimatedSubtraction from './nodes/AnimatedSubtraction';
import AnimatedTracking from './nodes/AnimatedTracking';
import AnimatedValue from './nodes/AnimatedValue';
import AnimatedValueXY from './nodes/AnimatedValueXY';
export type CompositeAnimation = {
start: (callback?: ?EndCallback, isLooping?: boolean) => void,
stop: () => void,
reset: () => void,
_startNativeLoop: (iterations?: number) => void,
_isUsingNativeDriver: () => boolean,
...
};
const addImpl = function (
a: AnimatedNode | number,
b: AnimatedNode | number,
): AnimatedAddition {
return new AnimatedAddition(a, b);
};
const subtractImpl = function (
a: AnimatedNode | number,
b: AnimatedNode | number,
): AnimatedSubtraction {
return new AnimatedSubtraction(a, b);
};
const divideImpl = function (
a: AnimatedNode | number,
b: AnimatedNode | number,
): AnimatedDivision {
return new AnimatedDivision(a, b);
};
const multiplyImpl = function (
a: AnimatedNode | number,
b: AnimatedNode | number,
): AnimatedMultiplication {
return new AnimatedMultiplication(a, b);
};
const moduloImpl = function (a: AnimatedNode, modulus: number): AnimatedModulo {
return new AnimatedModulo(a, modulus);
};
const diffClampImpl = function (
a: AnimatedNode,
min: number,
max: number,
): AnimatedDiffClamp {
return new AnimatedDiffClamp(a, min, max);
};
const _combineCallbacks = function (
callback: ?EndCallback,
config: $ReadOnly<{...AnimationConfig, ...}>,
) {
if (callback && config.onComplete) {
return (...args: Array<EndResult>) => {
config.onComplete && config.onComplete(...args);
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
callback && callback(...args);
};
} else {
return callback || config.onComplete;
}
};
const maybeVectorAnim = function (
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: Object,
anim: (value: AnimatedValue, config: Object) => CompositeAnimation,
): ?CompositeAnimation {
if (value instanceof AnimatedValueXY) {
const configX = {...config};
const configY = {...config};
for (const key in config) {
const {x, y} = config[key];
if (x !== undefined && y !== undefined) {
configX[key] = x;
configY[key] = y;
}
}
const aX = anim((value: AnimatedValueXY).x, configX);
const aY = anim((value: AnimatedValueXY).y, configY);
// We use `stopTogether: false` here because otherwise tracking will break
// because the second animation will get stopped before it can update.
return parallelImpl([aX, aY], {stopTogether: false});
} else if (value instanceof AnimatedColor) {
const configR = {...config};
const configG = {...config};
const configB = {...config};
const configA = {...config};
for (const key in config) {
const {r, g, b, a} = config[key];
if (
r !== undefined &&
g !== undefined &&
b !== undefined &&
a !== undefined
) {
configR[key] = r;
configG[key] = g;
configB[key] = b;
configA[key] = a;
}
}
const aR = anim((value: AnimatedColor).r, configR);
const aG = anim((value: AnimatedColor).g, configG);
const aB = anim((value: AnimatedColor).b, configB);
const aA = anim((value: AnimatedColor).a, configA);
// We use `stopTogether: false` here because otherwise tracking will break
// because the second animation will get stopped before it can update.
return parallelImpl([aR, aG, aB, aA], {stopTogether: false});
}
return null;
};
const springImpl = function (
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: SpringAnimationConfig,
): CompositeAnimation {
const start = function (
animatedValue: AnimatedValue | AnimatedValueXY | AnimatedColor,
configuration: SpringAnimationConfig,
callback?: ?EndCallback,
): void {
callback = _combineCallbacks(callback, configuration);
const singleValue: any = animatedValue;
const singleConfig: any = configuration;
singleValue.stopTracking();
if (configuration.toValue instanceof AnimatedNode) {
singleValue.track(
new AnimatedTracking(
singleValue,
configuration.toValue,
SpringAnimation,
singleConfig,
callback,
),
);
} else {
singleValue.animate(new SpringAnimation(singleConfig), callback);
}
};
return (
maybeVectorAnim(value, config, springImpl) || {
start: function (callback?: ?EndCallback): void {
start(value, config, callback);
},
stop: function (): void {
value.stopAnimation();
},
reset: function (): void {
value.resetAnimation();
},
_startNativeLoop: function (iterations?: number): void {
const singleConfig = {...config, iterations};
start(value, singleConfig);
},
_isUsingNativeDriver: function (): boolean {
return config.useNativeDriver || false;
},
}
);
};
const timingImpl = function (
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: TimingAnimationConfig,
): CompositeAnimation {
const start = function (
animatedValue: AnimatedValue | AnimatedValueXY | AnimatedColor,
configuration: TimingAnimationConfig,
callback?: ?EndCallback,
): void {
callback = _combineCallbacks(callback, configuration);
const singleValue: any = animatedValue;
const singleConfig: any = configuration;
singleValue.stopTracking();
if (configuration.toValue instanceof AnimatedNode) {
singleValue.track(
new AnimatedTracking(
singleValue,
configuration.toValue,
TimingAnimation,
singleConfig,
callback,
),
);
} else {
singleValue.animate(new TimingAnimation(singleConfig), callback);
}
};
return (
maybeVectorAnim(value, config, timingImpl) || {
start: function (callback?: ?EndCallback, isLooping?: boolean): void {
start(value, {...config, isLooping}, callback);
},
stop: function (): void {
value.stopAnimation();
},
reset: function (): void {
value.resetAnimation();
},
_startNativeLoop: function (iterations?: number): void {
const singleConfig = {...config, iterations};
start(value, singleConfig);
},
_isUsingNativeDriver: function (): boolean {
return config.useNativeDriver || false;
},
}
);
};
const decayImpl = function (
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: DecayAnimationConfig,
): CompositeAnimation {
const start = function (
animatedValue: AnimatedValue | AnimatedValueXY | AnimatedColor,
configuration: DecayAnimationConfig,
callback?: ?EndCallback,
): void {
callback = _combineCallbacks(callback, configuration);
const singleValue: any = animatedValue;
const singleConfig: any = configuration;
singleValue.stopTracking();
singleValue.animate(new DecayAnimation(singleConfig), callback);
};
return (
maybeVectorAnim(value, config, decayImpl) || {
start: function (callback?: ?EndCallback): void {
start(value, config, callback);
},
stop: function (): void {
value.stopAnimation();
},
reset: function (): void {
value.resetAnimation();
},
_startNativeLoop: function (iterations?: number): void {
const singleConfig = {...config, iterations};
start(value, singleConfig);
},
_isUsingNativeDriver: function (): boolean {
return config.useNativeDriver || false;
},
}
);
};
const sequenceImpl = function (
animations: Array<CompositeAnimation>,
): CompositeAnimation {
let current = 0;
return {
start: function (callback?: ?EndCallback, isLooping?: boolean) {
const onComplete = function (result: EndResult) {
if (!result.finished) {
callback && callback(result);
return;
}
current++;
if (current === animations.length) {
// if the start is called, even without a reset, it should start from the beginning
current = 0;
callback && callback(result);
return;
}
animations[current].start(onComplete, isLooping);
};
if (animations.length === 0) {
callback && callback({finished: true});
} else {
animations[current].start(onComplete, isLooping);
}
},
stop: function () {
if (current < animations.length) {
animations[current].stop();
}
},
reset: function () {
animations.forEach((animation, idx) => {
if (idx <= current) {
animation.reset();
}
});
current = 0;
},
_startNativeLoop: function () {
throw new Error(
'Loops run using the native driver cannot contain Animated.sequence animations',
);
},
_isUsingNativeDriver: function (): boolean {
return false;
},
};
};
type ParallelConfig = {
// If one is stopped, stop all. default: true
stopTogether?: boolean,
...
};
const parallelImpl = function (
animations: Array<CompositeAnimation>,
config?: ?ParallelConfig,
): CompositeAnimation {
let doneCount = 0;
// Make sure we only call stop() at most once for each animation
const hasEnded: {[number]: boolean} = {};
const stopTogether = !(config && config.stopTogether === false);
const result: CompositeAnimation = {
start: function (callback?: ?EndCallback, isLooping?: boolean) {
if (doneCount === animations.length) {
callback && callback({finished: true});
return;
}
animations.forEach((animation, idx) => {
const cb = function (endResult: EndResult) {
hasEnded[idx] = true;
doneCount++;
if (doneCount === animations.length) {
doneCount = 0;
callback && callback(endResult);
return;
}
if (!endResult.finished && stopTogether) {
result.stop();
}
};
if (!animation) {
cb({finished: true});
} else {
animation.start(cb, isLooping);
}
});
},
stop: function (): void {
animations.forEach((animation, idx) => {
!hasEnded[idx] && animation.stop();
hasEnded[idx] = true;
});
},
reset: function (): void {
animations.forEach((animation, idx) => {
animation.reset();
hasEnded[idx] = false;
doneCount = 0;
});
},
_startNativeLoop: function (): empty {
throw new Error(
'Loops run using the native driver cannot contain Animated.parallel animations',
);
},
_isUsingNativeDriver: function (): boolean {
return false;
},
};
return result;
};
const delayImpl = function (time: number): CompositeAnimation {
// Would be nice to make a specialized implementation
return timingImpl(new AnimatedValue(0), {
toValue: 0,
delay: time,
duration: 0,
useNativeDriver: false,
});
};
const staggerImpl = function (
time: number,
animations: Array<CompositeAnimation>,
): CompositeAnimation {
return parallelImpl(
animations.map((animation, i) => {
return sequenceImpl([delayImpl(time * i), animation]);
}),
);
};
type LoopAnimationConfig = {
iterations: number,
resetBeforeIteration?: boolean,
...
};
const loopImpl = function (
animation: CompositeAnimation,
// $FlowFixMe[incompatible-type]
{iterations = -1, resetBeforeIteration = true}: LoopAnimationConfig = {},
): CompositeAnimation {
let isFinished = false;
let iterationsSoFar = 0;
return {
start: function (callback?: ?EndCallback) {
const restart = function (result: EndResult = {finished: true}): void {
if (
isFinished ||
iterationsSoFar === iterations ||
result.finished === false
) {
callback && callback(result);
} else {
iterationsSoFar++;
resetBeforeIteration && animation.reset();
animation.start(restart, iterations === -1);
}
};
if (!animation || iterations === 0) {
callback && callback({finished: true});
} else {
if (animation._isUsingNativeDriver()) {
animation._startNativeLoop(iterations);
} else {
restart(); // Start looping recursively on the js thread
}
}
},
stop: function (): void {
isFinished = true;
animation.stop();
},
reset: function (): void {
iterationsSoFar = 0;
isFinished = false;
animation.reset();
},
_startNativeLoop: function () {
throw new Error(
'Loops run using the native driver cannot contain Animated.loop animations',
);
},
_isUsingNativeDriver: function (): boolean {
return animation._isUsingNativeDriver();
},
};
};
function forkEventImpl(
event: ?AnimatedEvent | ?Function,
listener: Function,
): AnimatedEvent | Function {
if (!event) {
return listener;
} else if (event instanceof AnimatedEvent) {
event.__addListener(listener);
return event;
} else {
return (...args) => {
typeof event === 'function' && event(...args);
listener(...args);
};
}
}
function unforkEventImpl(
event: ?AnimatedEvent | ?Function,
listener: Function,
): void {
if (event && event instanceof AnimatedEvent) {
event.__removeListener(listener);
}
}
const eventImpl = function <T>(
argMapping: $ReadOnlyArray<?Mapping>,
config: EventConfig<T>,
): any {
const animatedEvent = new AnimatedEvent(argMapping, config);
if (animatedEvent.__isNative) {
return animatedEvent;
} else {
return animatedEvent.__getHandler();
}
};
// All types of animated nodes that represent scalar numbers and can be interpolated (etc)
type AnimatedNumeric =
| AnimatedAddition
| AnimatedDiffClamp
| AnimatedDivision
| AnimatedInterpolation<number>
| AnimatedModulo
| AnimatedMultiplication
| AnimatedSubtraction
| AnimatedValue;
export type {AnimatedNumeric as Numeric};
/**
* The `Animated` library is designed to make animations fluid, powerful, and
* easy to build and maintain. `Animated` focuses on declarative relationships
* between inputs and outputs, with configurable transforms in between, and
* simple `start`/`stop` methods to control time-based animation execution.
* If additional transforms are added, be sure to include them in
* AnimatedMock.js as well.
*
* See https://reactnative.dev/docs/animated
*/
export default {
/**
* Standard value class for driving animations. Typically initialized with
* `new Animated.Value(0);`
*
* See https://reactnative.dev/docs/animated#value
*/
Value: AnimatedValue,
/**
* 2D value class for driving 2D animations, such as pan gestures.
*
* See https://reactnative.dev/docs/animatedvaluexy
*/
ValueXY: AnimatedValueXY,
/**
* Value class for driving color animations.
*/
Color: AnimatedColor,
/**
* Exported to use the Interpolation type in flow.
*
* See https://reactnative.dev/docs/animated#interpolation
*/
Interpolation: AnimatedInterpolation,
/**
* Exported for ease of type checking. All animated values derive from this
* class.
*
* See https://reactnative.dev/docs/animated#node
*/
Node: AnimatedNode,
decay: decayImpl,
timing: timingImpl,
spring: springImpl,
add: addImpl,
subtract: subtractImpl,
divide: divideImpl,
multiply: multiplyImpl,
modulo: moduloImpl,
diffClamp: diffClampImpl,
delay: delayImpl,
sequence: sequenceImpl,
parallel: parallelImpl,
stagger: staggerImpl,
loop: loopImpl,
event: eventImpl,
createAnimatedComponent,
attachNativeEvent: attachNativeEventImpl,
forkEvent: forkEventImpl,
unforkEvent: unforkEventImpl,
/**
* Expose Event class, so it can be used as a type for type checkers.
*/
Event: AnimatedEvent,
};

View File

@@ -0,0 +1,195 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {Numeric as AnimatedNumeric} from './AnimatedImplementation';
import type {EndResult} from './animations/Animation';
import type {EndCallback} from './animations/Animation';
import type {DecayAnimationConfig} from './animations/DecayAnimation';
import type {SpringAnimationConfig} from './animations/SpringAnimation';
import type {TimingAnimationConfig} from './animations/TimingAnimation';
import {AnimatedEvent, attachNativeEventImpl} from './AnimatedEvent';
import AnimatedImplementation from './AnimatedImplementation';
import createAnimatedComponent from './createAnimatedComponent';
import AnimatedColor from './nodes/AnimatedColor';
import AnimatedInterpolation from './nodes/AnimatedInterpolation';
import AnimatedNode from './nodes/AnimatedNode';
import AnimatedValue from './nodes/AnimatedValue';
import AnimatedValueXY from './nodes/AnimatedValueXY';
/**
* Animations are a source of flakiness in snapshot testing. This mock replaces
* animation functions from AnimatedImplementation with empty animations for
* predictability in tests. When possible the animation will run immediately
* to the final state.
*/
// Prevent any callback invocation from recursively triggering another
// callback, which may trigger another animation
let inAnimationCallback = false;
function mockAnimationStart(
start: (callback?: ?EndCallback) => void,
): (callback?: ?EndCallback) => void {
return callback => {
const guardedCallback =
callback == null
? callback
: (...args: Array<EndResult>) => {
if (inAnimationCallback) {
console.warn(
'Ignoring recursive animation callback when running mock animations',
);
return;
}
inAnimationCallback = true;
try {
callback(...args);
} finally {
inAnimationCallback = false;
}
};
start(guardedCallback);
};
}
export type CompositeAnimation = {
start: (callback?: ?EndCallback) => void,
stop: () => void,
reset: () => void,
_startNativeLoop: (iterations?: number) => void,
_isUsingNativeDriver: () => boolean,
...
};
const emptyAnimation = {
start: () => {},
stop: () => {},
reset: () => {},
_startNativeLoop: () => {},
_isUsingNativeDriver: () => {
return false;
},
};
const mockCompositeAnimation = (
animations: Array<CompositeAnimation>,
): CompositeAnimation => ({
...emptyAnimation,
start: mockAnimationStart((callback?: ?EndCallback): void => {
animations.forEach(animation => animation.start());
callback?.({finished: true});
}),
});
const spring = function (
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: SpringAnimationConfig,
): CompositeAnimation {
const anyValue: any = value;
return {
...emptyAnimation,
start: mockAnimationStart((callback?: ?EndCallback): void => {
anyValue.setValue(config.toValue);
callback?.({finished: true});
}),
};
};
const timing = function (
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: TimingAnimationConfig,
): CompositeAnimation {
const anyValue: any = value;
return {
...emptyAnimation,
start: mockAnimationStart((callback?: ?EndCallback): void => {
anyValue.setValue(config.toValue);
callback?.({finished: true});
}),
};
};
const decay = function (
value: AnimatedValue | AnimatedValueXY | AnimatedColor,
config: DecayAnimationConfig,
): CompositeAnimation {
return emptyAnimation;
};
const sequence = function (
animations: Array<CompositeAnimation>,
): CompositeAnimation {
return mockCompositeAnimation(animations);
};
type ParallelConfig = {stopTogether?: boolean, ...};
const parallel = function (
animations: Array<CompositeAnimation>,
config?: ?ParallelConfig,
): CompositeAnimation {
return mockCompositeAnimation(animations);
};
const delay = function (time: number): CompositeAnimation {
return emptyAnimation;
};
const stagger = function (
time: number,
animations: Array<CompositeAnimation>,
): CompositeAnimation {
return mockCompositeAnimation(animations);
};
type LoopAnimationConfig = {
iterations: number,
resetBeforeIteration?: boolean,
...
};
const loop = function (
animation: CompositeAnimation,
// $FlowFixMe[incompatible-type]
{iterations = -1}: LoopAnimationConfig = {},
): CompositeAnimation {
return emptyAnimation;
};
export type {AnimatedNumeric as Numeric};
export default {
Value: AnimatedValue,
ValueXY: AnimatedValueXY,
Color: AnimatedColor,
Interpolation: AnimatedInterpolation,
Node: AnimatedNode,
decay,
timing,
spring,
add: AnimatedImplementation.add,
subtract: AnimatedImplementation.subtract,
divide: AnimatedImplementation.divide,
multiply: AnimatedImplementation.multiply,
modulo: AnimatedImplementation.modulo,
diffClamp: AnimatedImplementation.diffClamp,
delay,
sequence,
parallel,
stagger,
loop,
event: AnimatedImplementation.event,
createAnimatedComponent,
attachNativeEvent: attachNativeEventImpl,
forkEvent: AnimatedImplementation.forkEvent,
unforkEvent: AnimatedImplementation.unforkEvent,
Event: AnimatedEvent,
} as typeof AnimatedImplementation;

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
export type PlatformConfig = {};

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
/**
* This class implements common easing functions. The math is pretty obscure,
* but this cool website has nice visual illustrations of what they represent:
* http://xaedes.de/dev/transitions/
*/
export type EasingFunction = (value: number) => number;
export interface EasingStatic {
step0: EasingFunction;
step1: EasingFunction;
linear: EasingFunction;
ease: EasingFunction;
quad: EasingFunction;
cubic: EasingFunction;
poly(n: number): EasingFunction;
sin: EasingFunction;
circle: EasingFunction;
exp: EasingFunction;
elastic(bounciness: number): EasingFunction;
back(s: number): EasingFunction;
bounce: EasingFunction;
bezier(x1: number, y1: number, x2: number, y2: number): EasingFunction;
in(easing: EasingFunction): EasingFunction;
out(easing: EasingFunction): EasingFunction;
inOut(easing: EasingFunction): EasingFunction;
}
export type Easing = EasingStatic;
export const Easing: EasingStatic;

250
node_modules/react-native/Libraries/Animated/Easing.js generated vendored Normal file
View File

@@ -0,0 +1,250 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
let ease;
export type EasingFunction = (t: number) => number;
/**
* The `Easing` module implements common easing functions. This module is used
* by [Animate.timing()](docs/animate.html#timing) to convey physically
* believable motion in animations.
*
* You can find a visualization of some common easing functions at
* http://easings.net/
*
* ### Predefined animations
*
* The `Easing` module provides several predefined animations through the
* following methods:
*
* - [`back`](docs/easing.html#back) provides a simple animation where the
* object goes slightly back before moving forward
* - [`bounce`](docs/easing.html#bounce) provides a bouncing animation
* - [`ease`](docs/easing.html#ease) provides a simple inertial animation
* - [`elastic`](docs/easing.html#elastic) provides a simple spring interaction
*
* ### Standard functions
*
* Three standard easing functions are provided:
*
* - [`linear`](docs/easing.html#linear)
* - [`quad`](docs/easing.html#quad)
* - [`cubic`](docs/easing.html#cubic)
*
* The [`poly`](docs/easing.html#poly) function can be used to implement
* quartic, quintic, and other higher power functions.
*
* ### Additional functions
*
* Additional mathematical functions are provided by the following methods:
*
* - [`bezier`](docs/easing.html#bezier) provides a cubic bezier curve
* - [`circle`](docs/easing.html#circle) provides a circular function
* - [`sin`](docs/easing.html#sin) provides a sinusoidal function
* - [`exp`](docs/easing.html#exp) provides an exponential function
*
* The following helpers are used to modify other easing functions.
*
* - [`in`](docs/easing.html#in) runs an easing function forwards
* - [`inOut`](docs/easing.html#inout) makes any easing function symmetrical
* - [`out`](docs/easing.html#out) runs an easing function backwards
*/
const EasingStatic = {
/**
* A stepping function, returns 1 for any positive value of `n`.
*/
step0(n: number): number {
return n > 0 ? 1 : 0;
},
/**
* A stepping function, returns 1 if `n` is greater than or equal to 1.
*/
step1(n: number): number {
return n >= 1 ? 1 : 0;
},
/**
* A linear function, `f(t) = t`. Position correlates to elapsed time one to
* one.
*
* http://cubic-bezier.com/#0,0,1,1
*/
linear(t: number): number {
return t;
},
/**
* A simple inertial interaction, similar to an object slowly accelerating to
* speed.
*
* http://cubic-bezier.com/#.42,0,1,1
*/
ease(t: number): number {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
if (!ease) {
ease = EasingStatic.bezier(0.42, 0, 1, 1);
}
return ease(t);
},
/**
* A quadratic function, `f(t) = t * t`. Position equals the square of elapsed
* time.
*
* http://easings.net/#easeInQuad
*/
quad(t: number): number {
return t * t;
},
/**
* A cubic function, `f(t) = t * t * t`. Position equals the cube of elapsed
* time.
*
* http://easings.net/#easeInCubic
*/
cubic(t: number): number {
return t * t * t;
},
/**
* A power function. Position is equal to the Nth power of elapsed time.
*
* n = 4: http://easings.net/#easeInQuart
* n = 5: http://easings.net/#easeInQuint
*/
poly(n: number): EasingFunction {
return (t: number) => Math.pow(t, n);
},
/**
* A sinusoidal function.
*
* http://easings.net/#easeInSine
*/
sin(t: number): number {
return 1 - Math.cos((t * Math.PI) / 2);
},
/**
* A circular function.
*
* http://easings.net/#easeInCirc
*/
circle(t: number): number {
return 1 - Math.sqrt(1 - t * t);
},
/**
* An exponential function.
*
* http://easings.net/#easeInExpo
*/
exp(t: number): number {
return Math.pow(2, 10 * (t - 1));
},
/**
* A simple elastic interaction, similar to a spring oscillating back and
* forth.
*
* Default bounciness is 1, which overshoots a little bit once. 0 bounciness
* doesn't overshoot at all, and bounciness of N > 1 will overshoot about N
* times.
*
* http://easings.net/#easeInElastic
*/
elastic(bounciness: number = 1): EasingFunction {
const p = bounciness * Math.PI;
return t => 1 - Math.pow(Math.cos((t * Math.PI) / 2), 3) * Math.cos(t * p);
},
/**
* Use with `Animated.parallel()` to create a simple effect where the object
* animates back slightly as the animation starts.
*
* https://easings.net/#easeInBack
*/
back(s: number = 1.70158): EasingFunction {
return t => t * t * ((s + 1) * t - s);
},
/**
* Provides a simple bouncing effect.
*
* http://easings.net/#easeInBounce
*/
bounce(t: number): number {
if (t < 1 / 2.75) {
return 7.5625 * t * t;
}
if (t < 2 / 2.75) {
const t2 = t - 1.5 / 2.75;
return 7.5625 * t2 * t2 + 0.75;
}
if (t < 2.5 / 2.75) {
const t2 = t - 2.25 / 2.75;
return 7.5625 * t2 * t2 + 0.9375;
}
const t2 = t - 2.625 / 2.75;
return 7.5625 * t2 * t2 + 0.984375;
},
/**
* Provides a cubic bezier curve, equivalent to CSS Transitions'
* `transition-timing-function`.
*
* A useful tool to visualize cubic bezier curves can be found at
* http://cubic-bezier.com/
*/
bezier(x1: number, y1: number, x2: number, y2: number): EasingFunction {
const _bezier = require('./bezier').default;
return _bezier(x1, y1, x2, y2);
},
/**
* Runs an easing function forwards.
*/
in(easing: EasingFunction): EasingFunction {
return easing;
},
/**
* Runs an easing function backwards.
*/
out(easing: EasingFunction): EasingFunction {
return t => 1 - easing(1 - t);
},
/**
* Makes any easing function symmetrical. The easing function will run
* forwards for half of the duration, then backwards for the rest of the
* duration.
*/
inOut(easing: EasingFunction): EasingFunction {
return t => {
if (t < 0.5) {
return easing(t * 2) / 2;
}
return 1 - easing((1 - t) * 2) / 2;
};
},
};
export type Easing = typeof EasingStatic;
export default EasingStatic;

View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {AnimatedPropsAllowlist} from './nodes/AnimatedProps';
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
/**
* Styles allowed by the native animated implementation.
*
* In general native animated implementation should support any numeric or color property that
* doesn't need to be updated through the shadow view hierarchy (all non-layout properties).
*/
const SUPPORTED_COLOR_STYLES: {[string]: true} = {
backgroundColor: true,
borderBottomColor: true,
borderColor: true,
borderEndColor: true,
borderLeftColor: true,
borderRightColor: true,
borderStartColor: true,
borderTopColor: true,
color: true,
tintColor: true,
};
const SUPPORTED_STYLES: {[string]: true} = {
...SUPPORTED_COLOR_STYLES,
borderBottomEndRadius: true,
borderBottomLeftRadius: true,
borderBottomRightRadius: true,
borderBottomStartRadius: true,
borderEndEndRadius: true,
borderEndStartRadius: true,
borderRadius: true,
borderTopEndRadius: true,
borderTopLeftRadius: true,
borderTopRightRadius: true,
borderTopStartRadius: true,
borderStartEndRadius: true,
borderStartStartRadius: true,
elevation: true,
opacity: true,
filter: true,
transform: true,
zIndex: true,
/* ios styles */
shadowOpacity: true,
shadowRadius: true,
/* legacy android transform properties */
scaleX: true,
scaleY: true,
translateX: true,
translateY: true,
};
const SUPPORTED_TRANSFORMS: {[string]: true} = {
translateX: true,
translateY: true,
scale: true,
scaleX: true,
scaleY: true,
rotate: true,
rotateX: true,
rotateY: true,
rotateZ: true,
perspective: true,
skewX: true,
skewY: true,
...(ReactNativeFeatureFlags.shouldUseAnimatedObjectForTransform()
? {matrix: true}
: {}),
};
const SUPPORTED_INTERPOLATION_PARAMS: {[string]: true} = {
inputRange: true,
outputRange: true,
extrapolate: true,
extrapolateRight: true,
extrapolateLeft: true,
};
/**
* Default allowlist for component props that support native animated values.
*/
export default {
style: SUPPORTED_STYLES,
} as AnimatedPropsAllowlist;
export function allowInterpolationParam(param: string): void {
SUPPORTED_INTERPOLATION_PARAMS[param] = true;
}
export function allowStyleProp(prop: string): void {
SUPPORTED_STYLES[prop] = true;
}
export function allowTransformProp(prop: string): void {
SUPPORTED_TRANSFORMS[prop] = true;
}
export function isSupportedColorStyleProp(prop: string): boolean {
return SUPPORTED_COLOR_STYLES.hasOwnProperty(prop);
}
export function isSupportedInterpolationParam(param: string): boolean {
return SUPPORTED_INTERPOLATION_PARAMS.hasOwnProperty(param);
}
export function isSupportedStyleProp(prop: string): boolean {
return SUPPORTED_STYLES.hasOwnProperty(prop);
}
export function isSupportedTransformProp(prop: string): boolean {
return SUPPORTED_TRANSFORMS.hasOwnProperty(prop);
}

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
export * from '../../src/private/specs_DEPRECATED/modules/NativeAnimatedModule';
import NativeAnimatedModule from '../../src/private/specs_DEPRECATED/modules/NativeAnimatedModule';
export default NativeAnimatedModule;

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
export * from '../../src/private/specs_DEPRECATED/modules/NativeAnimatedTurboModule';
import NativeAnimatedTurboModule from '../../src/private/specs_DEPRECATED/modules/NativeAnimatedTurboModule';
export default NativeAnimatedTurboModule;

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
type SpringConfigType = {
stiffness: number,
damping: number,
...
};
function stiffnessFromOrigamiValue(oValue: number) {
return (oValue - 30) * 3.62 + 194;
}
function dampingFromOrigamiValue(oValue: number) {
return (oValue - 8) * 3 + 25;
}
export function fromOrigamiTensionAndFriction(
tension: number,
friction: number,
): SpringConfigType {
return {
stiffness: stiffnessFromOrigamiValue(tension),
damping: dampingFromOrigamiValue(friction),
};
}
export function fromBouncinessAndSpeed(
bounciness: number,
speed: number,
): SpringConfigType {
function normalize(value: number, startValue: number, endValue: number) {
return (value - startValue) / (endValue - startValue);
}
function projectNormal(n: number, start: number, end: number) {
return start + n * (end - start);
}
function linearInterpolation(t: number, start: number, end: number) {
return t * end + (1 - t) * start;
}
function quadraticOutInterpolation(t: number, start: number, end: number) {
return linearInterpolation(2 * t - t * t, start, end);
}
function b3Friction1(x: number) {
return 0.0007 * Math.pow(x, 3) - 0.031 * Math.pow(x, 2) + 0.64 * x + 1.28;
}
function b3Friction2(x: number) {
return 0.000044 * Math.pow(x, 3) - 0.006 * Math.pow(x, 2) + 0.36 * x + 2;
}
function b3Friction3(x: number) {
return (
0.00000045 * Math.pow(x, 3) -
0.000332 * Math.pow(x, 2) +
0.1078 * x +
5.84
);
}
function b3Nobounce(tension: number) {
if (tension <= 18) {
return b3Friction1(tension);
} else if (tension > 18 && tension <= 44) {
return b3Friction2(tension);
} else {
return b3Friction3(tension);
}
}
let b = normalize(bounciness / 1.7, 0, 20);
b = projectNormal(b, 0, 0.8);
const s = normalize(speed / 1.7, 0, 20);
const bouncyTension = projectNormal(s, 0.5, 200);
const bouncyFriction = quadraticOutInterpolation(
b,
b3Nobounce(bouncyTension),
0.01,
);
return {
stiffness: stiffnessFromOrigamiValue(bouncyTension),
damping: dampingFromOrigamiValue(bouncyFriction),
};
}

View File

@@ -0,0 +1,200 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type AnimatedNode from '../nodes/AnimatedNode';
import type AnimatedValue from '../nodes/AnimatedValue';
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags';
import AnimatedProps from '../nodes/AnimatedProps';
export type EndResult = {
finished: boolean,
value?: number,
offset?: number,
...
};
export type EndCallback = (result: EndResult) => void;
export type AnimationConfig = $ReadOnly<{
isInteraction?: boolean,
useNativeDriver: boolean,
platformConfig?: PlatformConfig,
onComplete?: ?EndCallback,
iterations?: number,
isLooping?: boolean,
debugID?: ?string,
...
}>;
let startNativeAnimationNextId = 1;
// Important note: start() and stop() will only be called at most once.
// Once an animation has been stopped or finished its course, it will
// not be reused.
export default class Animation {
_nativeID: ?number;
_onEnd: ?EndCallback;
_useNativeDriver: boolean;
__active: boolean;
__isInteraction: boolean;
__isLooping: ?boolean;
__iterations: number;
__debugID: ?string;
constructor(config: AnimationConfig) {
this._useNativeDriver = NativeAnimatedHelper.shouldUseNativeDriver(config);
this.__active = false;
this.__isInteraction = config.isInteraction ?? !this._useNativeDriver;
this.__isLooping = config.isLooping;
this.__iterations = config.iterations ?? 1;
if (__DEV__) {
this.__debugID = config.debugID;
}
}
start(
fromValue: number,
onUpdate: (value: number) => void,
onEnd: ?EndCallback,
previousAnimation: ?Animation,
animatedValue: AnimatedValue,
): void {
if (!this._useNativeDriver && animatedValue.__isNative === true) {
throw new Error(
'Attempting to run JS driven animation on animated node ' +
'that has been moved to "native" earlier by starting an ' +
'animation with `useNativeDriver: true`',
);
}
this._onEnd = onEnd;
this.__active = true;
}
stop(): void {
if (this._nativeID != null) {
const nativeID = this._nativeID;
const identifier = `${nativeID}:stopAnimation`;
try {
// This is only required when singleOpBatching is used, as otherwise
// we flush calls immediately when there's no pending queue.
NativeAnimatedHelper.API.setWaitingForIdentifier(identifier);
NativeAnimatedHelper.API.stopAnimation(nativeID);
} finally {
NativeAnimatedHelper.API.unsetWaitingForIdentifier(identifier);
}
}
this.__active = false;
}
__getNativeAnimationConfig(): $ReadOnly<{
platformConfig: ?PlatformConfig,
...
}> {
// Subclasses that have corresponding animation implementation done in native
// should override this method
throw new Error('This animation type cannot be offloaded to native');
}
__findAnimatedPropsNodes(node: AnimatedNode): Array<AnimatedProps> {
const result = [];
if (node instanceof AnimatedProps) {
result.push(node);
return result;
}
for (const child of node.__getChildren()) {
result.push(...this.__findAnimatedPropsNodes(child));
}
return result;
}
__startAnimationIfNative(animatedValue: AnimatedValue): boolean {
if (!this._useNativeDriver) {
return false;
}
const startNativeAnimationWaitId = `${startNativeAnimationNextId}:startAnimation`;
startNativeAnimationNextId += 1;
NativeAnimatedHelper.API.setWaitingForIdentifier(
startNativeAnimationWaitId,
);
try {
const config = this.__getNativeAnimationConfig();
animatedValue.__makeNative(config.platformConfig);
this._nativeID = NativeAnimatedHelper.generateNewAnimationId();
NativeAnimatedHelper.API.startAnimatingNode(
this._nativeID,
animatedValue.__getNativeTag(),
config,
result => {
this.__notifyAnimationEnd(result);
// When using natively driven animations, once the animation completes,
// we need to ensure that the JS side nodes are synced with the updated
// values.
const {value, offset} = result;
if (value != null) {
animatedValue.__onAnimatedValueUpdateReceived(value, offset);
const isJsSyncRemoved =
ReactNativeFeatureFlags.cxxNativeAnimatedEnabled() &&
!ReactNativeFeatureFlags.disableFabricCommitInCXXAnimated() &&
ReactNativeFeatureFlags.cxxNativeAnimatedRemoveJsSync();
if (!isJsSyncRemoved) {
if (this.__isLooping === true) {
return;
}
}
// Once the JS side node is synced with the updated values, trigger an
// update on the AnimatedProps nodes to call any registered callbacks.
this.__findAnimatedPropsNodes(animatedValue).forEach(node =>
node.update(),
);
}
},
);
return true;
} catch (e) {
throw e;
} finally {
NativeAnimatedHelper.API.unsetWaitingForIdentifier(
startNativeAnimationWaitId,
);
}
}
/**
* Notify the completion callback that the animation has ended. The completion
* callback will never be called more than once.
*/
__notifyAnimationEnd(result: EndResult): void {
const callback = this._onEnd;
if (callback != null) {
this._onEnd = null;
callback(result);
}
}
__getDebugID(): ?string {
if (__DEV__) {
return this.__debugID;
}
return undefined;
}
}

View File

@@ -0,0 +1,122 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type AnimatedValue from '../nodes/AnimatedValue';
import type {AnimationConfig, EndCallback} from './Animation';
import Animation from './Animation';
export type DecayAnimationConfig = $ReadOnly<{
...AnimationConfig,
velocity:
| number
| $ReadOnly<{
x: number,
y: number,
...
}>,
deceleration?: number,
...
}>;
export type DecayAnimationConfigSingle = $ReadOnly<{
...AnimationConfig,
velocity: number,
deceleration?: number,
...
}>;
export default class DecayAnimation extends Animation {
_startTime: number;
_lastValue: number;
_fromValue: number;
_deceleration: number;
_velocity: number;
_onUpdate: (value: number) => void;
_animationFrame: ?AnimationFrameID;
_platformConfig: ?PlatformConfig;
constructor(config: DecayAnimationConfigSingle) {
super(config);
this._deceleration = config.deceleration ?? 0.998;
this._velocity = config.velocity;
this._platformConfig = config.platformConfig;
}
__getNativeAnimationConfig(): $ReadOnly<{
deceleration: number,
iterations: number,
platformConfig: ?PlatformConfig,
type: 'decay',
velocity: number,
...
}> {
return {
type: 'decay',
deceleration: this._deceleration,
velocity: this._velocity,
iterations: this.__iterations,
platformConfig: this._platformConfig,
debugID: this.__getDebugID(),
};
}
start(
fromValue: number,
onUpdate: (value: number) => void,
onEnd: ?EndCallback,
previousAnimation: ?Animation,
animatedValue: AnimatedValue,
): void {
super.start(fromValue, onUpdate, onEnd, previousAnimation, animatedValue);
this._lastValue = fromValue;
this._fromValue = fromValue;
this._onUpdate = onUpdate;
this._startTime = Date.now();
const useNativeDriver = this.__startAnimationIfNative(animatedValue);
if (!useNativeDriver) {
this._animationFrame = requestAnimationFrame(() => this.onUpdate());
}
}
onUpdate(): void {
const now = Date.now();
const value =
this._fromValue +
(this._velocity / (1 - this._deceleration)) *
(1 - Math.exp(-(1 - this._deceleration) * (now - this._startTime)));
this._onUpdate(value);
if (Math.abs(this._lastValue - value) < 0.1) {
this.__notifyAnimationEnd({finished: true});
return;
}
this._lastValue = value;
if (this.__active) {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
}
}
stop(): void {
super.stop();
if (this._animationFrame != null) {
global.cancelAnimationFrame(this._animationFrame);
}
this.__notifyAnimationEnd({finished: false});
}
}

View File

@@ -0,0 +1,373 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type AnimatedInterpolation from '../nodes/AnimatedInterpolation';
import type AnimatedValue from '../nodes/AnimatedValue';
import type AnimatedValueXY from '../nodes/AnimatedValueXY';
import type {AnimationConfig, EndCallback} from './Animation';
import AnimatedColor from '../nodes/AnimatedColor';
import * as SpringConfig from '../SpringConfig';
import Animation from './Animation';
import invariant from 'invariant';
export type SpringAnimationConfig = $ReadOnly<{
...AnimationConfig,
toValue:
| number
| AnimatedValue
| {
x: number,
y: number,
...
}
| AnimatedValueXY
| {
r: number,
g: number,
b: number,
a: number,
...
}
| AnimatedColor
| AnimatedInterpolation<number>,
overshootClamping?: boolean,
restDisplacementThreshold?: number,
restSpeedThreshold?: number,
velocity?:
| number
| $ReadOnly<{
x: number,
y: number,
...
}>,
bounciness?: number,
speed?: number,
tension?: number,
friction?: number,
stiffness?: number,
damping?: number,
mass?: number,
delay?: number,
...
}>;
export type SpringAnimationConfigSingle = $ReadOnly<{
...AnimationConfig,
toValue: number,
overshootClamping?: boolean,
restDisplacementThreshold?: number,
restSpeedThreshold?: number,
velocity?: number,
bounciness?: number,
speed?: number,
tension?: number,
friction?: number,
stiffness?: number,
damping?: number,
mass?: number,
delay?: number,
...
}>;
opaque type SpringAnimationInternalState = $ReadOnly<{
lastPosition: number,
lastVelocity: number,
lastTime: number,
}>;
export default class SpringAnimation extends Animation {
_overshootClamping: boolean;
_restDisplacementThreshold: number;
_restSpeedThreshold: number;
_lastVelocity: number;
_startPosition: number;
_lastPosition: number;
_fromValue: number;
_toValue: number;
_stiffness: number;
_damping: number;
_mass: number;
_initialVelocity: number;
_delay: number;
_timeout: ?TimeoutID;
_startTime: number;
_lastTime: number;
_frameTime: number;
_onUpdate: (value: number) => void;
_animationFrame: ?AnimationFrameID;
_platformConfig: ?PlatformConfig;
constructor(config: SpringAnimationConfigSingle) {
super(config);
this._overshootClamping = config.overshootClamping ?? false;
this._restDisplacementThreshold = config.restDisplacementThreshold ?? 0.001;
this._restSpeedThreshold = config.restSpeedThreshold ?? 0.001;
this._initialVelocity = config.velocity ?? 0;
this._lastVelocity = config.velocity ?? 0;
this._toValue = config.toValue;
this._delay = config.delay ?? 0;
this._platformConfig = config.platformConfig;
if (
config.stiffness !== undefined ||
config.damping !== undefined ||
config.mass !== undefined
) {
invariant(
config.bounciness === undefined &&
config.speed === undefined &&
config.tension === undefined &&
config.friction === undefined,
'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one',
);
this._stiffness = config.stiffness ?? 100;
this._damping = config.damping ?? 10;
this._mass = config.mass ?? 1;
} else if (config.bounciness !== undefined || config.speed !== undefined) {
// Convert the origami bounciness/speed values to stiffness/damping
// We assume mass is 1.
invariant(
config.tension === undefined &&
config.friction === undefined &&
config.stiffness === undefined &&
config.damping === undefined &&
config.mass === undefined,
'You can define one of bounciness/speed, tension/friction, or stiffness/damping/mass, but not more than one',
);
const springConfig = SpringConfig.fromBouncinessAndSpeed(
config.bounciness ?? 8,
config.speed ?? 12,
);
this._stiffness = springConfig.stiffness;
this._damping = springConfig.damping;
this._mass = 1;
} else {
// Convert the origami tension/friction values to stiffness/damping
// We assume mass is 1.
const springConfig = SpringConfig.fromOrigamiTensionAndFriction(
config.tension ?? 40,
config.friction ?? 7,
);
this._stiffness = springConfig.stiffness;
this._damping = springConfig.damping;
this._mass = 1;
}
invariant(this._stiffness > 0, 'Stiffness value must be greater than 0');
invariant(this._damping > 0, 'Damping value must be greater than 0');
invariant(this._mass > 0, 'Mass value must be greater than 0');
}
__getNativeAnimationConfig(): $ReadOnly<{
damping: number,
initialVelocity: number,
iterations: number,
mass: number,
platformConfig: ?PlatformConfig,
overshootClamping: boolean,
restDisplacementThreshold: number,
restSpeedThreshold: number,
stiffness: number,
toValue: number,
type: 'spring',
...
}> {
return {
type: 'spring',
overshootClamping: this._overshootClamping,
restDisplacementThreshold: this._restDisplacementThreshold,
restSpeedThreshold: this._restSpeedThreshold,
stiffness: this._stiffness,
damping: this._damping,
mass: this._mass,
initialVelocity: this._initialVelocity ?? this._lastVelocity,
toValue: this._toValue,
iterations: this.__iterations,
platformConfig: this._platformConfig,
debugID: this.__getDebugID(),
};
}
start(
fromValue: number,
onUpdate: (value: number) => void,
onEnd: ?EndCallback,
previousAnimation: ?Animation,
animatedValue: AnimatedValue,
): void {
super.start(fromValue, onUpdate, onEnd, previousAnimation, animatedValue);
this._startPosition = fromValue;
this._lastPosition = this._startPosition;
this._onUpdate = onUpdate;
this._lastTime = Date.now();
this._frameTime = 0.0;
if (previousAnimation instanceof SpringAnimation) {
const internalState = previousAnimation.getInternalState();
this._lastPosition = internalState.lastPosition;
this._lastVelocity = internalState.lastVelocity;
// Set the initial velocity to the last velocity
this._initialVelocity = this._lastVelocity;
this._lastTime = internalState.lastTime;
}
const start = () => {
const useNativeDriver = this.__startAnimationIfNative(animatedValue);
if (!useNativeDriver) {
this.onUpdate();
}
};
// If this._delay is more than 0, we start after the timeout.
if (this._delay) {
this._timeout = setTimeout(start, this._delay);
} else {
start();
}
}
getInternalState(): SpringAnimationInternalState {
return {
lastPosition: this._lastPosition,
lastVelocity: this._lastVelocity,
lastTime: this._lastTime,
};
}
/**
* This spring model is based off of a damped harmonic oscillator
* (https://en.wikipedia.org/wiki/Harmonic_oscillator#Damped_harmonic_oscillator).
*
* We use the closed form of the second order differential equation:
*
* x'' + (2ζ⍵_0)x' + ⍵^2x = 0
*
* where
* ⍵_0 = √(k / m) (undamped angular frequency of the oscillator),
* ζ = c / 2√mk (damping ratio),
* c = damping constant
* k = stiffness
* m = mass
*
* The derivation of the closed form is described in detail here:
* http://planetmath.org/sites/default/files/texpdf/39745.pdf
*
* This algorithm happens to match the algorithm used by CASpringAnimation,
* a QuartzCore (iOS) API that creates spring animations.
*/
onUpdate(): void {
// If for some reason we lost a lot of frames (e.g. process large payload or
// stopped in the debugger), we only advance by 4 frames worth of
// computation and will continue on the next frame. It's better to have it
// running at faster speed than jumping to the end.
const MAX_STEPS = 64;
let now = Date.now();
if (now > this._lastTime + MAX_STEPS) {
now = this._lastTime + MAX_STEPS;
}
const deltaTime = (now - this._lastTime) / 1000;
this._frameTime += deltaTime;
const c: number = this._damping;
const m: number = this._mass;
const k: number = this._stiffness;
const v0: number = -this._initialVelocity;
const zeta = c / (2 * Math.sqrt(k * m)); // damping ratio
const omega0 = Math.sqrt(k / m); // undamped angular frequency of the oscillator (rad/ms)
const omega1 = omega0 * Math.sqrt(1.0 - zeta * zeta); // exponential decay
const x0 = this._toValue - this._startPosition; // calculate the oscillation from x0 = 1 to x = 0
let position = 0.0;
let velocity = 0.0;
const t = this._frameTime;
if (zeta < 1) {
// Under damped
const envelope = Math.exp(-zeta * omega0 * t);
position =
this._toValue -
envelope *
(((v0 + zeta * omega0 * x0) / omega1) * Math.sin(omega1 * t) +
x0 * Math.cos(omega1 * t));
// This looks crazy -- it's actually just the derivative of the
// oscillation function
velocity =
zeta *
omega0 *
envelope *
((Math.sin(omega1 * t) * (v0 + zeta * omega0 * x0)) / omega1 +
x0 * Math.cos(omega1 * t)) -
envelope *
(Math.cos(omega1 * t) * (v0 + zeta * omega0 * x0) -
omega1 * x0 * Math.sin(omega1 * t));
} else {
// Critically damped
const envelope = Math.exp(-omega0 * t);
position = this._toValue - envelope * (x0 + (v0 + omega0 * x0) * t);
velocity =
envelope * (v0 * (t * omega0 - 1) + t * x0 * (omega0 * omega0));
}
this._lastTime = now;
this._lastPosition = position;
this._lastVelocity = velocity;
this._onUpdate(position);
if (!this.__active) {
// a listener might have stopped us in _onUpdate
return;
}
// Conditions for stopping the spring animation
let isOvershooting = false;
if (this._overshootClamping && this._stiffness !== 0) {
if (this._startPosition < this._toValue) {
isOvershooting = position > this._toValue;
} else {
isOvershooting = position < this._toValue;
}
}
const isVelocity = Math.abs(velocity) <= this._restSpeedThreshold;
let isDisplacement = true;
if (this._stiffness !== 0) {
isDisplacement =
Math.abs(this._toValue - position) <= this._restDisplacementThreshold;
}
if (isOvershooting || (isVelocity && isDisplacement)) {
if (this._stiffness !== 0) {
// Ensure that we end up with a round value
this._lastPosition = this._toValue;
this._lastVelocity = 0;
this._onUpdate(this._toValue);
}
this.__notifyAnimationEnd({finished: true});
return;
}
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
}
stop(): void {
super.stop();
clearTimeout(this._timeout);
if (this._animationFrame != null) {
global.cancelAnimationFrame(this._animationFrame);
}
this.__notifyAnimationEnd({finished: false});
}
}

View File

@@ -0,0 +1,176 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {RgbaValue} from '../nodes/AnimatedColor';
import type AnimatedInterpolation from '../nodes/AnimatedInterpolation';
import type AnimatedValue from '../nodes/AnimatedValue';
import type AnimatedValueXY from '../nodes/AnimatedValueXY';
import type {AnimationConfig, EndCallback} from './Animation';
import AnimatedColor from '../nodes/AnimatedColor';
import Animation from './Animation';
export type TimingAnimationConfig = $ReadOnly<{
...AnimationConfig,
toValue:
| number
| AnimatedValue
| $ReadOnly<{
x: number,
y: number,
...
}>
| AnimatedValueXY
| RgbaValue
| AnimatedColor
| AnimatedInterpolation<number>,
easing?: (value: number) => number,
duration?: number,
delay?: number,
...
}>;
export type TimingAnimationConfigSingle = $ReadOnly<{
...AnimationConfig,
toValue: number,
easing?: (value: number) => number,
duration?: number,
delay?: number,
...
}>;
let _easeInOut;
function easeInOut() {
/* $FlowFixMe[constant-condition] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/1v97vimq. */
if (!_easeInOut) {
const Easing = require('../Easing').default;
_easeInOut = Easing.inOut(Easing.ease);
}
return _easeInOut;
}
export default class TimingAnimation extends Animation {
_startTime: number;
_fromValue: number;
_toValue: number;
_duration: number;
_delay: number;
_easing: (value: number) => number;
_onUpdate: (value: number) => void;
_animationFrame: ?AnimationFrameID;
_timeout: ?TimeoutID;
_platformConfig: ?PlatformConfig;
constructor(config: TimingAnimationConfigSingle) {
super(config);
this._toValue = config.toValue;
this._easing = config.easing ?? easeInOut();
this._duration = config.duration ?? 500;
this._delay = config.delay ?? 0;
this._platformConfig = config.platformConfig;
}
__getNativeAnimationConfig(): $ReadOnly<{
type: 'frames',
frames: $ReadOnlyArray<number>,
toValue: number,
iterations: number,
platformConfig: ?PlatformConfig,
...
}> {
const frameDuration = 1000.0 / 60.0;
const frames = [];
const numFrames = Math.round(this._duration / frameDuration);
for (let frame = 0; frame < numFrames; frame++) {
frames.push(this._easing(frame / numFrames));
}
frames.push(this._easing(1));
return {
type: 'frames',
frames,
toValue: this._toValue,
iterations: this.__iterations,
platformConfig: this._platformConfig,
debugID: this.__getDebugID(),
};
}
start(
fromValue: number,
onUpdate: (value: number) => void,
onEnd: ?EndCallback,
previousAnimation: ?Animation,
animatedValue: AnimatedValue,
): void {
super.start(fromValue, onUpdate, onEnd, previousAnimation, animatedValue);
this._fromValue = fromValue;
this._onUpdate = onUpdate;
const start = () => {
this._startTime = Date.now();
const useNativeDriver = this.__startAnimationIfNative(animatedValue);
if (!useNativeDriver) {
// Animations that sometimes have 0 duration and sometimes do not
// still need to use the native driver when duration is 0 so as to
// not cause intermixed JS and native animations.
if (this._duration === 0) {
this._onUpdate(this._toValue);
this.__notifyAnimationEnd({finished: true});
} else {
this._animationFrame = requestAnimationFrame(() => this.onUpdate());
}
}
};
if (this._delay) {
this._timeout = setTimeout(start, this._delay);
} else {
start();
}
}
onUpdate(): void {
const now = Date.now();
if (now >= this._startTime + this._duration) {
if (this._duration === 0) {
this._onUpdate(this._toValue);
} else {
this._onUpdate(
this._fromValue + this._easing(1) * (this._toValue - this._fromValue),
);
}
this.__notifyAnimationEnd({finished: true});
return;
}
this._onUpdate(
this._fromValue +
this._easing((now - this._startTime) / this._duration) *
(this._toValue - this._fromValue),
);
if (this.__active) {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
}
}
stop(): void {
super.stop();
clearTimeout(this._timeout);
if (this._animationFrame != null) {
global.cancelAnimationFrame(this._animationFrame);
}
this.__notifyAnimationEnd({finished: false});
}
}

164
node_modules/react-native/Libraries/Animated/bezier.js generated vendored Normal file
View File

@@ -0,0 +1,164 @@
/**
* Portions Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
/**
* BezierEasing - use bezier curve for transition easing function
* https://github.com/gre/bezier-easing
* @copyright 2014-2015 Gaëtan Renaudeau. MIT License.
*/
'use strict';
// These values are established by empiricism with tests (tradeoff: performance VS precision)
const NEWTON_ITERATIONS = 4;
const NEWTON_MIN_SLOPE = 0.001;
const SUBDIVISION_PRECISION = 0.0000001;
const SUBDIVISION_MAX_ITERATIONS = 10;
const kSplineTableSize = 11;
const kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
const float32ArraySupported = typeof Float32Array === 'function';
function A(aA1: number, aA2: number) {
return 1.0 - 3.0 * aA2 + 3.0 * aA1;
}
function B(aA1: number, aA2: number) {
return 3.0 * aA2 - 6.0 * aA1;
}
function C(aA1: number) {
return 3.0 * aA1;
}
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
function calcBezier(aT: number, aA1: number, aA2: number) {
return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT;
}
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
function getSlope(aT: number, aA1: number, aA2: number) {
return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
}
function binarySubdivide(
aX: number,
_aA: number,
_aB: number,
mX1: number,
mX2: number,
) {
let currentX,
currentT,
i = 0,
aA = _aA,
aB = _aB;
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) > SUBDIVISION_PRECISION &&
++i < SUBDIVISION_MAX_ITERATIONS
);
return currentT;
}
function newtonRaphsonIterate(
aX: number,
_aGuessT: number,
mX1: number,
mX2: number,
) {
let aGuessT = _aGuessT;
for (let i = 0; i < NEWTON_ITERATIONS; ++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;
}
export default function bezier(
mX1: number,
mY1: number,
mX2: number,
mY2: number,
): (x: number) => number {
if (!(mX1 >= 0 && mX1 <= 1 && mX2 >= 0 && mX2 <= 1)) {
throw new Error('bezier x values must be in [0, 1] range');
}
// Precompute samples table
const sampleValues = float32ArraySupported
? new Float32Array(kSplineTableSize)
: new Array<number>(kSplineTableSize);
if (mX1 !== mY1 || mX2 !== mY2) {
for (let i = 0; i < kSplineTableSize; ++i) {
sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
}
}
function getTForX(aX: number) {
let intervalStart = 0.0;
let currentSample = 1;
const lastSample = kSplineTableSize - 1;
for (
;
currentSample !== lastSample && sampleValues[currentSample] <= aX;
++currentSample
) {
intervalStart += kSampleStepSize;
}
--currentSample;
// Interpolate to provide an initial guess for t
const dist =
(aX - sampleValues[currentSample]) /
(sampleValues[currentSample + 1] - sampleValues[currentSample]);
const guessForT = intervalStart + dist * kSampleStepSize;
const initialSlope = getSlope(guessForT, mX1, mX2);
if (initialSlope >= NEWTON_MIN_SLOPE) {
return newtonRaphsonIterate(aX, guessForT, mX1, mX2);
} else if (initialSlope === 0.0) {
return guessForT;
} else {
return binarySubdivide(
aX,
intervalStart,
intervalStart + kSampleStepSize,
mX1,
mX2,
);
}
}
return function BezierEasing(x: number): number {
if (mX1 === mY1 && mX2 === mY2) {
return x; // linear
}
// Because JavaScript number are imprecise, we should guarantee the extremes are right.
if (x === 0) {
return 0;
}
if (x === 1) {
return 1;
}
return calcBezier(getTForX(x), mY1, mY2);
};
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {AnimatedProps} from '../createAnimatedComponent';
import FlatList, {type FlatListProps} from '../../Lists/FlatList';
import createAnimatedComponent from '../createAnimatedComponent';
import * as React from 'react';
export default createAnimatedComponent(FlatList) as $FlowFixMe as component<
// $FlowExpectedError[unclear-type]
ItemT = any,
>(
ref?: React.RefSetter<FlatList<ItemT>>,
...props: AnimatedProps<FlatListProps<ItemT>>
);

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {AnimatedComponentType} from '../createAnimatedComponent';
import Image from '../../Image/Image';
import createAnimatedComponent from '../createAnimatedComponent';
import * as React from 'react';
export default (createAnimatedComponent<
$FlowFixMe,
React.ElementRef<typeof Image>,
>((Image: $FlowFixMe)): AnimatedComponentType<
React.ElementConfig<typeof Image>,
React.ElementRef<typeof Image>,
>);

View File

@@ -0,0 +1,146 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {____ViewStyle_Internal} from '../../StyleSheet/StyleSheetTypes';
import type {
AnimatedComponentType,
AnimatedProps,
} from '../createAnimatedComponent';
import RefreshControl from '../../Components/RefreshControl/RefreshControl';
import ScrollView, {
type ScrollViewProps,
} from '../../Components/ScrollView/ScrollView';
import flattenStyle from '../../StyleSheet/flattenStyle';
import splitLayoutProps from '../../StyleSheet/splitLayoutProps';
import StyleSheet from '../../StyleSheet/StyleSheet';
import Platform from '../../Utilities/Platform';
import useMergeRefs from '../../Utilities/useMergeRefs';
import createAnimatedComponent from '../createAnimatedComponent';
import useAnimatedProps from '../useAnimatedProps';
import * as React from 'react';
import {cloneElement, useMemo} from 'react';
type AnimatedScrollViewInstance = React.ElementRef<typeof ScrollView>;
/**
* @see https://github.com/facebook/react-native/commit/b8c8562
*/
const AnimatedScrollView: AnimatedComponentType<
ScrollViewProps,
AnimatedScrollViewInstance,
> = function AnimatedScrollViewWithOrWithoutInvertedRefreshControl({
ref: forwardedRef,
...props
}: {
ref?: React.RefSetter<AnimatedScrollViewInstance>,
...AnimatedProps<ScrollViewProps>,
}) {
// (Android only) When a ScrollView has a RefreshControl and
// any `style` property set with an Animated.Value, the CSS
// gets incorrectly applied twice. This is because ScrollView
// swaps the parent/child relationship of itself and the
// RefreshControl component (see ScrollView.js for more details).
if (
Platform.OS === 'android' &&
props.refreshControl != null &&
props.style != null
) {
return (
// $FlowFixMe[incompatible-type] - It should return an Animated ScrollView but it returns a ScrollView with Animated props applied.
// $FlowFixMe[incompatible-variance]
<AnimatedScrollViewWithInvertedRefreshControl
scrollEventThrottle={0.0001}
{...props}
ref={forwardedRef}
// $FlowFixMe[incompatible-type]
refreshControl={props.refreshControl}
/>
);
} else {
return (
<AnimatedScrollViewWithoutInvertedRefreshControl
scrollEventThrottle={0.0001}
{...props}
ref={forwardedRef}
/>
);
}
};
const AnimatedScrollViewWithInvertedRefreshControl =
function AnimatedScrollViewWithInvertedRefreshControl({
ref: forwardedRef,
...props
}: {
ref?: React.RefSetter<AnimatedScrollViewInstance>,
...React.ElementConfig<typeof ScrollView>,
// $FlowFixMe[unclear-type] Same Flow type as `refreshControl` in ScrollView
refreshControl: ExactReactElement_DEPRECATED<any>,
}) {
// Split `props` into the animate-able props for the parent (RefreshControl)
// and child (ScrollView).
const {intermediatePropsForRefreshControl, intermediatePropsForScrollView} =
useMemo(() => {
// $FlowFixMe[underconstrained-implicit-instantiation]
// $FlowFixMe[incompatible-type]
const {outer, inner} = splitLayoutProps(flattenStyle(props.style));
return {
intermediatePropsForRefreshControl: {style: outer},
intermediatePropsForScrollView: {...props, style: inner},
};
}, [props]);
// Handle animated props on `refreshControl`.
const [refreshControlAnimatedProps, refreshControlRef] = useAnimatedProps<
{style: ?____ViewStyle_Internal},
$FlowFixMe,
>(intermediatePropsForRefreshControl);
// NOTE: Assumes that refreshControl.ref` and `refreshControl.style` can be
// safely clobbered.
const refreshControl: ExactReactElement_DEPRECATED<typeof RefreshControl> =
cloneElement(props.refreshControl, {
...refreshControlAnimatedProps,
ref: refreshControlRef,
});
// Handle animated props on `NativeDirectionalScrollView`.
const [scrollViewAnimatedProps, scrollViewRef] = useAnimatedProps<
ScrollViewProps,
AnimatedScrollViewInstance,
>(intermediatePropsForScrollView);
const ref = useMergeRefs<AnimatedScrollViewInstance>(
scrollViewRef,
forwardedRef,
);
return (
// $FlowFixMe[incompatible-use] Investigate useAnimatedProps return value
<ScrollView
{...scrollViewAnimatedProps}
ref={ref}
refreshControl={refreshControl}
// Because `refreshControl` is a clone of `props.refreshControl` with
// `refreshControlAnimatedProps` added, we need to pass ScrollView.js
// the combined styles since it also splits the outer/inner styles for
// its parent/child, respectively. Without this, the refreshControl
// styles would be ignored.
style={StyleSheet.compose(
scrollViewAnimatedProps.style,
refreshControlAnimatedProps.style,
)}
/>
);
};
const AnimatedScrollViewWithoutInvertedRefreshControl =
createAnimatedComponent(ScrollView);
export default AnimatedScrollView;

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {AnimatedProps} from '../createAnimatedComponent';
import SectionList, {type SectionListProps} from '../../Lists/SectionList';
import createAnimatedComponent from '../createAnimatedComponent';
import * as React from 'react';
// $FlowFixMe[incompatible-type]
export default createAnimatedComponent(SectionList) as $FlowFixMe as component<
// $FlowExpectedError[unclear-type]
ItemT = any,
// $FlowExpectedError[unclear-type]
SectionT = any,
>(
ref?: React.RefSetter<SectionList<ItemT, SectionT>>,
...props: AnimatedProps<SectionListProps<ItemT, SectionT>>
);

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {AnimatedComponentType} from '../createAnimatedComponent';
import Text, {type TextProps} from '../../Text/Text';
import createAnimatedComponent from '../createAnimatedComponent';
import * as React from 'react';
export default (createAnimatedComponent<
$FlowFixMe,
React.ElementRef<typeof Text>,
>((Text: $FlowFixMe)): AnimatedComponentType<
TextProps,
React.ElementRef<typeof Text>,
>);

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {ViewProps} from '../../Components/View/ViewPropTypes';
import type {AnimatedComponentType} from '../createAnimatedComponent';
import View from '../../Components/View/View';
import createAnimatedComponent from '../createAnimatedComponent';
import * as React from 'react';
// $FlowFixMe[incompatible-type]
export default createAnimatedComponent(View) as AnimatedComponentType<
ViewProps,
React.ElementRef<typeof View>,
>;

View File

@@ -0,0 +1,163 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
import type AnimatedAddition from './nodes/AnimatedAddition';
import type AnimatedDiffClamp from './nodes/AnimatedDiffClamp';
import type AnimatedDivision from './nodes/AnimatedDivision';
import type AnimatedInterpolation from './nodes/AnimatedInterpolation';
import type AnimatedModulo from './nodes/AnimatedModulo';
import type AnimatedMultiplication from './nodes/AnimatedMultiplication';
import type AnimatedNode from './nodes/AnimatedNode';
import type {AnimatedPropsAllowlist} from './nodes/AnimatedProps';
import type AnimatedSubtraction from './nodes/AnimatedSubtraction';
import type AnimatedValue from './nodes/AnimatedValue';
import createAnimatedPropsHook from '../../src/private/animated/createAnimatedPropsHook';
import composeStyles from '../../src/private/styles/composeStyles';
import {type ViewProps} from '../Components/View/ViewPropTypes';
import useMergeRefs from '../Utilities/useMergeRefs';
import * as React from 'react';
import {useMemo} from 'react';
type Nullable = void | null;
type Primitive = string | number | boolean | symbol | void;
type Builtin = (...$ReadOnlyArray<empty>) => mixed | Date | Error | RegExp;
export type WithAnimatedValue<+T> = T extends Builtin | Nullable
? T
: T extends Primitive
?
| T
| AnimatedNode
| AnimatedAddition
| AnimatedSubtraction
| AnimatedDivision
| AnimatedMultiplication
| AnimatedModulo
| AnimatedDiffClamp
| AnimatedValue
| AnimatedInterpolation<number | string>
| AnimatedInterpolation<number>
| AnimatedInterpolation<string>
: T extends $ReadOnlyArray<infer P>
? $ReadOnlyArray<WithAnimatedValue<P>>
: T extends {...}
? {+[K in keyof T]: WithAnimatedValue<T[K]>}
: T;
type NonAnimatedProps =
| 'ref'
| 'innerViewRef'
| 'scrollViewRef'
| 'testID'
| 'disabled'
| 'accessibilityLabel';
type PassThroughProps = $ReadOnly<{
passthroughAnimatedPropExplicitValues?: ViewProps | null,
}>;
type LooseOmit<O: interface {}, K: $Keys<$FlowFixMe>> = Pick<
O,
Exclude<$Keys<O>, K>,
>;
export type AnimatedProps<Props: {...}> = LooseOmit<
{
[K in keyof Props]: K extends NonAnimatedProps
? Props[K]
: WithAnimatedValue<Props[K]>,
},
'ref',
> &
PassThroughProps;
export type AnimatedBaseProps<Props: {...}> = LooseOmit<
{
[K in keyof Props]: K extends NonAnimatedProps
? Props[K]
: WithAnimatedValue<Props[K]>,
},
'ref',
>;
export type AnimatedComponentType<Props: {...}, +Instance = mixed> = component(
ref?: React.RefSetter<Instance>,
...AnimatedProps<Props>
);
export default function createAnimatedComponent<
TInstance: React.ComponentType<any>,
>(
Component: TInstance,
): AnimatedComponentType<
$ReadOnly<React.ElementConfig<TInstance>>,
React.ElementRef<TInstance>,
> {
return unstable_createAnimatedComponentWithAllowlist(Component, null);
}
export function unstable_createAnimatedComponentWithAllowlist<
TProps: {...},
TInstance: React.ComponentType<TProps>,
>(
Component: TInstance,
allowlist: ?AnimatedPropsAllowlist,
): AnimatedComponentType<TProps, React.ElementRef<TInstance>> {
const useAnimatedProps = createAnimatedPropsHook(allowlist);
const AnimatedComponent: AnimatedComponentType<
TProps,
React.ElementRef<TInstance>,
> = ({
ref: forwardedRef,
...props
}: {
ref?: React.RefSetter<React.ElementRef<TInstance>>,
...AnimatedProps<TProps>,
}) => {
const [reducedProps, callbackRef] = useAnimatedProps<
TProps,
React.ElementRef<TInstance>,
>(props);
const ref = useMergeRefs<React.ElementRef<TInstance>>(
callbackRef,
forwardedRef,
);
// Some components require explicit passthrough values for animation
// to work properly. For example, if an animated component is
// transformed and Pressable, onPress will not work after transform
// without these passthrough values.
// $FlowFixMe[prop-missing]
const {passthroughAnimatedPropExplicitValues, style} = reducedProps;
const passthroughStyle = passthroughAnimatedPropExplicitValues?.style;
const mergedStyle = useMemo(
() => composeStyles(style, passthroughStyle),
[passthroughStyle, style],
);
// NOTE: It is important that `passthroughAnimatedPropExplicitValues` is
// spread after `reducedProps` but before `style`.
return (
<Component
{...reducedProps}
{...passthroughAnimatedPropExplicitValues}
style={mergedStyle}
ref={ref}
/>
);
};
AnimatedComponent.displayName = `Animated(${
Component.displayName || 'Anonymous'
})`;
return AnimatedComponent;
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {InterpolationConfigType} from './AnimatedInterpolation';
import type AnimatedNode from './AnimatedNode';
import type {AnimatedNodeConfig} from './AnimatedNode';
import AnimatedInterpolation from './AnimatedInterpolation';
import AnimatedValue from './AnimatedValue';
import AnimatedWithChildren from './AnimatedWithChildren';
export default class AnimatedAddition extends AnimatedWithChildren {
_a: AnimatedNode;
_b: AnimatedNode;
constructor(
a: AnimatedNode | number,
b: AnimatedNode | number,
config?: ?AnimatedNodeConfig,
) {
super(config);
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
}
__makeNative(platformConfig: ?PlatformConfig) {
this._a.__makeNative(platformConfig);
this._b.__makeNative(platformConfig);
super.__makeNative(platformConfig);
}
__getValue(): number {
return this._a.__getValue() + this._b.__getValue();
}
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}
__attach(): void {
this._a.__addChild(this);
this._b.__addChild(this);
super.__attach();
}
__detach(): void {
this._a.__removeChild(this);
this._b.__removeChild(this);
super.__detach();
}
__getNativeConfig(): any {
return {
type: 'addition',
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
debugID: this.__getDebugID(),
};
}
}

View File

@@ -0,0 +1,339 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {ProcessedColorValue} from '../../StyleSheet/processColor';
import type {ColorValue} from '../../StyleSheet/StyleSheet';
import type {NativeColorValue} from '../../StyleSheet/StyleSheetTypes';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {AnimatedNodeConfig} from './AnimatedNode';
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import normalizeColor from '../../StyleSheet/normalizeColor';
import {processColorObject} from '../../StyleSheet/PlatformColorValueTypes';
import AnimatedValue, {flushValue} from './AnimatedValue';
import AnimatedWithChildren from './AnimatedWithChildren';
export type AnimatedColorConfig = $ReadOnly<{
...AnimatedNodeConfig,
useNativeDriver: boolean,
}>;
type ColorListenerCallback = (value: ColorValue) => mixed;
export type RgbaValue = {
+r: number,
+g: number,
+b: number,
+a: number,
...
};
type RgbaAnimatedValue = {
+r: AnimatedValue,
+g: AnimatedValue,
+b: AnimatedValue,
+a: AnimatedValue,
...
};
export type InputValue = ?(RgbaValue | RgbaAnimatedValue | ColorValue);
const NativeAnimatedAPI = NativeAnimatedHelper.API;
const defaultColor: RgbaValue = {r: 0, g: 0, b: 0, a: 1.0};
/* eslint no-bitwise: 0 */
function processColor(
color?: ?(ColorValue | RgbaValue),
): ?(RgbaValue | NativeColorValue) {
if (color === undefined || color === null) {
return null;
}
if (isRgbaValue(color)) {
// $FlowFixMe[incompatible-type] - Type is verified above
return (color: RgbaValue);
}
let normalizedColor: ?ProcessedColorValue = normalizeColor(
// $FlowFixMe[incompatible-type] - Type is verified above
(color: ColorValue),
);
if (normalizedColor === undefined || normalizedColor === null) {
return null;
}
if (typeof normalizedColor === 'object') {
const processedColorObj: ?NativeColorValue =
processColorObject(normalizedColor);
if (processedColorObj != null) {
return processedColorObj;
}
} else if (typeof normalizedColor === 'number') {
const r: number = (normalizedColor & 0xff000000) >>> 24;
const g: number = (normalizedColor & 0x00ff0000) >>> 16;
const b: number = (normalizedColor & 0x0000ff00) >>> 8;
const a: number = (normalizedColor & 0x000000ff) / 255;
return {r, g, b, a};
}
return null;
}
function isRgbaValue(value: any): boolean {
return (
value &&
typeof value.r === 'number' &&
typeof value.g === 'number' &&
typeof value.b === 'number' &&
typeof value.a === 'number'
);
}
function isRgbaAnimatedValue(value: any): boolean {
return (
value &&
value.r instanceof AnimatedValue &&
value.g instanceof AnimatedValue &&
value.b instanceof AnimatedValue &&
value.a instanceof AnimatedValue
);
}
export function getRgbaValueAndNativeColor(
value: RgbaValue | ColorValue,
): $ReadOnly<{
rgbaValue: RgbaValue,
nativeColor?: NativeColorValue,
}> {
const processedColor: RgbaValue | NativeColorValue =
// $FlowFixMe[incompatible-type] - Type is verified above
processColor((value: ColorValue | RgbaValue)) ?? defaultColor;
if (isRgbaValue(processedColor)) {
// $FlowFixMe[incompatible-type] - Type is verified above
return {rgbaValue: (processedColor: RgbaValue)};
} else {
return {
// $FlowFixMe[incompatible-type] - Type is verified above
nativeColor: (processedColor: NativeColorValue),
rgbaValue: defaultColor,
};
}
}
export default class AnimatedColor extends AnimatedWithChildren {
r: AnimatedValue;
g: AnimatedValue;
b: AnimatedValue;
a: AnimatedValue;
nativeColor: ?NativeColorValue;
_suspendCallbacks: number = 0;
constructor(valueIn?: InputValue, config?: ?AnimatedColorConfig) {
super(config);
let value: RgbaValue | RgbaAnimatedValue | ColorValue =
valueIn ?? defaultColor;
if (isRgbaAnimatedValue(value)) {
// $FlowFixMe[incompatible-type] - Type is verified above
const rgbaAnimatedValue: RgbaAnimatedValue = (value: RgbaAnimatedValue);
this.r = rgbaAnimatedValue.r;
this.g = rgbaAnimatedValue.g;
this.b = rgbaAnimatedValue.b;
this.a = rgbaAnimatedValue.a;
} else {
const {rgbaValue: initColor, nativeColor} = getRgbaValueAndNativeColor(
// $FlowFixMe[incompatible-type] - Type is verified above
(value: ColorValue | RgbaValue),
);
if (nativeColor) {
this.nativeColor = nativeColor;
}
this.r = new AnimatedValue(initColor.r);
this.g = new AnimatedValue(initColor.g);
this.b = new AnimatedValue(initColor.b);
this.a = new AnimatedValue(initColor.a);
}
if (config?.useNativeDriver) {
this.__makeNative();
}
}
/**
* Directly set the value. This will stop any animations running on the value
* and update all the bound properties.
*/
setValue(value: RgbaValue | ColorValue): void {
let shouldUpdateNodeConfig = false;
if (this.__isNative) {
const nativeTag = this.__getNativeTag();
NativeAnimatedAPI.setWaitingForIdentifier(nativeTag.toString());
}
const processedColor: RgbaValue | NativeColorValue =
processColor(value) ?? defaultColor;
this._withSuspendedCallbacks(() => {
if (isRgbaValue(processedColor)) {
// $FlowFixMe[incompatible-type] - Type is verified above
const rgbaValue: RgbaValue = processedColor;
this.r.setValue(rgbaValue.r);
this.g.setValue(rgbaValue.g);
this.b.setValue(rgbaValue.b);
this.a.setValue(rgbaValue.a);
if (this.nativeColor != null) {
this.nativeColor = null;
shouldUpdateNodeConfig = true;
}
} else {
// $FlowFixMe[incompatible-type] - Type is verified above
const nativeColor: NativeColorValue = processedColor;
if (this.nativeColor !== nativeColor) {
this.nativeColor = nativeColor;
shouldUpdateNodeConfig = true;
}
}
});
if (this.__isNative) {
const nativeTag = this.__getNativeTag();
if (shouldUpdateNodeConfig) {
NativeAnimatedAPI.updateAnimatedNodeConfig(
nativeTag,
this.__getNativeConfig(),
);
}
NativeAnimatedAPI.unsetWaitingForIdentifier(nativeTag.toString());
} else {
flushValue(this);
}
// $FlowFixMe[incompatible-type]
this.__callListeners(this.__getValue());
}
/**
* Sets an offset that is applied on top of whatever value is set, whether
* via `setValue`, an animation, or `Animated.event`. Useful for compensating
* things like the start of a pan gesture.
*/
setOffset(offset: RgbaValue): void {
this.r.setOffset(offset.r);
this.g.setOffset(offset.g);
this.b.setOffset(offset.b);
this.a.setOffset(offset.a);
}
/**
* Merges the offset value into the base value and resets the offset to zero.
* The final output of the value is unchanged.
*/
flattenOffset(): void {
this.r.flattenOffset();
this.g.flattenOffset();
this.b.flattenOffset();
this.a.flattenOffset();
}
/**
* Sets the offset value to the base value, and resets the base value to
* zero. The final output of the value is unchanged.
*/
extractOffset(): void {
this.r.extractOffset();
this.g.extractOffset();
this.b.extractOffset();
this.a.extractOffset();
}
/**
* Stops any running animation or tracking. `callback` is invoked with the
* final value after stopping the animation, which is useful for updating
* state to match the animation position with layout.
*/
stopAnimation(callback?: ColorListenerCallback): void {
this.r.stopAnimation();
this.g.stopAnimation();
this.b.stopAnimation();
this.a.stopAnimation();
callback && callback(this.__getValue());
}
/**
* Stops any animation and resets the value to its original.
*/
resetAnimation(callback?: ColorListenerCallback): void {
this.r.resetAnimation();
this.g.resetAnimation();
this.b.resetAnimation();
this.a.resetAnimation();
callback && callback(this.__getValue());
}
__getValue(): ColorValue {
if (this.nativeColor != null) {
return this.nativeColor;
} else {
return `rgba(${this.r.__getValue()}, ${this.g.__getValue()}, ${this.b.__getValue()}, ${this.a.__getValue()})`;
}
}
__attach(): void {
this.r.__addChild(this);
this.g.__addChild(this);
this.b.__addChild(this);
this.a.__addChild(this);
super.__attach();
}
__detach(): void {
this.r.__removeChild(this);
this.g.__removeChild(this);
this.b.__removeChild(this);
this.a.__removeChild(this);
super.__detach();
}
_withSuspendedCallbacks(callback: () => void) {
this._suspendCallbacks++;
callback();
this._suspendCallbacks--;
}
__callListeners(value: number): void {
if (this._suspendCallbacks === 0) {
super.__callListeners(value);
}
}
__makeNative(platformConfig: ?PlatformConfig) {
this.r.__makeNative(platformConfig);
this.g.__makeNative(platformConfig);
this.b.__makeNative(platformConfig);
this.a.__makeNative(platformConfig);
super.__makeNative(platformConfig);
}
__getNativeConfig(): {...} {
return {
type: 'color',
r: this.r.__getNativeTag(),
g: this.g.__getNativeTag(),
b: this.b.__getNativeTag(),
a: this.a.__getNativeTag(),
nativeColor: this.nativeColor,
debugID: this.__getDebugID(),
};
}
}

View File

@@ -0,0 +1,80 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {InterpolationConfigType} from './AnimatedInterpolation';
import type AnimatedNode from './AnimatedNode';
import type {AnimatedNodeConfig} from './AnimatedNode';
import AnimatedInterpolation from './AnimatedInterpolation';
import AnimatedWithChildren from './AnimatedWithChildren';
export default class AnimatedDiffClamp extends AnimatedWithChildren {
_a: AnimatedNode;
_min: number;
_max: number;
_value: number;
_lastValue: number;
constructor(
a: AnimatedNode,
min: number,
max: number,
config?: ?AnimatedNodeConfig,
) {
super(config);
this._a = a;
this._min = min;
this._max = max;
this._value = this._lastValue = this._a.__getValue();
}
__makeNative(platformConfig: ?PlatformConfig) {
this._a.__makeNative(platformConfig);
super.__makeNative(platformConfig);
}
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}
__getValue(): number {
const value = this._a.__getValue();
const diff = value - this._lastValue;
this._lastValue = value;
this._value = Math.min(Math.max(this._value + diff, this._min), this._max);
return this._value;
}
__attach(): void {
this._a.__addChild(this);
super.__attach();
}
__detach(): void {
this._a.__removeChild(this);
super.__detach();
}
__getNativeConfig(): any {
return {
type: 'diffclamp',
input: this._a.__getNativeTag(),
min: this._min,
max: this._max,
debugID: this.__getDebugID(),
};
}
}

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {InterpolationConfigType} from './AnimatedInterpolation';
import type {AnimatedNodeConfig} from './AnimatedNode';
import AnimatedInterpolation from './AnimatedInterpolation';
import AnimatedNode from './AnimatedNode';
import AnimatedValue from './AnimatedValue';
import AnimatedWithChildren from './AnimatedWithChildren';
export default class AnimatedDivision extends AnimatedWithChildren {
_a: AnimatedNode;
_b: AnimatedNode;
_warnedAboutDivideByZero: boolean = false;
constructor(
a: AnimatedNode | number,
b: AnimatedNode | number,
config?: ?AnimatedNodeConfig,
) {
super(config);
if (b === 0 || (b instanceof AnimatedNode && b.__getValue() === 0)) {
console.error('Detected potential division by zero in AnimatedDivision');
}
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
}
__makeNative(platformConfig: ?PlatformConfig) {
this._a.__makeNative(platformConfig);
this._b.__makeNative(platformConfig);
super.__makeNative(platformConfig);
}
__getValue(): number {
const a = this._a.__getValue();
const b = this._b.__getValue();
if (b === 0) {
// Prevent spamming the console/LogBox
if (!this._warnedAboutDivideByZero) {
console.error('Detected division by zero in AnimatedDivision');
this._warnedAboutDivideByZero = true;
}
// Passing infinity/NaN to Fabric will cause a native crash
return 0;
}
this._warnedAboutDivideByZero = false;
return a / b;
}
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}
__attach(): void {
this._a.__addChild(this);
this._b.__addChild(this);
super.__attach();
}
__detach(): void {
this._a.__removeChild(this);
this._b.__removeChild(this);
super.__detach();
}
__getNativeConfig(): any {
return {
type: 'division',
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
debugID: this.__getDebugID(),
};
}
}

View File

@@ -0,0 +1,420 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
/* eslint no-bitwise: 0 */
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type AnimatedNode from './AnimatedNode';
import type {AnimatedNodeConfig} from './AnimatedNode';
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import {validateInterpolation} from '../../../src/private/animated/NativeAnimatedValidation';
import normalizeColor from '../../StyleSheet/normalizeColor';
import processColor from '../../StyleSheet/processColor';
import Easing from '../Easing';
import AnimatedWithChildren from './AnimatedWithChildren';
import invariant from 'invariant';
type ExtrapolateType = 'extend' | 'identity' | 'clamp';
export type InterpolationConfigType<OutputT: number | string> = $ReadOnly<{
...AnimatedNodeConfig,
inputRange: $ReadOnlyArray<number>,
outputRange: $ReadOnlyArray<OutputT>,
easing?: (input: number) => number,
extrapolate?: ExtrapolateType,
extrapolateLeft?: ExtrapolateType,
extrapolateRight?: ExtrapolateType,
}>;
/**
* Very handy helper to map input ranges to output ranges with an easing
* function and custom behavior outside of the ranges.
*/
function createNumericInterpolation(
config: InterpolationConfigType<number>,
): (input: number) => number {
const outputRange: $ReadOnlyArray<number> = (config.outputRange: any);
const inputRange = config.inputRange;
const easing = config.easing || Easing.linear;
let extrapolateLeft: ExtrapolateType = 'extend';
if (config.extrapolateLeft !== undefined) {
extrapolateLeft = config.extrapolateLeft;
} else if (config.extrapolate !== undefined) {
extrapolateLeft = config.extrapolate;
}
let extrapolateRight: ExtrapolateType = 'extend';
if (config.extrapolateRight !== undefined) {
extrapolateRight = config.extrapolateRight;
} else if (config.extrapolate !== undefined) {
extrapolateRight = config.extrapolate;
}
return input => {
invariant(
typeof input === 'number',
'Cannot interpolation an input which is not a number',
);
const range = findRange(input, inputRange);
return (interpolate(
input,
inputRange[range],
inputRange[range + 1],
outputRange[range],
outputRange[range + 1],
easing,
extrapolateLeft,
extrapolateRight,
): any);
};
}
function interpolate(
input: number,
inputMin: number,
inputMax: number,
outputMin: number,
outputMax: number,
easing: (input: number) => number,
extrapolateLeft: ExtrapolateType,
extrapolateRight: ExtrapolateType,
) {
let result = input;
// Extrapolate
if (result < inputMin) {
if (extrapolateLeft === 'identity') {
return result;
} else if (extrapolateLeft === 'clamp') {
result = inputMin;
} else if (extrapolateLeft === 'extend') {
// noop
}
}
if (result > inputMax) {
if (extrapolateRight === 'identity') {
return result;
} else if (extrapolateRight === 'clamp') {
result = inputMax;
} else if (extrapolateRight === 'extend') {
// noop
}
}
if (outputMin === outputMax) {
return outputMin;
}
if (inputMin === inputMax) {
if (input <= inputMin) {
return outputMin;
}
return outputMax;
}
// Input Range
if (inputMin === -Infinity) {
result = -result;
} else if (inputMax === Infinity) {
result = result - inputMin;
} else {
result = (result - inputMin) / (inputMax - inputMin);
}
// Easing
result = easing(result);
// Output Range
if (outputMin === -Infinity) {
result = -result;
} else if (outputMax === Infinity) {
result = result + outputMin;
} else {
result = result * (outputMax - outputMin) + outputMin;
}
return result;
}
const numericComponentRegex = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/g;
// Maps string inputs an RGBA color or an array of numeric components
function mapStringToNumericComponents(
input: string,
):
| {isColor: true, components: [number, number, number, number]}
| {isColor: false, components: $ReadOnlyArray<number | string>} {
let normalizedColor = normalizeColor(input);
invariant(
normalizedColor == null || typeof normalizedColor !== 'object',
'PlatformColors are not supported',
);
if (typeof normalizedColor === 'number') {
normalizedColor = normalizedColor || 0;
const r = (normalizedColor & 0xff000000) >>> 24;
const g = (normalizedColor & 0x00ff0000) >>> 16;
const b = (normalizedColor & 0x0000ff00) >>> 8;
const a = (normalizedColor & 0x000000ff) / 255;
return {isColor: true, components: [r, g, b, a]};
} else {
const components: Array<string | number> = [];
let lastMatchEnd = 0;
let match: RegExp$matchResult;
while ((match = (numericComponentRegex.exec(input): any)) != null) {
if (match.index > lastMatchEnd) {
components.push(input.substring(lastMatchEnd, match.index));
}
components.push(parseFloat(match[0]));
lastMatchEnd = match.index + match[0].length;
}
invariant(
components.length > 0,
'outputRange must contain color or value with numeric component',
);
if (lastMatchEnd < input.length) {
components.push(input.substring(lastMatchEnd, input.length));
}
return {isColor: false, components};
}
}
/**
* Supports string shapes by extracting numbers so new values can be computed,
* and recombines those values into new strings of the same shape. Supports
* things like:
*
* rgba(123, 42, 99, 0.36) // colors
* -45deg // values with units
*/
function createStringInterpolation(
config: InterpolationConfigType<string>,
): (input: number) => string {
invariant(config.outputRange.length >= 2, 'Bad output range');
const outputRange = config.outputRange.map(mapStringToNumericComponents);
const isColor = outputRange[0].isColor;
if (__DEV__) {
invariant(
outputRange.every(output => output.isColor === isColor),
'All elements of output range should either be a color or a string with numeric components',
);
const firstOutput = outputRange[0].components;
invariant(
outputRange.every(
output => output.components.length === firstOutput.length,
),
'All elements of output range should have the same number of components',
);
invariant(
outputRange.every(output =>
output.components.every(
(component, i) =>
// $FlowFixMe[invalid-compare]
typeof component === 'number' || component === firstOutput[i],
),
),
'All elements of output range should have the same non-numeric components',
);
}
const numericComponents: $ReadOnlyArray<$ReadOnlyArray<number>> =
outputRange.map(output =>
isColor
? // $FlowFixMe[incompatible-type]
output.components
: // $FlowFixMe[incompatible-call]
output.components.filter(c => typeof c === 'number'),
);
const interpolations = numericComponents[0].map((_, i) =>
createNumericInterpolation({
...config,
outputRange: numericComponents.map(components => components[i]),
}),
);
if (!isColor) {
return input => {
const values = interpolations.map(interpolation => interpolation(input));
let i = 0;
return outputRange[0].components
.map(c => (typeof c === 'number' ? values[i++] : c))
.join('');
};
} else {
return input => {
const result = interpolations.map((interpolation, i) => {
const value = interpolation(input);
// rgba requires that the r,g,b are integers.... so we want to round them, but we *dont* want to
// round the opacity (4th column).
return i < 3 ? Math.round(value) : Math.round(value * 1000) / 1000;
});
return `rgba(${result[0]}, ${result[1]}, ${result[2]}, ${result[3]})`;
};
}
}
function findRange(input: number, inputRange: $ReadOnlyArray<number>) {
let i;
for (i = 1; i < inputRange.length - 1; ++i) {
if (inputRange[i] >= input) {
break;
}
}
return i - 1;
}
function checkValidRanges<OutputT: number | string>(
inputRange: $ReadOnlyArray<number>,
outputRange: $ReadOnlyArray<OutputT>,
) {
checkInfiniteRange('outputRange', outputRange);
checkInfiniteRange('inputRange', inputRange);
checkValidInputRange(inputRange);
invariant(
inputRange.length === outputRange.length,
'inputRange (' +
inputRange.length +
') and outputRange (' +
outputRange.length +
') must have the same length',
);
}
function checkValidInputRange(arr: $ReadOnlyArray<number>) {
invariant(arr.length >= 2, 'inputRange must have at least 2 elements');
const message =
'inputRange must be monotonically non-decreasing ' + String(arr);
for (let i = 1; i < arr.length; ++i) {
invariant(arr[i] >= arr[i - 1], message);
}
}
function checkInfiniteRange<OutputT: number | string>(
name: string,
arr: $ReadOnlyArray<OutputT>,
) {
invariant(arr.length >= 2, name + ' must have at least 2 elements');
invariant(
arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity,
/* $FlowFixMe[incompatible-type] (>=0.13.0) - In the addition expression
* below this comment, one or both of the operands may be something that
* doesn't cleanly convert to a string, like undefined, null, and object,
* etc. If you really mean this implicit string conversion, you can do
* something like String(myThing) */
// $FlowFixMe[unsafe-addition]
name + 'cannot be ]-infinity;+infinity[ ' + arr,
);
}
export default class AnimatedInterpolation<
OutputT: number | string,
> extends AnimatedWithChildren {
_parent: AnimatedNode;
_config: InterpolationConfigType<OutputT>;
_interpolation: ?(input: number) => OutputT;
constructor(parent: AnimatedNode, config: InterpolationConfigType<OutputT>) {
super(config);
this._parent = parent;
this._config = config;
if (__DEV__) {
checkValidRanges(config.inputRange, config.outputRange);
// Create interpolation eagerly in dev, so we can signal errors faster
// even when using the native driver
this._getInterpolation();
}
}
_getInterpolation(): number => OutputT {
if (!this._interpolation) {
const config = this._config;
if (config.outputRange && typeof config.outputRange[0] === 'string') {
this._interpolation = (createStringInterpolation((config: any)): any);
} else {
this._interpolation = (createNumericInterpolation((config: any)): any);
}
}
return this._interpolation;
}
__makeNative(platformConfig: ?PlatformConfig) {
this._parent.__makeNative(platformConfig);
super.__makeNative(platformConfig);
}
__getValue(): OutputT {
const parentValue: number = this._parent.__getValue();
invariant(
typeof parentValue === 'number',
'Cannot interpolate an input which is not a number.',
);
return this._getInterpolation()(parentValue);
}
interpolate<NewOutputT: number | string>(
config: InterpolationConfigType<NewOutputT>,
): AnimatedInterpolation<NewOutputT> {
return new AnimatedInterpolation(this, config);
}
__attach(): void {
this._parent.__addChild(this);
super.__attach();
}
__detach(): void {
this._parent.__removeChild(this);
super.__detach();
}
__getNativeConfig(): any {
if (__DEV__) {
validateInterpolation(this._config);
}
// Only the `outputRange` can contain strings so we don't need to transform `inputRange` here
let outputRange = this._config.outputRange;
let outputType = null;
if (typeof outputRange[0] === 'string') {
// $FlowFixMe[incompatible-type]
outputRange = ((outputRange: $ReadOnlyArray<string>).map(value => {
const processedColor = processColor(value);
if (typeof processedColor === 'number') {
outputType = 'color';
return processedColor;
} else {
return NativeAnimatedHelper.transformDataType(value);
}
}): any);
}
return {
inputRange: this._config.inputRange,
outputRange,
outputType,
extrapolateLeft:
this._config.extrapolateLeft || this._config.extrapolate || 'extend',
extrapolateRight:
this._config.extrapolateRight || this._config.extrapolate || 'extend',
type: 'interpolation',
debugID: this.__getDebugID(),
};
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {InterpolationConfigType} from './AnimatedInterpolation';
import type AnimatedNode from './AnimatedNode';
import type {AnimatedNodeConfig} from './AnimatedNode';
import AnimatedInterpolation from './AnimatedInterpolation';
import AnimatedWithChildren from './AnimatedWithChildren';
export default class AnimatedModulo extends AnimatedWithChildren {
_a: AnimatedNode;
_modulus: number;
constructor(a: AnimatedNode, modulus: number, config?: ?AnimatedNodeConfig) {
super(config);
this._a = a;
this._modulus = modulus;
}
__makeNative(platformConfig: ?PlatformConfig) {
this._a.__makeNative(platformConfig);
super.__makeNative(platformConfig);
}
__getValue(): number {
return (
((this._a.__getValue() % this._modulus) + this._modulus) % this._modulus
);
}
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}
__attach(): void {
this._a.__addChild(this);
super.__attach();
}
__detach(): void {
this._a.__removeChild(this);
super.__detach();
}
__getNativeConfig(): any {
return {
type: 'modulus',
input: this._a.__getNativeTag(),
modulus: this._modulus,
debugID: this.__getDebugID(),
};
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {InterpolationConfigType} from './AnimatedInterpolation';
import type AnimatedNode from './AnimatedNode';
import type {AnimatedNodeConfig} from './AnimatedNode';
import AnimatedInterpolation from './AnimatedInterpolation';
import AnimatedValue from './AnimatedValue';
import AnimatedWithChildren from './AnimatedWithChildren';
export default class AnimatedMultiplication extends AnimatedWithChildren {
_a: AnimatedNode;
_b: AnimatedNode;
constructor(
a: AnimatedNode | number,
b: AnimatedNode | number,
config?: ?AnimatedNodeConfig,
) {
super(config);
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
}
__makeNative(platformConfig: ?PlatformConfig) {
this._a.__makeNative(platformConfig);
this._b.__makeNative(platformConfig);
super.__makeNative(platformConfig);
}
__getValue(): number {
return this._a.__getValue() * this._b.__getValue();
}
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}
__attach(): void {
this._a.__addChild(this);
this._b.__addChild(this);
super.__attach();
}
__detach(): void {
this._a.__removeChild(this);
this._b.__removeChild(this);
super.__detach();
}
__getNativeConfig(): any {
return {
type: 'multiplication',
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
debugID: this.__getDebugID(),
};
}
}

View File

@@ -0,0 +1,187 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import invariant from 'invariant';
type ValueListenerCallback = (state: {value: number, ...}) => mixed;
export type AnimatedNodeConfig = $ReadOnly<{
debugID?: string,
unstable_disableBatchingForNativeCreate?: boolean,
}>;
let _uniqueId = 1;
let _assertNativeAnimatedModule: ?() => void = () => {
NativeAnimatedHelper.assertNativeAnimatedModule();
// We only have to assert that the module exists once. After we've asserted
// this, clear out the function so we know to skip it in the future.
_assertNativeAnimatedModule = null;
};
export default class AnimatedNode {
_listeners: Map<string, ValueListenerCallback>;
_platformConfig: ?PlatformConfig = undefined;
constructor(
config?: ?$ReadOnly<{
...AnimatedNodeConfig,
...
}>,
) {
this._listeners = new Map();
if (__DEV__) {
this.__debugID = config?.debugID;
}
this.__disableBatchingForNativeCreate =
config?.unstable_disableBatchingForNativeCreate;
}
__attach(): void {}
__detach(): void {
this.removeAllListeners();
if (this.__isNative && this.__nativeTag != null) {
NativeAnimatedHelper.API.dropAnimatedNode(this.__nativeTag);
this.__nativeTag = undefined;
}
}
__getValue(): any {}
__getAnimatedValue(): any {
return this.__getValue();
}
__addChild(child: AnimatedNode) {}
__removeChild(child: AnimatedNode) {}
__getChildren(): $ReadOnlyArray<AnimatedNode> {
return [];
}
/* Methods and props used by native Animated impl */
__isNative: boolean = false;
__nativeTag: ?number = undefined;
__disableBatchingForNativeCreate: ?boolean = undefined;
__makeNative(platformConfig: ?PlatformConfig): void {
// Subclasses are expected to set `__isNative` to true before this.
invariant(
this.__isNative,
'This node cannot be made a "native" animated node',
);
this._platformConfig = platformConfig;
}
/**
* Adds an asynchronous listener to the value so you can observe updates from
* animations. This is useful because there is no way to
* synchronously read the value because it might be driven natively.
*
* See https://reactnative.dev/docs/animatedvalue#addlistener
*/
addListener(callback: (value: any) => mixed): string {
const id = String(_uniqueId++);
this._listeners.set(id, callback);
return id;
}
/**
* Unregister a listener. The `id` param shall match the identifier
* previously returned by `addListener()`.
*
* See https://reactnative.dev/docs/animatedvalue#removelistener
*/
removeListener(id: string): void {
this._listeners.delete(id);
}
/**
* Remove all registered listeners.
*
* See https://reactnative.dev/docs/animatedvalue#removealllisteners
*/
removeAllListeners(): void {
this._listeners.clear();
}
hasListeners(): boolean {
return this._listeners.size > 0;
}
__onAnimatedValueUpdateReceived(value: number, offset: number): void {
this.__callListeners(value + offset);
}
__callListeners(value: number): void {
const event = {value};
this._listeners.forEach(listener => {
listener(event);
});
}
__getNativeTag(): number {
let nativeTag = this.__nativeTag;
if (nativeTag == null) {
_assertNativeAnimatedModule?.();
// `__isNative` is initialized as false and only ever set to true. So we
// only need to check it once here when initializing `__nativeTag`.
invariant(
this.__isNative,
'Attempt to get native tag from node not marked as "native"',
);
nativeTag = NativeAnimatedHelper.generateNewNodeTag();
this.__nativeTag = nativeTag;
const config = this.__getNativeConfig();
if (this._platformConfig) {
config.platformConfig = this._platformConfig;
}
if (this.__disableBatchingForNativeCreate) {
config.disableBatchingForNativeCreate = true;
}
NativeAnimatedHelper.API.createAnimatedNode(nativeTag, config);
}
return nativeTag;
}
__getNativeConfig(): Object {
throw new Error(
'This JS animated node type cannot be used as native animated node',
);
}
__getPlatformConfig(): ?PlatformConfig {
return this._platformConfig;
}
__setPlatformConfig(platformConfig: ?PlatformConfig) {
this._platformConfig = platformConfig;
}
/**
* NOTE: This is intended to prevent `JSON.stringify` from throwing "cyclic
* structure" errors in React DevTools. Avoid depending on this!
*/
toJSON(): mixed {
return this.__getValue();
}
__debugID: ?string = undefined;
__getDebugID(): ?string {
if (__DEV__) {
return this.__debugID;
}
return undefined;
}
}

View File

@@ -0,0 +1,169 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {AnimatedNodeConfig} from './AnimatedNode';
import AnimatedNode from './AnimatedNode';
import AnimatedWithChildren from './AnimatedWithChildren';
import {isValidElement} from 'react';
const MAX_DEPTH = 5;
export function isPlainObject(
value: mixed,
/* $FlowFixMe[incompatible-type-guard] - Flow does not know that the prototype
and ReactElement checks preserve the type refinement of `value`. */
): value is $ReadOnly<{[string]: mixed}> {
return (
// $FlowFixMe[incompatible-type-guard]
value !== null &&
typeof value === 'object' &&
Object.getPrototypeOf(value).isPrototypeOf(Object) &&
!isValidElement(value)
);
}
function flatAnimatedNodes(
value: mixed,
nodes: Array<AnimatedNode> = [],
depth: number = 0,
): Array<AnimatedNode> {
if (depth >= MAX_DEPTH) {
return nodes;
}
if (value instanceof AnimatedNode) {
nodes.push(value);
} else if (Array.isArray(value)) {
for (let ii = 0, length = value.length; ii < length; ii++) {
const element = value[ii];
flatAnimatedNodes(element, nodes, depth + 1);
}
} else if (isPlainObject(value)) {
const keys = Object.keys(value);
for (let ii = 0, length = keys.length; ii < length; ii++) {
const key = keys[ii];
flatAnimatedNodes(value[key], nodes, depth + 1);
}
}
return nodes;
}
// Returns a copy of value with a transformation fn applied to any AnimatedNodes
function mapAnimatedNodes(value: any, fn: any => any, depth: number = 0): any {
if (depth >= MAX_DEPTH) {
return value;
}
if (value instanceof AnimatedNode) {
return fn(value);
} else if (Array.isArray(value)) {
return value.map(element => mapAnimatedNodes(element, fn, depth + 1));
} else if (isPlainObject(value)) {
const result: {[string]: any} = {};
const keys = Object.keys(value);
for (let ii = 0, length = keys.length; ii < length; ii++) {
const key = keys[ii];
result[key] = mapAnimatedNodes(value[key], fn, depth + 1);
}
return result;
} else {
return value;
}
}
export default class AnimatedObject extends AnimatedWithChildren {
_nodes: $ReadOnlyArray<AnimatedNode>;
_value: mixed;
/**
* Creates an `AnimatedObject` if `value` contains `AnimatedNode` instances.
* Otherwise, returns `null`.
*/
static from(value: mixed): ?AnimatedObject {
const nodes = flatAnimatedNodes(value);
if (nodes.length === 0) {
return null;
}
return new AnimatedObject(nodes, value);
}
/**
* Should only be called by `AnimatedObject.from`.
*/
constructor(
nodes: $ReadOnlyArray<AnimatedNode>,
value: mixed,
config?: ?AnimatedNodeConfig,
) {
super(config);
this._nodes = nodes;
this._value = value;
}
__getValue(): any {
return mapAnimatedNodes(this._value, node => {
return node.__getValue();
});
}
__getValueWithStaticObject(staticObject: mixed): any {
const nodes = this._nodes;
let index = 0;
// NOTE: We can depend on `this._value` and `staticObject` sharing a
// structure because of `useAnimatedPropsMemo`.
return mapAnimatedNodes(staticObject, () => nodes[index++].__getValue());
}
__getAnimatedValue(): any {
return mapAnimatedNodes(this._value, node => {
return node.__getAnimatedValue();
});
}
__attach(): void {
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const node = nodes[ii];
node.__addChild(this);
}
super.__attach();
}
__detach(): void {
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const node = nodes[ii];
node.__removeChild(this);
}
super.__detach();
}
__makeNative(platformConfig: ?PlatformConfig): void {
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const node = nodes[ii];
node.__makeNative(platformConfig);
}
super.__makeNative(platformConfig);
}
__getNativeConfig(): any {
return {
type: 'object',
value: mapAnimatedNodes(this._value, node => {
return {nodeTag: node.__getNativeTag()};
}),
debugID: this.__getDebugID(),
};
}
}

View File

@@ -0,0 +1,332 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {AnimatedNodeConfig} from './AnimatedNode';
import type {AnimatedStyleAllowlist} from './AnimatedStyle';
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import {findNodeHandle} from '../../ReactNative/RendererProxy';
import flattenStyle from '../../StyleSheet/flattenStyle';
import {AnimatedEvent} from '../AnimatedEvent';
import AnimatedNode from './AnimatedNode';
import AnimatedObject from './AnimatedObject';
import AnimatedStyle from './AnimatedStyle';
import invariant from 'invariant';
export type AnimatedPropsAllowlist = $ReadOnly<{
style?: ?AnimatedStyleAllowlist,
[key: string]: true | AnimatedStyleAllowlist,
}>;
type TargetView = {
+instance: TargetViewInstance,
connectedViewTag: ?number,
};
type TargetViewInstance = React.ElementRef<React.ElementType>;
function createAnimatedProps(
inputProps: {[string]: mixed},
allowlist: ?AnimatedPropsAllowlist,
): [$ReadOnlyArray<string>, $ReadOnlyArray<AnimatedNode>, {[string]: mixed}] {
const nodeKeys: Array<string> = [];
const nodes: Array<AnimatedNode> = [];
const props: {[string]: mixed} = {};
const keys = Object.keys(inputProps);
for (let ii = 0, length = keys.length; ii < length; ii++) {
const key = keys[ii];
const value = inputProps[key];
let staticValue = value;
if (allowlist == null || hasOwn(allowlist, key)) {
let node;
if (key === 'style') {
// Ignore `style` if it is not an object (or array).
if (typeof value === 'object' && value != null) {
// Even if we do not find any `AnimatedNode` values in `style`, we
// still need to use the flattened `style` object because static
// values can shadow `AnimatedNode` values. We need to make sure that
// we propagate the flattened `style` object to the `props` object.
const flatStyle = flattenStyle(value as $FlowFixMe);
node = AnimatedStyle.from(flatStyle, allowlist?.style, value);
staticValue = flatStyle;
}
} else if (value instanceof AnimatedNode) {
node = value;
} else {
node = AnimatedObject.from(value);
}
if (node == null) {
props[key] = staticValue;
} else {
nodeKeys.push(key);
nodes.push(node);
props[key] = node;
}
} else {
if (__DEV__) {
// WARNING: This is a potentially expensive check that we should only
// do in development. Without this check in development, it might be
// difficult to identify which props need to be allowlisted.
if (AnimatedObject.from(inputProps[key]) != null) {
console.error(
`AnimatedProps: ${key} is not allowlisted for animation, but it ` +
'contains AnimatedNode values; props allowing animation: ',
allowlist,
);
}
}
props[key] = value;
}
}
return [nodeKeys, nodes, props];
}
export default class AnimatedProps extends AnimatedNode {
_callback: () => void;
_nodeKeys: $ReadOnlyArray<string>;
_nodes: $ReadOnlyArray<AnimatedNode>;
_props: {[string]: mixed};
_target: ?TargetView = null;
constructor(
inputProps: {[string]: mixed},
callback: () => void,
allowlist?: ?AnimatedPropsAllowlist,
config?: ?AnimatedNodeConfig,
) {
super(config);
const [nodeKeys, nodes, props] = createAnimatedProps(inputProps, allowlist);
this._nodeKeys = nodeKeys;
this._nodes = nodes;
this._props = props;
this._callback = callback;
}
__getValue(): Object {
const props: {[string]: mixed} = {};
const keys = Object.keys(this._props);
for (let ii = 0, length = keys.length; ii < length; ii++) {
const key = keys[ii];
const value = this._props[key];
if (value instanceof AnimatedNode) {
props[key] = value.__getValue();
} else if (value instanceof AnimatedEvent) {
props[key] = value.__getHandler();
} else {
props[key] = value;
}
}
return props;
}
/**
* Creates a new `props` object that contains the same props as the supplied
* `staticProps` object, except with animated nodes for any props that were
* created by this `AnimatedProps` instance.
*/
__getValueWithStaticProps(staticProps: Object): Object {
const props: {[string]: mixed} = {...staticProps};
const keys = Object.keys(staticProps);
for (let ii = 0, length = keys.length; ii < length; ii++) {
const key = keys[ii];
const maybeNode = this._props[key];
if (key === 'style') {
const staticStyle = staticProps.style;
const flatStaticStyle = flattenStyle(staticStyle);
if (maybeNode instanceof AnimatedStyle) {
const mutableStyle: {[string]: mixed} =
flatStaticStyle == null
? {}
: flatStaticStyle === staticStyle
? // Copy the input style, since we'll mutate it below.
{...flatStaticStyle}
: // Reuse `flatStaticStyle` if it is a newly created object.
flatStaticStyle;
maybeNode.__replaceAnimatedNodeWithValues(mutableStyle);
props[key] = maybeNode.__getValueForStyle(mutableStyle);
} else {
props[key] = flatStaticStyle;
}
} else if (maybeNode instanceof AnimatedNode) {
props[key] = maybeNode.__getValue();
} else if (maybeNode instanceof AnimatedEvent) {
props[key] = maybeNode.__getHandler();
}
}
return props;
}
__getNativeAnimatedEventTuples(): $ReadOnlyArray<[string, AnimatedEvent]> {
const tuples = [];
const keys = Object.keys(this._props);
for (let ii = 0, length = keys.length; ii < length; ii++) {
const key = keys[ii];
const value = this._props[key];
if (value instanceof AnimatedEvent && value.__isNative) {
tuples.push([key, value]);
}
}
return tuples;
}
__getAnimatedValue(): Object {
const props: {[string]: mixed} = {};
const nodeKeys = this._nodeKeys;
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const key = nodeKeys[ii];
const node = nodes[ii];
props[key] = node.__getAnimatedValue();
}
return props;
}
__attach(): void {
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const node = nodes[ii];
node.__addChild(this);
}
super.__attach();
}
__detach(): void {
if (this.__isNative && this._target != null) {
this.#disconnectAnimatedView(this._target);
}
this._target = null;
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const node = nodes[ii];
node.__removeChild(this);
}
super.__detach();
}
update(): void {
this._callback();
}
__makeNative(platformConfig: ?PlatformConfig): void {
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const node = nodes[ii];
node.__makeNative(platformConfig);
}
if (!this.__isNative) {
this.__isNative = true;
// Since this does not call the super.__makeNative, we need to store the
// supplied platformConfig here, before calling #connectAnimatedView
// where it will be needed to traverse the graph of attached values.
super.__setPlatformConfig(platformConfig);
if (this._target != null) {
this.#connectAnimatedView(this._target);
}
}
}
setNativeView(instance: TargetViewInstance): void {
if (this._target?.instance === instance) {
return;
}
this._target = {instance, connectedViewTag: null};
if (this.__isNative) {
this.#connectAnimatedView(this._target);
}
}
#connectAnimatedView(target: TargetView): void {
invariant(this.__isNative, 'Expected node to be marked as "native"');
let viewTag: ?number = findNodeHandle(target.instance);
if (viewTag == null) {
if (process.env.NODE_ENV === 'test') {
viewTag = -1;
} else {
throw new Error('Unable to locate attached view in the native tree');
}
}
NativeAnimatedHelper.API.connectAnimatedNodeToView(
this.__getNativeTag(),
viewTag,
);
target.connectedViewTag = viewTag;
}
#disconnectAnimatedView(target: TargetView): void {
invariant(this.__isNative, 'Expected node to be marked as "native"');
const viewTag = target.connectedViewTag;
if (viewTag == null) {
return;
}
NativeAnimatedHelper.API.disconnectAnimatedNodeFromView(
this.__getNativeTag(),
viewTag,
);
target.connectedViewTag = null;
}
__restoreDefaultValues(): void {
// When using the native driver, view properties need to be restored to
// their default values manually since react no longer tracks them. This
// is needed to handle cases where a prop driven by native animated is removed
// after having been changed natively by an animation.
if (this.__isNative) {
NativeAnimatedHelper.API.restoreDefaultValues(this.__getNativeTag());
}
}
__getNativeConfig(): Object {
const platformConfig = this.__getPlatformConfig();
const propsConfig: {[string]: number} = {};
const nodeKeys = this._nodeKeys;
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const key = nodeKeys[ii];
const node = nodes[ii];
node.__makeNative(platformConfig);
propsConfig[key] = node.__getNativeTag();
}
return {
type: 'props',
props: propsConfig,
debugID: this.__getDebugID(),
};
}
}
// Supported versions of JSC do not implement the newer Object.hasOwn. Remove
// this shim when they do.
// $FlowFixMe[method-unbinding]
const _hasOwnProp = Object.prototype.hasOwnProperty;
const hasOwn: (obj: $ReadOnly<{...}>, prop: string) => boolean =
// $FlowFixMe[method-unbinding]
Object.hasOwn ?? ((obj, prop) => _hasOwnProp.call(obj, prop));

View File

@@ -0,0 +1,258 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {AnimatedNodeConfig} from './AnimatedNode';
import {validateStyles} from '../../../src/private/animated/NativeAnimatedValidation';
import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags';
import Platform from '../../Utilities/Platform';
import AnimatedNode from './AnimatedNode';
import AnimatedObject from './AnimatedObject';
import AnimatedTransform from './AnimatedTransform';
import AnimatedWithChildren from './AnimatedWithChildren';
export type AnimatedStyleAllowlist = $ReadOnly<{[string]: true}>;
type FlatStyle = {[string]: mixed};
type FlatStyleForWeb<TStyle: FlatStyle> = [mixed, TStyle];
function createAnimatedStyle(
flatStyle: FlatStyle,
allowlist: ?AnimatedStyleAllowlist,
keepUnanimatedValues: boolean,
): [$ReadOnlyArray<string>, $ReadOnlyArray<AnimatedNode>, {[string]: mixed}] {
const nodeKeys: Array<string> = [];
const nodes: Array<AnimatedNode> = [];
const style: {[string]: mixed} = {};
const keys = Object.keys(flatStyle);
for (let ii = 0, length = keys.length; ii < length; ii++) {
const key = keys[ii];
const value = flatStyle[key];
if (allowlist == null || hasOwn(allowlist, key)) {
let node;
if (value != null && key === 'transform') {
node = ReactNativeFeatureFlags.shouldUseAnimatedObjectForTransform()
? AnimatedObject.from(value)
: // $FlowFixMe[incompatible-type] - `value` is mixed.
AnimatedTransform.from(value);
} else if (value instanceof AnimatedNode) {
node = value;
} else {
node = AnimatedObject.from(value);
}
if (node == null) {
if (keepUnanimatedValues) {
style[key] = value;
}
} else {
nodeKeys.push(key);
nodes.push(node);
style[key] = node;
}
} else {
if (__DEV__) {
// WARNING: This is a potentially expensive check that we should only
// do in development. Without this check in development, it might be
// difficult to identify which styles need to be allowlisted.
if (AnimatedObject.from(flatStyle[key]) != null) {
console.error(
`AnimatedStyle: ${key} is not allowlisted for animation, but ` +
'it contains AnimatedNode values; styles allowing animation: ',
allowlist,
);
}
}
if (keepUnanimatedValues) {
style[key] = value;
}
}
}
return [nodeKeys, nodes, style];
}
export default class AnimatedStyle extends AnimatedWithChildren {
_originalStyleForWeb: ?mixed;
_nodeKeys: $ReadOnlyArray<string>;
_nodes: $ReadOnlyArray<AnimatedNode>;
_style: {[string]: mixed};
/**
* Creates an `AnimatedStyle` if `value` contains `AnimatedNode` instances.
* Otherwise, returns `null`.
*/
static from(
flatStyle: ?FlatStyle,
allowlist: ?AnimatedStyleAllowlist,
originalStyleForWeb: ?mixed,
): ?AnimatedStyle {
if (flatStyle == null) {
return null;
}
const [nodeKeys, nodes, style] = createAnimatedStyle(
flatStyle,
allowlist,
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/4oq3zi07. */
Platform.OS !== 'web',
);
if (nodes.length === 0) {
return null;
}
return new AnimatedStyle(nodeKeys, nodes, style, originalStyleForWeb);
}
constructor(
nodeKeys: $ReadOnlyArray<string>,
nodes: $ReadOnlyArray<AnimatedNode>,
style: {[string]: mixed},
originalStyleForWeb: ?mixed,
config?: ?AnimatedNodeConfig,
) {
super(config);
this._nodeKeys = nodeKeys;
this._nodes = nodes;
this._style = style;
if ((Platform.OS as string) === 'web') {
// $FlowFixMe[cannot-write] - Intentional shadowing.
this.__getValueForStyle = resultStyle => [
originalStyleForWeb,
resultStyle,
];
}
}
__getValue(): FlatStyleForWeb<FlatStyle> | FlatStyle {
const style: {[string]: mixed} = {};
const keys = Object.keys(this._style);
for (let ii = 0, length = keys.length; ii < length; ii++) {
const key = keys[ii];
const value = this._style[key];
if (value instanceof AnimatedNode) {
style[key] = value.__getValue();
} else {
style[key] = value;
}
}
return this.__getValueForStyle(style);
}
/**
* See the constructor, where this is shadowed on web platforms.
*/
__getValueForStyle<TStyle: FlatStyle>(
style: TStyle,
): FlatStyleForWeb<TStyle> | TStyle {
return style;
}
/**
* Mutates the supplied `style` object such that animated nodes are replaced
* with rasterized values.
*/
__replaceAnimatedNodeWithValues(style: {[string]: mixed}): void {
const keys = Object.keys(style);
for (let ii = 0, length = keys.length; ii < length; ii++) {
const key = keys[ii];
const maybeNode = this._style[key];
if (key === 'transform' && maybeNode instanceof AnimatedTransform) {
style[key] = maybeNode.__getValueWithStaticTransforms(
// NOTE: This check should not be necessary, but the types are not
// enforced as of this writing.
Array.isArray(style[key]) ? style[key] : [],
);
} else if (maybeNode instanceof AnimatedObject) {
style[key] = maybeNode.__getValueWithStaticObject(style[key]);
} else if (maybeNode instanceof AnimatedNode) {
style[key] = maybeNode.__getValue();
}
}
}
__getAnimatedValue(): Object {
const style: {[string]: mixed} = {};
const nodeKeys = this._nodeKeys;
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const key = nodeKeys[ii];
const node = nodes[ii];
style[key] = node.__getAnimatedValue();
}
return style;
}
__attach(): void {
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const node = nodes[ii];
node.__addChild(this);
}
super.__attach();
}
__detach(): void {
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const node = nodes[ii];
node.__removeChild(this);
}
super.__detach();
}
__makeNative(platformConfig: ?PlatformConfig) {
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const node = nodes[ii];
node.__makeNative(platformConfig);
}
super.__makeNative(platformConfig);
}
__getNativeConfig(): Object {
const platformConfig = this.__getPlatformConfig();
const styleConfig: {[string]: ?number} = {};
const nodeKeys = this._nodeKeys;
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const key = nodeKeys[ii];
const node = nodes[ii];
node.__makeNative(platformConfig);
styleConfig[key] = node.__getNativeTag();
}
if (__DEV__) {
validateStyles(styleConfig);
}
return {
type: 'style',
style: styleConfig,
debugID: this.__getDebugID(),
};
}
}
// Supported versions of JSC do not implement the newer Object.hasOwn. Remove
// this shim when they do.
// $FlowFixMe[method-unbinding]
const _hasOwnProp = Object.prototype.hasOwnProperty;
const hasOwn: (obj: $ReadOnly<{...}>, prop: string) => boolean =
// $FlowFixMe[method-unbinding]
Object.hasOwn ?? ((obj, prop) => _hasOwnProp.call(obj, prop));

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {InterpolationConfigType} from './AnimatedInterpolation';
import type AnimatedNode from './AnimatedNode';
import type {AnimatedNodeConfig} from './AnimatedNode';
import AnimatedInterpolation from './AnimatedInterpolation';
import AnimatedValue from './AnimatedValue';
import AnimatedWithChildren from './AnimatedWithChildren';
export default class AnimatedSubtraction extends AnimatedWithChildren {
_a: AnimatedNode;
_b: AnimatedNode;
constructor(
a: AnimatedNode | number,
b: AnimatedNode | number,
config?: ?AnimatedNodeConfig,
) {
super(config);
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
}
__makeNative(platformConfig: ?PlatformConfig) {
this._a.__makeNative(platformConfig);
this._b.__makeNative(platformConfig);
super.__makeNative(platformConfig);
}
__getValue(): number {
return this._a.__getValue() - this._b.__getValue();
}
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}
__attach(): void {
this._a.__addChild(this);
this._b.__addChild(this);
super.__attach();
}
__detach(): void {
this._a.__removeChild(this);
this._b.__removeChild(this);
super.__detach();
}
__getNativeConfig(): any {
return {
type: 'subtraction',
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
debugID: this.__getDebugID(),
};
}
}

View File

@@ -0,0 +1,104 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {EndCallback} from '../animations/Animation';
import type {AnimatedNodeConfig} from './AnimatedNode';
import type AnimatedValue from './AnimatedValue';
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import AnimatedNode from './AnimatedNode';
export default class AnimatedTracking extends AnimatedNode {
_value: AnimatedValue;
_parent: AnimatedNode;
_callback: ?EndCallback;
_animationConfig: Object;
_animationClass: any;
_useNativeDriver: boolean;
constructor(
value: AnimatedValue,
parent: AnimatedNode,
animationClass: any,
animationConfig: Object,
callback?: ?EndCallback,
config?: ?AnimatedNodeConfig,
) {
super(config);
this._value = value;
this._parent = parent;
this._animationClass = animationClass;
this._animationConfig = animationConfig;
this._useNativeDriver =
NativeAnimatedHelper.shouldUseNativeDriver(animationConfig);
this._callback = callback;
this.__attach();
}
__makeNative(platformConfig: ?PlatformConfig) {
this.__isNative = true;
this._parent.__makeNative(platformConfig);
super.__makeNative(platformConfig);
this._value.__makeNative(platformConfig);
}
__getValue(): Object {
return this._parent.__getValue();
}
__attach(): void {
this._parent.__addChild(this);
if (this._useNativeDriver) {
// when the tracking starts we need to convert this node to a "native node"
// so that the parent node will be made "native" too. This is necessary as
// if we don't do this `update` method will get called. At that point it
// may be too late as it would mean the JS driver has already started
// updating node values
let {platformConfig} = this._animationConfig;
this.__makeNative(platformConfig);
}
super.__attach();
}
__detach(): void {
this._parent.__removeChild(this);
super.__detach();
}
update(): void {
this._value.animate(
new this._animationClass({
...this._animationConfig,
toValue: (this._animationConfig.toValue: any).__getValue(),
}),
this._callback,
);
}
__getNativeConfig(): any {
const animation = new this._animationClass({
...this._animationConfig,
// remove toValue from the config as it's a ref to Animated.Value
toValue: undefined,
});
const animationConfig = animation.__getNativeAnimationConfig();
return {
type: 'tracking',
animationId: NativeAnimatedHelper.generateNewAnimationId(),
animationConfig,
toValue: this._parent.__getNativeTag(),
value: this._value.__getNativeTag(),
debugID: this.__getDebugID(),
};
}
}

View File

@@ -0,0 +1,202 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {AnimatedNodeConfig} from './AnimatedNode';
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import {validateTransform} from '../../../src/private/animated/NativeAnimatedValidation';
import AnimatedNode from './AnimatedNode';
import AnimatedWithChildren from './AnimatedWithChildren';
type Transform<T = AnimatedNode> = {
[string]:
| number
| string
| T
| $ReadOnlyArray<number | string | T>
| {[string]: number | string | T},
};
function flatAnimatedNodes(
transforms: $ReadOnlyArray<Transform<>>,
): Array<AnimatedNode> {
const nodes = [];
for (let ii = 0, length = transforms.length; ii < length; ii++) {
const transform = transforms[ii];
// There should be exactly one property in `transform`.
for (const key in transform) {
const value = transform[key];
if (value instanceof AnimatedNode) {
nodes.push(value);
}
}
}
return nodes;
}
export default class AnimatedTransform extends AnimatedWithChildren {
// NOTE: For potentially historical reasons, some operations only operate on
// the first level of AnimatedNode instances. This optimizes that bevavior.
_nodes: $ReadOnlyArray<AnimatedNode>;
_transforms: $ReadOnlyArray<Transform<>>;
/**
* Creates an `AnimatedTransform` if `transforms` contains `AnimatedNode`
* instances. Otherwise, returns `null`.
*/
static from(transforms: $ReadOnlyArray<Transform<>>): ?AnimatedTransform {
const nodes = flatAnimatedNodes(
// NOTE: This check should not be necessary, but the types are not
// enforced as of this writing. This check should be hoisted to
// instantiation sites.
Array.isArray(transforms) ? transforms : [],
);
if (nodes.length === 0) {
return null;
}
return new AnimatedTransform(nodes, transforms);
}
constructor(
nodes: $ReadOnlyArray<AnimatedNode>,
transforms: $ReadOnlyArray<Transform<>>,
config?: ?AnimatedNodeConfig,
) {
super(config);
this._nodes = nodes;
this._transforms = transforms;
}
__makeNative(platformConfig: ?PlatformConfig) {
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const node = nodes[ii];
node.__makeNative(platformConfig);
}
super.__makeNative(platformConfig);
}
__getValue(): $ReadOnlyArray<Transform<any>> {
return mapTransforms(this._transforms, animatedNode =>
animatedNode.__getValue(),
);
}
__getValueWithStaticTransforms(
staticTransforms: $ReadOnlyArray<Object>,
): $ReadOnlyArray<Object> {
const values = [];
mapTransforms(this._transforms, node => {
values.push(node.__getValue());
});
// NOTE: We can depend on `this._transforms` and `staticTransforms` sharing
// a structure because of `useAnimatedPropsMemo`.
return mapTransforms(staticTransforms, () => values.shift());
}
__getAnimatedValue(): $ReadOnlyArray<Transform<any>> {
return mapTransforms(this._transforms, animatedNode =>
animatedNode.__getAnimatedValue(),
);
}
__attach(): void {
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const node = nodes[ii];
node.__addChild(this);
}
super.__attach();
}
__detach(): void {
const nodes = this._nodes;
for (let ii = 0, length = nodes.length; ii < length; ii++) {
const node = nodes[ii];
node.__removeChild(this);
}
super.__detach();
}
__getNativeConfig(): any {
const transformsConfig: Array<any> = [];
const transforms = this._transforms;
for (let ii = 0, length = transforms.length; ii < length; ii++) {
const transform = transforms[ii];
// There should be exactly one property in `transform`.
for (const key in transform) {
const value = transform[key];
if (value instanceof AnimatedNode) {
transformsConfig.push({
type: 'animated',
property: key,
nodeTag: value.__getNativeTag(),
});
} else {
transformsConfig.push({
type: 'static',
property: key,
/* $FlowFixMe[incompatible-type] - `value` can be an array or an
object. This is not currently handled by `transformDataType`.
Migrating to `TransformObject` might solve this. */
value: NativeAnimatedHelper.transformDataType(value),
});
}
}
}
if (__DEV__) {
validateTransform(transformsConfig);
}
return {
type: 'transform',
transforms: transformsConfig,
debugID: this.__getDebugID(),
};
}
}
function mapTransforms<T>(
transforms: $ReadOnlyArray<Transform<>>,
mapFunction: AnimatedNode => T,
): $ReadOnlyArray<Transform<T>> {
return transforms.map(transform => {
const result: Transform<T> = {};
// There should be exactly one property in `transform`.
for (const key in transform) {
const value = transform[key];
if (value instanceof AnimatedNode) {
result[key] = mapFunction(value);
} else if (Array.isArray(value)) {
result[key] = value.map(element =>
element instanceof AnimatedNode ? mapFunction(element) : element,
);
} else if (typeof value === 'object') {
const object: {[string]: number | string | T} = {};
for (const propertyName in value) {
const propertyValue = value[propertyName];
object[propertyName] =
propertyValue instanceof AnimatedNode
? mapFunction(propertyValue)
: propertyValue;
}
result[key] = object;
} else {
result[key] = value;
}
}
return result;
});
}

View File

@@ -0,0 +1,371 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
import type {EventSubscription} from '../../vendor/emitter/EventEmitter';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type Animation from '../animations/Animation';
import type {EndCallback} from '../animations/Animation';
import type {InterpolationConfigType} from './AnimatedInterpolation';
import type AnimatedNode from './AnimatedNode';
import type {AnimatedNodeConfig} from './AnimatedNode';
import type AnimatedTracking from './AnimatedTracking';
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import AnimatedInterpolation from './AnimatedInterpolation';
import AnimatedWithChildren from './AnimatedWithChildren';
export type AnimatedValueConfig = $ReadOnly<{
...AnimatedNodeConfig,
useNativeDriver: boolean,
}>;
const NativeAnimatedAPI = NativeAnimatedHelper.API;
/**
* Animated works by building a directed acyclic graph of dependencies
* transparently when you render your Animated components.
*
* new Animated.Value(0)
* .interpolate() .interpolate() new Animated.Value(1)
* opacity translateY scale
* style transform
* View#234 style
* View#123
*
* A) Top Down phase
* When an Animated.Value is updated, we recursively go down through this
* graph in order to find leaf nodes: the views that we flag as needing
* an update.
*
* B) Bottom Up phase
* When a view is flagged as needing an update, we recursively go back up
* in order to build the new value that it needs. The reason why we need
* this two-phases process is to deal with composite props such as
* transform which can receive values from multiple parents.
*/
export function flushValue(rootNode: AnimatedNode): void {
const leaves = new Set<{update: () => void, ...}>();
function findAnimatedStyles(node: AnimatedNode) {
// $FlowFixMe[prop-missing]
if (typeof node.update === 'function') {
leaves.add((node: any));
} else {
node.__getChildren().forEach(findAnimatedStyles);
}
}
findAnimatedStyles(rootNode);
leaves.forEach(leaf => leaf.update());
}
/**
* Some operations are executed only on batch end, which is _mostly_ scheduled when
* Animated component props change. For some of the changes which require immediate execution
* (e.g. setValue), we create a separate batch in case none is scheduled.
*/
function _executeAsAnimatedBatch(id: string, operation: () => void) {
NativeAnimatedAPI.setWaitingForIdentifier(id);
operation();
NativeAnimatedAPI.unsetWaitingForIdentifier(id);
}
/**
* Standard value for driving animations. One `Animated.Value` can drive
* multiple properties in a synchronized fashion, but can only be driven by one
* mechanism at a time. Using a new mechanism (e.g. starting a new animation,
* or calling `setValue`) will stop any previous ones.
*
* See https://reactnative.dev/docs/animatedvalue
*/
export default class AnimatedValue extends AnimatedWithChildren {
_listenerCount: number;
_updateSubscription: ?EventSubscription;
_value: number;
_startingValue: number;
_offset: number;
_animation: ?Animation;
_tracking: ?AnimatedTracking;
constructor(value: number, config?: ?AnimatedValueConfig) {
super(config);
if (typeof value !== 'number') {
throw new Error('AnimatedValue: Attempting to set value to undefined');
}
this._listenerCount = 0;
this._updateSubscription = null;
this._startingValue = this._value = value;
this._offset = 0;
this._animation = null;
if (config && config.useNativeDriver) {
this.__makeNative();
}
}
__detach() {
if (this.__isNative) {
NativeAnimatedAPI.getValue(this.__getNativeTag(), value => {
this._value = value - this._offset;
});
}
this.stopAnimation();
super.__detach();
}
__getValue(): number {
return this._value + this._offset;
}
__makeNative(platformConfig: ?PlatformConfig): void {
super.__makeNative(platformConfig);
if (this._listenerCount > 0) {
this.__ensureUpdateSubscriptionExists();
}
}
addListener(callback: (value: any) => mixed): string {
const id = super.addListener(callback);
this._listenerCount++;
if (this.__isNative) {
this.__ensureUpdateSubscriptionExists();
}
return id;
}
removeListener(id: string): void {
super.removeListener(id);
this._listenerCount--;
if (this.__isNative && this._listenerCount === 0) {
this._updateSubscription?.remove();
}
}
removeAllListeners(): void {
super.removeAllListeners();
this._listenerCount = 0;
if (this.__isNative) {
this._updateSubscription?.remove();
}
}
__ensureUpdateSubscriptionExists(): void {
if (this._updateSubscription != null) {
return;
}
const nativeTag = this.__getNativeTag();
NativeAnimatedAPI.startListeningToAnimatedNodeValue(nativeTag);
const subscription: EventSubscription =
NativeAnimatedHelper.nativeEventEmitter.addListener(
'onAnimatedValueUpdate',
data => {
if (data.tag === nativeTag) {
this.__onAnimatedValueUpdateReceived(data.value, data.offset);
}
},
);
this._updateSubscription = {
remove: () => {
// Only this function assigns to `this.#updateSubscription`.
if (this._updateSubscription == null) {
return;
}
this._updateSubscription = null;
subscription.remove();
NativeAnimatedAPI.stopListeningToAnimatedNodeValue(nativeTag);
},
};
}
/**
* Directly set the value. This will stop any animations running on the value
* and update all the bound properties.
*
* See https://reactnative.dev/docs/animatedvalue#setvalue
*/
setValue(value: number): void {
if (this._animation) {
this._animation.stop();
this._animation = null;
}
this._updateValue(
value,
!this.__isNative /* don't perform a flush for natively driven values */,
);
if (this.__isNative) {
_executeAsAnimatedBatch(this.__getNativeTag().toString(), () =>
NativeAnimatedAPI.setAnimatedNodeValue(this.__getNativeTag(), value),
);
}
}
/**
* Sets an offset that is applied on top of whatever value is set, whether via
* `setValue`, an animation, or `Animated.event`. Useful for compensating
* things like the start of a pan gesture.
*
* See https://reactnative.dev/docs/animatedvalue#setoffset
*/
setOffset(offset: number): void {
this._offset = offset;
if (this.__isNative) {
NativeAnimatedAPI.setAnimatedNodeOffset(this.__getNativeTag(), offset);
}
}
/**
* Merges the offset value into the base value and resets the offset to zero.
* The final output of the value is unchanged.
*
* See https://reactnative.dev/docs/animatedvalue#flattenoffset
*/
flattenOffset(): void {
this._value += this._offset;
this._offset = 0;
if (this.__isNative) {
NativeAnimatedAPI.flattenAnimatedNodeOffset(this.__getNativeTag());
}
}
/**
* Sets the offset value to the base value, and resets the base value to zero.
* The final output of the value is unchanged.
*
* See https://reactnative.dev/docs/animatedvalue#extractoffset
*/
extractOffset(): void {
this._offset += this._value;
this._value = 0;
if (this.__isNative) {
_executeAsAnimatedBatch(this.__getNativeTag().toString(), () =>
NativeAnimatedAPI.extractAnimatedNodeOffset(this.__getNativeTag()),
);
}
}
/**
* Stops any running animation or tracking. `callback` is invoked with the
* final value after stopping the animation, which is useful for updating
* state to match the animation position with layout.
*
* See https://reactnative.dev/docs/animatedvalue#stopanimation
*/
stopAnimation(callback?: ?(value: number) => void): void {
this.stopTracking();
this._animation && this._animation.stop();
this._animation = null;
if (callback) {
if (this.__isNative) {
NativeAnimatedAPI.getValue(this.__getNativeTag(), callback);
} else {
callback(this.__getValue());
}
}
}
/**
* Stops any animation and resets the value to its original.
*
* See https://reactnative.dev/docs/animatedvalue#resetanimation
*/
resetAnimation(callback?: ?(value: number) => void): void {
this.stopAnimation(callback);
this._value = this._startingValue;
if (this.__isNative) {
NativeAnimatedAPI.setAnimatedNodeValue(
this.__getNativeTag(),
this._startingValue,
);
}
}
__onAnimatedValueUpdateReceived(value: number, offset?: number): void {
this._updateValue(value, false /*flush*/);
if (offset != null) {
this._offset = offset;
}
}
/**
* Interpolates the value before updating the property, e.g. mapping 0-1 to
* 0-10.
*/
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}
/**
* Typically only used internally, but could be used by a custom Animation
* class.
*
* See https://reactnative.dev/docs/animatedvalue#animate
*/
animate(animation: Animation, callback: ?EndCallback): void {
const previousAnimation = this._animation;
this._animation && this._animation.stop();
this._animation = animation;
animation.start(
this._value,
value => {
// Natively driven animations will never call into that callback, therefore we can always
// pass flush = true to allow the updated value to propagate to native with setNativeProps
this._updateValue(value, true /* flush */);
},
result => {
this._animation = null;
callback && callback(result);
},
previousAnimation,
this,
);
}
/**
* Typically only used internally.
*/
stopTracking(): void {
this._tracking && this._tracking.__detach();
this._tracking = null;
}
/**
* Typically only used internally.
*/
track(tracking: AnimatedTracking): void {
this.stopTracking();
this._tracking = tracking;
// Make sure that the tracking animation starts executing
this._tracking && this._tracking.update();
}
_updateValue(value: number, flush: boolean): void {
if (value === undefined) {
throw new Error('AnimatedValue: Attempting to set value to undefined');
}
this._value = value;
if (flush) {
flushValue(this);
}
this.__callListeners(this.__getValue());
}
__getNativeConfig(): Object {
return {
type: 'value',
value: this._value,
offset: this._offset,
debugID: this.__getDebugID(),
};
}
}

View File

@@ -0,0 +1,240 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import type {AnimatedNodeConfig} from './AnimatedNode';
import AnimatedValue from './AnimatedValue';
import AnimatedWithChildren from './AnimatedWithChildren';
import invariant from 'invariant';
export type AnimatedValueXYConfig = $ReadOnly<{
...AnimatedNodeConfig,
useNativeDriver: boolean,
}>;
type ValueXYListenerCallback = (value: {x: number, y: number, ...}) => mixed;
let _uniqueId = 1;
/**
* 2D Value for driving 2D animations, such as pan gestures. Almost identical
* API to normal `Animated.Value`, but multiplexed.
*
* See https://reactnative.dev/docs/animatedvaluexy
*/
export default class AnimatedValueXY extends AnimatedWithChildren {
x: AnimatedValue;
y: AnimatedValue;
_listeners: {
[key: string]: {
x: string,
y: string,
...
},
...
};
constructor(
valueIn?: ?{
+x: number | AnimatedValue,
+y: number | AnimatedValue,
...
},
config?: ?AnimatedValueXYConfig,
) {
super(config);
const value: any = valueIn || {x: 0, y: 0}; // @flowfixme: shouldn't need `: any`
if (typeof value.x === 'number' && typeof value.y === 'number') {
this.x = new AnimatedValue(value.x);
this.y = new AnimatedValue(value.y);
} else {
invariant(
value.x instanceof AnimatedValue && value.y instanceof AnimatedValue,
'AnimatedValueXY must be initialized with an object of numbers or ' +
'AnimatedValues.',
);
this.x = value.x;
this.y = value.y;
}
this._listeners = {};
if (config && config.useNativeDriver) {
this.__makeNative();
}
}
/**
* Directly set the value. This will stop any animations running on the value
* and update all the bound properties.
*
* See https://reactnative.dev/docs/animatedvaluexy#setvalue
*/
setValue(value: {x: number, y: number, ...}) {
this.x.setValue(value.x);
this.y.setValue(value.y);
}
/**
* Sets an offset that is applied on top of whatever value is set, whether
* via `setValue`, an animation, or `Animated.event`. Useful for compensating
* things like the start of a pan gesture.
*
* See https://reactnative.dev/docs/animatedvaluexy#setoffset
*/
setOffset(offset: {x: number, y: number, ...}) {
this.x.setOffset(offset.x);
this.y.setOffset(offset.y);
}
/**
* Merges the offset value into the base value and resets the offset to zero.
* The final output of the value is unchanged.
*
* See https://reactnative.dev/docs/animatedvaluexy#flattenoffset
*/
flattenOffset(): void {
this.x.flattenOffset();
this.y.flattenOffset();
}
/**
* Sets the offset value to the base value, and resets the base value to
* zero. The final output of the value is unchanged.
*
* See https://reactnative.dev/docs/animatedvaluexy#extractoffset
*/
extractOffset(): void {
this.x.extractOffset();
this.y.extractOffset();
}
__getValue(): {
x: number,
y: number,
...
} {
return {
x: this.x.__getValue(),
y: this.y.__getValue(),
};
}
/**
* Stops any animation and resets the value to its original.
*
* See https://reactnative.dev/docs/animatedvaluexy#resetanimation
*/
resetAnimation(
callback?: (value: {x: number, y: number, ...}) => void,
): void {
this.x.resetAnimation();
this.y.resetAnimation();
callback && callback(this.__getValue());
}
/**
* Stops any running animation or tracking. `callback` is invoked with the
* final value after stopping the animation, which is useful for updating
* state to match the animation position with layout.
*
* See https://reactnative.dev/docs/animatedvaluexy#stopanimation
*/
stopAnimation(callback?: (value: {x: number, y: number, ...}) => void): void {
this.x.stopAnimation();
this.y.stopAnimation();
callback && callback(this.__getValue());
}
/**
* Adds an asynchronous listener to the value so you can observe updates from
* animations. This is useful because there is no way to synchronously read
* the value because it might be driven natively.
*
* Returns a string that serves as an identifier for the listener.
*
* See https://reactnative.dev/docs/animatedvaluexy#addlistener
*/
addListener(callback: ValueXYListenerCallback): string {
const id = String(_uniqueId++);
const jointCallback = ({value: number}: any) => {
callback(this.__getValue());
};
this._listeners[id] = {
x: this.x.addListener(jointCallback),
y: this.y.addListener(jointCallback),
};
return id;
}
/**
* Unregister a listener. The `id` param shall match the identifier
* previously returned by `addListener()`.
*
* See https://reactnative.dev/docs/animatedvaluexy#removelistener
*/
removeListener(id: string): void {
this.x.removeListener(this._listeners[id].x);
this.y.removeListener(this._listeners[id].y);
delete this._listeners[id];
}
/**
* Remove all registered listeners.
*
* See https://reactnative.dev/docs/animatedvaluexy#removealllisteners
*/
removeAllListeners(): void {
this.x.removeAllListeners();
this.y.removeAllListeners();
this._listeners = {};
}
/**
* Converts `{x, y}` into `{left, top}` for use in style.
*
* See https://reactnative.dev/docs/animatedvaluexy#getlayout
*/
getLayout(): {[key: string]: AnimatedValue, ...} {
return {
left: this.x,
top: this.y,
};
}
/**
* Converts `{x, y}` into a useable translation transform.
*
* See https://reactnative.dev/docs/animatedvaluexy#gettranslatetransform
*/
getTranslateTransform(): Array<
{translateX: AnimatedValue} | {translateY: AnimatedValue},
> {
return [{translateX: this.x}, {translateY: this.y}];
}
__attach(): void {
this.x.__addChild(this);
this.y.__addChild(this);
super.__attach();
}
__detach(): void {
this.x.__removeChild(this);
this.y.__removeChild(this);
super.__detach();
}
__makeNative(platformConfig: ?PlatformConfig) {
this.x.__makeNative(platformConfig);
this.y.__makeNative(platformConfig);
super.__makeNative(platformConfig);
}
}

View File

@@ -0,0 +1,85 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import type {PlatformConfig} from '../AnimatedPlatformConfig';
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import AnimatedNode from './AnimatedNode';
const {connectAnimatedNodes, disconnectAnimatedNodes} =
NativeAnimatedHelper.API;
export default class AnimatedWithChildren extends AnimatedNode {
_children: Array<AnimatedNode> = [];
__makeNative(platformConfig: ?PlatformConfig) {
if (!this.__isNative) {
this.__isNative = true;
const children = this._children;
let length = children.length;
if (length > 0) {
for (let ii = 0; ii < length; ii++) {
const child = children[ii];
child.__makeNative(platformConfig);
connectAnimatedNodes(this.__getNativeTag(), child.__getNativeTag());
}
}
}
super.__makeNative(platformConfig);
}
__addChild(child: AnimatedNode): void {
if (this._children.length === 0) {
this.__attach();
}
this._children.push(child);
if (this.__isNative) {
// Only accept "native" animated nodes as children
child.__makeNative(this.__getPlatformConfig());
connectAnimatedNodes(this.__getNativeTag(), child.__getNativeTag());
}
}
__removeChild(child: AnimatedNode): void {
const index = this._children.indexOf(child);
if (index === -1) {
console.warn("Trying to remove a child that doesn't exist");
return;
}
if (this.__isNative && child.__isNative) {
disconnectAnimatedNodes(this.__getNativeTag(), child.__getNativeTag());
}
this._children.splice(index, 1);
if (this._children.length === 0) {
this.__detach();
}
}
__getChildren(): $ReadOnlyArray<AnimatedNode> {
return this._children;
}
__callListeners(value: number): void {
super.__callListeners(value);
if (!this.__isNative) {
const children = this._children;
for (let ii = 0, length = children.length; ii < length; ii++) {
const child = children[ii];
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
if (child.__getValue) {
child.__callListeners(child.__getValue());
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
import Platform from '../Utilities/Platform';
function shouldUseTurboAnimatedModule(): boolean {
if (ReactNativeFeatureFlags.cxxNativeAnimatedEnabled()) {
return false;
} else {
return Platform.OS === 'ios' && global.RN$Bridgeless === true;
}
}
export default shouldUseTurboAnimatedModule;

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {AnimatedPropsHook} from '../../src/private/animated/createAnimatedPropsHook';
import createAnimatedPropsHook from '../../src/private/animated/createAnimatedPropsHook';
/**
* @deprecated
*/
export default createAnimatedPropsHook(null) as AnimatedPropsHook;

View File

@@ -0,0 +1,15 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import type {Animated} from './Animated';
export function useAnimatedValue(
initialValue: number,
config?: Animated.AnimatedConfig,
): Animated.Value;

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import Animated from './Animated';
import {useRef} from 'react';
export default function useAnimatedValue(
initialValue: number,
config?: ?Animated.AnimatedConfig,
): Animated.Value {
const ref = useRef<null | Animated.Value>(null);
if (ref.current == null) {
ref.current = new Animated.Value(initialValue, config);
}
return ref.current;
}