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,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.
*
* @format
*/
import {ProcessedColorValue} from '../StyleSheet/processColor';
import {ColorValue} from '../StyleSheet/StyleSheet';
/**
* @see: https://reactnative.dev/docs/actionsheetios#content
*/
export interface ActionSheetIOSOptions {
title?: string | undefined;
options: string[];
cancelButtonIndex?: number | undefined;
destructiveButtonIndex?: number | number[] | undefined | null;
message?: string | undefined;
anchor?: number | undefined;
tintColor?: ColorValue | ProcessedColorValue | undefined;
cancelButtonTintColor?: ColorValue | ProcessedColorValue | undefined;
disabledButtonTintColor?: ColorValue | ProcessedColorValue | undefined;
userInterfaceStyle?: 'light' | 'dark' | undefined;
disabledButtonIndices?: number[] | undefined;
}
export interface ShareActionSheetIOSOptions {
message?: string | undefined;
url?: string | undefined;
subject?: string | undefined;
anchor?: number | undefined;
/** The activities to exclude from the ActionSheet.
* For example: ['com.apple.UIKit.activity.PostToTwitter']
*/
excludedActivityTypes?: string[] | undefined;
}
/**
* @see https://reactnative.dev/docs/actionsheetios#content
*/
export interface ActionSheetIOSStatic {
/**
* Display an iOS action sheet. The `options` object must contain one or more
* of:
* - `options` (array of strings) - a list of button titles (required)
* - `cancelButtonIndex` (int) - index of cancel button in `options`
* - `destructiveButtonIndex` (int) - index of destructive button in `options`
* - `title` (string) - a title to show above the action sheet
* - `message` (string) - a message to show below the title
*/
showActionSheetWithOptions: (
options: ActionSheetIOSOptions,
callback: (buttonIndex: number) => void,
) => void;
/**
* Display the iOS share sheet. The `options` object should contain
* one or both of `message` and `url` and can additionally have
* a `subject` or `excludedActivityTypes`:
*
* - `url` (string) - a URL to share
* - `message` (string) - a message to share
* - `subject` (string) - a subject for the message
* - `excludedActivityTypes` (array) - the activities to exclude from the ActionSheet
*
* NOTE: if `url` points to a local file, or is a base64-encoded
* uri, the file it points to will be loaded and shared directly.
* In this way, you can share images, videos, PDF files, etc.
*/
showShareActionSheetWithOptions: (
options: ShareActionSheetIOSOptions,
failureCallback: (error: Error) => void,
successCallback: (success: boolean, method: string) => void,
) => void;
/**
* Dismisses the most upper iOS action sheet presented, if no action sheet is
* present a warning is displayed.
*/
dismissActionSheet: () => void;
}
export const ActionSheetIOS: ActionSheetIOSStatic;
export type ActionSheetIOS = ActionSheetIOSStatic;

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
* @format
*/
import type {ProcessedColorValue} from '../StyleSheet/processColor';
import type {ColorValue} from '../StyleSheet/StyleSheet';
import RCTActionSheetManager from './NativeActionSheetManager';
const processColor = require('../StyleSheet/processColor').default;
const invariant = require('invariant');
export type ActionSheetIOSOptions = $ReadOnly<{
title?: ?string,
message?: ?string,
options: Array<string>,
destructiveButtonIndex?: ?number | ?Array<number>,
cancelButtonIndex?: ?number,
anchor?: ?number,
tintColor?: ColorValue | ProcessedColorValue,
cancelButtonTintColor?: ColorValue | ProcessedColorValue,
disabledButtonTintColor?: ColorValue | ProcessedColorValue,
userInterfaceStyle?: string,
disabledButtonIndices?: Array<number>,
}>;
export type ShareActionSheetIOSOptions = $ReadOnly<{
message?: ?string,
url?: ?string,
subject?: ?string,
anchor?: ?number,
tintColor?: ?number,
cancelButtonTintColor?: ?number,
disabledButtonTintColor?: ?number,
excludedActivityTypes?: ?Array<string>,
userInterfaceStyle?: ?string,
}>;
export type ShareActionSheetError = $ReadOnly<{
domain: string,
code: string,
userInfo?: ?Object,
message: string,
}>;
/**
* Display action sheets and share sheets on iOS.
*
* See https://reactnative.dev/docs/actionsheetios
*/
const ActionSheetIOS = {
/**
* Display an iOS action sheet.
*
* The `options` object must contain one or more of:
*
* - `options` (array of strings) - a list of button titles (required)
* - `cancelButtonIndex` (int) - index of cancel button in `options`
* - `destructiveButtonIndex` (int or array of ints) - index or indices of destructive buttons in `options`
* - `title` (string) - a title to show above the action sheet
* - `message` (string) - a message to show below the title
* - `disabledButtonIndices` (array of numbers) - a list of button indices which should be disabled
*
* The 'callback' function takes one parameter, the zero-based index
* of the selected item.
*
* See https://reactnative.dev/docs/actionsheetios#showactionsheetwithoptions
*/
showActionSheetWithOptions(
options: ActionSheetIOSOptions,
callback: (buttonIndex: number) => void,
) {
invariant(
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/5whu3i34. */
typeof options === 'object' && options !== null,
'Options must be a valid object',
);
invariant(typeof callback === 'function', 'Must provide a valid callback');
invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist");
const {
tintColor,
cancelButtonTintColor,
disabledButtonTintColor,
destructiveButtonIndex,
...remainingOptions
} = options;
let destructiveButtonIndices = null;
if (Array.isArray(destructiveButtonIndex)) {
destructiveButtonIndices = destructiveButtonIndex;
} else if (typeof destructiveButtonIndex === 'number') {
destructiveButtonIndices = [destructiveButtonIndex];
}
const processedTintColor = processColor(tintColor);
const processedCancelButtonTintColor = processColor(cancelButtonTintColor);
const processedDisabledButtonTintColor = processColor(
disabledButtonTintColor,
);
invariant(
processedTintColor == null || typeof processedTintColor === 'number',
'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions tintColor',
);
invariant(
processedCancelButtonTintColor == null ||
typeof processedCancelButtonTintColor === 'number',
'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions cancelButtonTintColor',
);
invariant(
processedDisabledButtonTintColor == null ||
typeof processedDisabledButtonTintColor === 'number',
'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions disabledButtonTintColor',
);
RCTActionSheetManager.showActionSheetWithOptions(
{
...remainingOptions,
// $FlowFixMe[incompatible-type]
tintColor: processedTintColor,
// $FlowFixMe[incompatible-type]
cancelButtonTintColor: processedCancelButtonTintColor,
// $FlowFixMe[incompatible-type]
disabledButtonTintColor: processedDisabledButtonTintColor,
destructiveButtonIndices,
},
callback,
);
},
/**
* Display the iOS share sheet. The `options` object should contain
* one or both of `message` and `url` and can additionally have
* a `subject` or `excludedActivityTypes`:
*
* - `url` (string) - a URL to share
* - `message` (string) - a message to share
* - `subject` (string) - a subject for the message
* - `excludedActivityTypes` (array) - the activities to exclude from
* the ActionSheet
* - `tintColor` (color) - tint color of the buttons
*
* The 'failureCallback' function takes one parameter, an error object.
* The only property defined on this object is an optional `stack` property
* of type `string`.
*
* The 'successCallback' function takes two parameters:
*
* - a boolean value signifying success or failure
* - a string that, in the case of success, indicates the method of sharing
*
* See https://reactnative.dev/docs/actionsheetios#showshareactionsheetwithoptions
*/
showShareActionSheetWithOptions(
options: ShareActionSheetIOSOptions,
failureCallback: Function | ((error: ShareActionSheetError) => void),
successCallback: Function | ((success: boolean, method: ?string) => void),
) {
invariant(
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/5whu3i34. */
typeof options === 'object' && options !== null,
'Options must be a valid object',
);
invariant(
typeof failureCallback === 'function',
'Must provide a valid failureCallback',
);
invariant(
typeof successCallback === 'function',
'Must provide a valid successCallback',
);
invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist");
RCTActionSheetManager.showShareActionSheetWithOptions(
{...options, tintColor: processColor(options.tintColor) as $FlowFixMe},
failureCallback,
successCallback,
);
},
/**
* Dismisses the most upper iOS action sheet presented, if no action sheet is
* present a warning is displayed.
*/
dismissActionSheet: () => {
invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist");
if (typeof RCTActionSheetManager.dismissActionSheet === 'function') {
RCTActionSheetManager.dismissActionSheet();
}
},
};
export default ActionSheetIOS;

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/NativeActionSheetManager';
import NativeActionSheetManager from '../../src/private/specs_DEPRECATED/modules/NativeActionSheetManager';
export default NativeActionSheetManager;

View File

@@ -0,0 +1,34 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "React-RCTActionSheet"
s.version = version
s.summary = "An API for displaying iOS action sheets and share sheets."
s.homepage = "https://reactnative.dev/"
s.documentation_url = "https://reactnative.dev/docs/actionsheetios"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("*.{m}", "")
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
s.header_dir = "RCTActionSheet"
s.dependency "React-Core/RCTActionSheetHeaders", version
end

94
node_modules/react-native/Libraries/Alert/Alert.d.ts generated vendored Normal file
View File

@@ -0,0 +1,94 @@
/**
* 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
*/
/**
* @see https://reactnative.dev/docs/alert#content
*/
export interface AlertButton {
text?: string | undefined;
onPress?:
| ((value?: string) => void)
| ((value?: {login: string; password: string}) => void)
| undefined;
isPreferred?: boolean | undefined;
style?: 'default' | 'cancel' | 'destructive' | undefined;
}
interface AlertOptions {
/** @platform android */
cancelable?: boolean | undefined;
userInterfaceStyle?: 'unspecified' | 'light' | 'dark' | undefined;
/** @platform android */
onDismiss?: (() => void) | undefined;
}
/**
* Launches an alert dialog with the specified title and message.
*
* Optionally provide a list of buttons. Tapping any button will fire the
* respective onPress callback and dismiss the alert. By default, the only
* button will be an 'OK' button.
*
* This is an API that works both on iOS and Android and can show static
* alerts. On iOS, you can show an alert that prompts the user to enter
* some information.
*
* ## iOS
*
* On iOS you can specify any number of buttons. Each button can optionally
* specify a style, which is one of 'default', 'cancel' or 'destructive'.
*
* ## Android
*
* On Android at most three buttons can be specified. Android has a concept
* of a neutral, negative and a positive button:
*
* - If you specify one button, it will be the 'positive' one (such as 'OK')
* - Two buttons mean 'negative', 'positive' (such as 'Cancel', 'OK')
* - Three buttons mean 'neutral', 'negative', 'positive' (such as 'Later', 'Cancel', 'OK')
*
* ```
* // Works on both iOS and Android
* Alert.alert(
* 'Alert Title',
* 'My Alert Msg',
* [
* {text: 'Ask me later', onPress: () => console.log('Ask me later pressed')},
* {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
* {text: 'OK', onPress: () => console.log('OK Pressed')},
* ]
* )
* ```
*/
export interface AlertStatic {
alert: (
title: string,
message?: string,
buttons?: AlertButton[],
options?: AlertOptions,
) => void;
prompt: (
title: string,
message?: string,
callbackOrButtons?: ((text: string) => void) | AlertButton[],
type?: AlertType,
defaultValue?: string,
keyboardType?: string,
options?: AlertOptions,
) => void;
}
export type AlertType =
| 'default'
| 'plain-text'
| 'secure-text'
| 'login-password';
export const Alert: AlertStatic;
export type Alert = AlertStatic;

203
node_modules/react-native/Libraries/Alert/Alert.js generated vendored Normal file
View File

@@ -0,0 +1,203 @@
/**
* 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 {DialogOptions} from '../NativeModules/specs/NativeDialogManagerAndroid';
import Platform from '../Utilities/Platform';
import {alertWithArgs} from './RCTAlertManager';
/**
* @platform ios
*/
export type AlertType =
| 'default'
| 'plain-text'
| 'secure-text'
| 'login-password';
/**
* @platform ios
*/
export type AlertButtonStyle = 'default' | 'cancel' | 'destructive';
export type AlertButton = {
text?: string,
onPress?: ?((value?: string) => any) | ?Function,
isPreferred?: boolean,
style?: AlertButtonStyle,
...
};
export type AlertButtons = Array<AlertButton>;
export type AlertOptions = {
/** @platform android */
cancelable?: ?boolean,
userInterfaceStyle?: 'unspecified' | 'light' | 'dark',
/** @platform android */
onDismiss?: ?() => void,
...
};
/**
* Launches an alert dialog with the specified title and message.
*
* Optionally provide a list of buttons. Tapping any button will fire the
* respective onPress callback and dismiss the alert. By default, the only
* button will be an 'OK' button.
*
* This is an API that works both on iOS and Android and can show static
* alerts. On iOS, you can show an alert that prompts the user to enter
* some information.
*
* See https://reactnative.dev/docs/alert
*/
class Alert {
static alert(
title: ?string,
message?: ?string,
buttons?: AlertButtons,
options?: AlertOptions,
): void {
if (Platform.OS === 'ios') {
Alert.prompt(
title,
message,
buttons,
'default',
undefined,
undefined,
options,
);
} else if (Platform.OS === 'android') {
const NativeDialogManagerAndroid =
require('../NativeModules/specs/NativeDialogManagerAndroid').default;
if (!NativeDialogManagerAndroid) {
return;
}
const constants = NativeDialogManagerAndroid.getConstants();
const config: DialogOptions = {
title: title || '',
message: message || '',
cancelable: false,
};
if (options && options.cancelable) {
config.cancelable = options.cancelable;
}
// At most three buttons (neutral, negative, positive). Ignore rest.
// The text 'OK' should be probably localized. iOS Alert does that in native.
const defaultPositiveText = 'OK';
const validButtons: AlertButtons = buttons
? buttons.slice(0, 3)
: [{text: defaultPositiveText}];
const buttonPositive = validButtons.pop();
const buttonNegative = validButtons.pop();
const buttonNeutral = validButtons.pop();
if (buttonNeutral) {
config.buttonNeutral = buttonNeutral.text || '';
}
if (buttonNegative) {
config.buttonNegative = buttonNegative.text || '';
}
if (buttonPositive) {
config.buttonPositive = buttonPositive.text || defaultPositiveText;
}
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by
* Flow's LTI update could not be added via codemod */
const onAction = (action, buttonKey) => {
if (action === constants.buttonClicked) {
if (buttonKey === constants.buttonNeutral) {
// $FlowFixMe[incompatible-type]
// $FlowFixMe[incompatible-use]
buttonNeutral.onPress && buttonNeutral.onPress();
} else if (buttonKey === constants.buttonNegative) {
// $FlowFixMe[incompatible-type]
// $FlowFixMe[incompatible-use]
buttonNegative.onPress && buttonNegative.onPress();
} else if (buttonKey === constants.buttonPositive) {
// $FlowFixMe[incompatible-type]
// $FlowFixMe[incompatible-use]
buttonPositive.onPress && buttonPositive.onPress();
}
} else if (action === constants.dismissed) {
options && options.onDismiss && options.onDismiss();
}
};
const onError = (errorMessage: string) => console.warn(errorMessage);
NativeDialogManagerAndroid.showAlert(config, onError, onAction);
}
}
/**
* @platform ios
*/
static prompt(
title: ?string,
message?: ?string,
callbackOrButtons?: ?(((text: string) => void) | AlertButtons),
type?: ?AlertType = 'plain-text',
defaultValue?: string,
keyboardType?: string,
options?: AlertOptions,
): void {
if (Platform.OS === 'ios') {
let callbacks: Array<?any> = [];
const buttons = [];
let cancelButtonKey;
let destructiveButtonKey;
let preferredButtonKey;
if (typeof callbackOrButtons === 'function') {
callbacks = [callbackOrButtons];
} else if (Array.isArray(callbackOrButtons)) {
callbackOrButtons.forEach((btn, index) => {
callbacks[index] = btn.onPress;
if (btn.style === 'cancel') {
cancelButtonKey = String(index);
} else if (btn.style === 'destructive') {
destructiveButtonKey = String(index);
}
if (btn.isPreferred) {
preferredButtonKey = String(index);
}
if (btn.text || index < (callbackOrButtons || []).length - 1) {
const btnDef: {[number]: string} = {};
btnDef[index] = btn.text || '';
buttons.push(btnDef);
}
});
}
alertWithArgs(
{
title: title || '',
message: message || undefined,
buttons,
type: type || undefined,
defaultValue,
cancelButtonKey,
destructiveButtonKey,
preferredButtonKey,
keyboardType,
userInterfaceStyle: options?.userInterfaceStyle || undefined,
},
(id, value) => {
const cb = callbacks[id];
cb && cb(value);
},
);
}
}
}
export default Alert;

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/NativeAlertManager';
import NativeAlertManager from '../../src/private/specs_DEPRECATED/modules/NativeAlertManager';
export default NativeAlertManager;

View File

@@ -0,0 +1,35 @@
/**
* 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 {Args} from './NativeAlertManager';
import NativeDialogManagerAndroid from '../NativeModules/specs/NativeDialogManagerAndroid';
function emptyCallback() {}
export function alertWithArgs(
args: Args,
callback: (id: number, value: string) => void,
) {
// TODO(5998984): Polyfill it correctly with DialogManagerAndroid
if (!NativeDialogManagerAndroid) {
return;
}
NativeDialogManagerAndroid.showAlert(
// $FlowFixMe[incompatible-type] - Mismatched platform interfaces.
args,
emptyCallback,
// $FlowFixMe[incompatible-type] - Mismatched platform interfaces.
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
callback || emptyCallback,
);
}

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 {Args} from './NativeAlertManager';
import NativeAlertManager from './NativeAlertManager';
export function alertWithArgs(
args: Args,
callback: (id: number, value: string) => void,
): void {
if (NativeAlertManager == null) {
return;
}
NativeAlertManager.alertWithArgs(args, callback);
}

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.
*
* @flow strict-local
* @format
*/
// NOTE: This file supports backwards compatibility of subpath (deep) imports
// from 'react-native' with platform-specific extensions. It can be deleted
// once we remove the "./*" mapping from package.json "exports".
export * from './RCTAlertManager';

View File

@@ -0,0 +1,16 @@
/**
* 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 {Args} from './NativeAlertManager';
declare export function alertWithArgs(
args: Args,
callback: (id: number, value: string) => void,
): void;

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;
}

View File

@@ -0,0 +1,86 @@
/*
* 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.
*/
#import <UIKit/UIKit.h>
#import "RCTDefaultReactNativeFactoryDelegate.h"
#import "RCTReactNativeFactory.h"
#import "RCTRootViewFactory.h"
@class RCTBridge;
@protocol RCTBridgeDelegate;
@protocol RCTComponentViewProtocol;
@class RCTRootView;
@class RCTSurfacePresenterBridgeAdapter;
@protocol RCTDependencyProvider;
NS_ASSUME_NONNULL_BEGIN
/**
* @deprecated RCTAppDelegate is deprecated and will be removed in a future version of React Native. Use
`RCTReactNativeFactory` instead.
*
* The RCTAppDelegate is an utility class that implements some base configurations for all the React Native apps.
* It is not mandatory to use it, but it could simplify your AppDelegate code.
*
* To use it, you just need to make your AppDelegate a subclass of RCTAppDelegate:
*
* ```objc
* #import <React/RCTAppDelegate.h>
* @interface AppDelegate: RCTAppDelegate
* @end
* ```
*
* All the methods implemented by the RCTAppDelegate can be overridden by your AppDelegate if you need to provide a
custom implementation.
* If you need to customize the default implementation, you can invoke `[super <method_name>]` and use the returned
object.
*
* Overridable methods
* Shared:
* - (RCTBridge *)createBridgeWithDelegate:(id<RCTBridgeDelegate>)delegate launchOptions:(NSDictionary
*)launchOptions;
* - (UIView *)createRootViewWithBridge:(RCTBridge *)bridge moduleName:(NSString*)moduleName initProps:(NSDictionary
*)initProps;
* - (UIViewController *)createRootViewController;
* - (void)setRootView:(UIView *)rootView toRootViewController:(UIViewController *)rootViewController;
* New Architecture:
* - (BOOL)turboModuleEnabled;
* - (BOOL)fabricEnabled;
* - (NSDictionary *)prepareInitialProps
* - (Class)getModuleClassFromName:(const char *)name
* - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
* - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
initParams:
(const facebook::react::ObjCTurboModule::InitParams &)params
* - (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
*/
__attribute__((deprecated(
"RCTAppDelegate is deprecated and will be removed in a future version of React Native. Use `RCTReactNativeFactory` instead.")))
@interface RCTAppDelegate : RCTDefaultReactNativeFactoryDelegate<UIApplicationDelegate>
/// The window object, used to render the UViewControllers
@property (nonatomic, strong, nonnull) UIWindow *window;
@property (nonatomic, nullable) RCTBridge *bridge
__attribute__((deprecated("The bridge is deprecated and will be removed when removing the legacy architecture.")));
@property (nonatomic, strong, nullable) NSString *moduleName;
@property (nonatomic, strong, nullable) NSDictionary *initialProps;
@property (nonatomic, strong) RCTReactNativeFactory *reactNativeFactory;
/// If `automaticallyLoadReactNativeWindow` is set to `true`, the React Native window will be loaded automatically.
@property (nonatomic, assign) BOOL automaticallyLoadReactNativeWindow;
@property (nonatomic, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter __attribute__((
deprecated("The bridge adapter is deprecated and will be removed when removing the legacy architecture.")));
;
- (RCTRootViewFactory *)rootViewFactory;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,90 @@
/*
* 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.
*/
#import "RCTAppDelegate.h"
#import <React/RCTBridgeDelegate.h>
#import <React/RCTLog.h>
#import <React/RCTRootView.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <React/RCTUtils.h>
#import <ReactCommon/RCTHost.h>
#include <UIKit/UIKit.h>
#import <objc/runtime.h>
#import "RCTAppSetupUtils.h"
#import "RCTDependencyProvider.h"
#if RN_DISABLE_OSS_PLUGIN_HEADER
#import <RCTTurboModulePlugin/RCTTurboModulePlugin.h>
#else
#import <React/CoreModulesPlugins.h>
#endif
#import <React/RCTComponentViewFactory.h>
#import <React/RCTComponentViewProtocol.h>
#import <react/nativemodule/defaults/DefaultTurboModules.h>
using namespace facebook::react;
@implementation RCTAppDelegate
- (instancetype)init
{
if (self = [super init]) {
_automaticallyLoadReactNativeWindow = YES;
}
return self;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.reactNativeFactory = [[RCTReactNativeFactory alloc] initWithDelegate:self];
if (self.automaticallyLoadReactNativeWindow) {
[self loadReactNativeWindow:launchOptions];
}
return YES;
}
- (void)loadReactNativeWindow:(NSDictionary *)launchOptions
{
UIView *rootView = [self.rootViewFactory viewWithModuleName:self.moduleName
initialProperties:self.initialProps
launchOptions:launchOptions];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [self createRootViewController];
[self setRootView:rootView toRootViewController:rootViewController];
_window.rootViewController = rootViewController;
[_window makeKeyAndVisible];
}
- (RCTRootViewFactory *)rootViewFactory
{
return self.reactNativeFactory.rootViewFactory;
}
- (RCTBridge *)bridge
{
return self.rootViewFactory.bridge;
}
- (RCTSurfacePresenterBridgeAdapter *)bridgeAdapter
{
return self.rootViewFactory.bridgeAdapter;
}
- (void)setBridge:(RCTBridge *)bridge
{
self.reactNativeFactory.rootViewFactory.bridge = bridge;
}
- (void)setBridgeAdapter:(RCTSurfacePresenterBridgeAdapter *)bridgeAdapter
{
self.reactNativeFactory.rootViewFactory.bridgeAdapter = bridgeAdapter;
}
@end

View File

@@ -0,0 +1,59 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
#import <React/RCTBridge.h>
#import <React/RCTRootView.h>
#ifdef __cplusplus
#import <memory>
#if USE_THIRD_PARTY_JSC != 1
#import <reacthermes/HermesExecutorFactory.h>
#endif
#import <ReactCommon/RCTTurboModuleManager.h>
#import <jsireact/JSIExecutor.h>
@protocol RCTDependencyProvider;
// Forward declaration to decrease compilation coupling
namespace facebook::react {
class RuntimeScheduler;
}
RCT_EXTERN NSArray<NSString *> *RCTAppSetupUnstableModulesRequiringMainQueueSetup(
id<RCTDependencyProvider> dependencyProvider);
RCT_EXTERN id<RCTTurboModule> RCTAppSetupDefaultModuleFromClass(
Class moduleClass,
id<RCTDependencyProvider> dependencyProvider);
std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupDefaultJsExecutorFactory(
RCTBridge *bridge,
RCTTurboModuleManager *turboModuleManager,
const std::shared_ptr<facebook::react::RuntimeScheduler> &runtimeScheduler);
std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupJsExecutorFactoryForOldArch(
RCTBridge *bridge,
const std::shared_ptr<facebook::react::RuntimeScheduler> &runtimeScheduler)
__attribute__((deprecated(
"RCTAppSetupJsExecutorFactoryForOldArch(RCTBridge *, RuntimeScheduler) is deprecated and will be removed when we remove the legacy architecture.")));
;
#endif // __cplusplus
RCT_EXTERN_C_BEGIN
void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled) __attribute__((deprecated(
"RCTAppSetupPrepareApp(UIApplication, BOOL) is deprecated and it's signature will change when we remove the legacy arch")));
UIView *
RCTAppSetupDefaultRootView(RCTBridge *bridge, NSString *moduleName, NSDictionary *initialProperties, BOOL fabricEnabled)
__attribute__((deprecated(
"RCTAppSetupDefaultRootView(RCTBridge *, NSString *, NSDictionary *, BOOL) is deprecated and it's signature will change when we remove the legacy arch")));
RCT_EXTERN_C_END

View File

@@ -0,0 +1,179 @@
/*
* 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.
*/
#import "RCTAppSetupUtils.h"
#import <React/RCTJSIExecutorRuntimeInstaller.h>
#import <react/renderer/runtimescheduler/RuntimeScheduler.h>
#import <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
// Turbo Module
#import <React/RCTBundleAssetImageLoader.h>
#import <React/RCTDataRequestHandler.h>
#import <React/RCTFileRequestHandler.h>
#import <React/RCTGIFImageDecoder.h>
#import <React/RCTHTTPRequestHandler.h>
#import <React/RCTImageLoader.h>
#import <React/RCTNetworking.h>
// Fabric
#import <React/RCTFabricSurface.h>
#import <React/RCTSurfaceHostingProxyRootView.h>
// jsinspector-modern
#import <jsinspector-modern/InspectorFlags.h>
#import "RCTDependencyProvider.h"
void RCTAppSetupPrepareApp(UIApplication *application, BOOL turboModuleEnabled)
{
RCTEnableTurboModule(YES);
#if DEBUG
// Disable idle timer in dev builds to avoid putting application in background and complicating
// Metro reconnection logic. Users only need this when running the application using our CLI tooling.
application.idleTimerDisabled = YES;
#endif
}
UIView *
RCTAppSetupDefaultRootView(RCTBridge *bridge, NSString *moduleName, NSDictionary *initialProperties, BOOL fabricEnabled)
{
id<RCTSurfaceProtocol> surface = [[RCTFabricSurface alloc] initWithBridge:bridge
moduleName:moduleName
initialProperties:initialProperties];
UIView *rootView = [[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
[surface start];
return rootView;
}
NSArray<NSString *> *RCTAppSetupUnstableModulesRequiringMainQueueSetup(id<RCTDependencyProvider> dependencyProvider)
{
// For oss, insert core main queue setup modules here
return (dependencyProvider != nullptr) ? dependencyProvider.unstableModulesRequiringMainQueueSetup : @[];
}
id<RCTTurboModule> RCTAppSetupDefaultModuleFromClass(Class moduleClass, id<RCTDependencyProvider> dependencyProvider)
{
// private block used to filter out modules depending on protocol conformance
NSArray * (^extractModuleConformingToProtocol)(RCTModuleRegistry *, Protocol *) =
^NSArray *(RCTModuleRegistry *moduleRegistry, Protocol *protocol) {
NSArray<NSString *> *classNames = @[];
if (protocol == @protocol(RCTImageURLLoader)) {
classNames = (dependencyProvider != nullptr) ? dependencyProvider.imageURLLoaderClassNames : @[];
} else if (protocol == @protocol(RCTImageDataDecoder)) {
classNames = (dependencyProvider != nullptr) ? dependencyProvider.imageDataDecoderClassNames : @[];
} else if (protocol == @protocol(RCTURLRequestHandler)) {
classNames = (dependencyProvider != nullptr) ? dependencyProvider.URLRequestHandlerClassNames : @[];
}
NSMutableArray *modules = [NSMutableArray new];
for (NSString *className in classNames) {
const char *cModuleName = [className cStringUsingEncoding:NSUTF8StringEncoding];
id moduleFromLibrary = [moduleRegistry moduleForName:cModuleName];
if (![moduleFromLibrary conformsToProtocol:protocol]) {
continue;
}
[modules addObject:moduleFromLibrary];
}
return modules;
};
// Set up the default RCTImageLoader and RCTNetworking modules.
if (moduleClass == RCTImageLoader.class) {
return [[moduleClass alloc] initWithRedirectDelegate:nil
loadersProvider:^NSArray<id<RCTImageURLLoader>> *(RCTModuleRegistry *moduleRegistry) {
NSArray *imageURLLoaderModules =
extractModuleConformingToProtocol(moduleRegistry, @protocol(RCTImageURLLoader));
return [@[ [RCTBundleAssetImageLoader new] ] arrayByAddingObjectsFromArray:imageURLLoaderModules];
}
decodersProvider:^NSArray<id<RCTImageDataDecoder>> *(RCTModuleRegistry *moduleRegistry) {
NSArray *imageDataDecoder = extractModuleConformingToProtocol(moduleRegistry, @protocol(RCTImageDataDecoder));
return [@[ [RCTGIFImageDecoder new] ] arrayByAddingObjectsFromArray:imageDataDecoder];
}];
} else if (moduleClass == RCTNetworking.class) {
return [[moduleClass alloc]
initWithHandlersProvider:^NSArray<id<RCTURLRequestHandler>> *(RCTModuleRegistry *moduleRegistry) {
NSArray *URLRequestHandlerModules =
extractModuleConformingToProtocol(moduleRegistry, @protocol(RCTURLRequestHandler));
return [@[
[RCTHTTPRequestHandler new],
[RCTDataRequestHandler new],
[RCTFileRequestHandler new],
[moduleRegistry moduleForName:"BlobModule"],
] arrayByAddingObjectsFromArray:URLRequestHandlerModules];
}];
}
// No custom initializer here.
return [moduleClass new];
}
std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupDefaultJsExecutorFactory(
RCTBridge *bridge,
RCTTurboModuleManager *turboModuleManager,
const std::shared_ptr<facebook::react::RuntimeScheduler> &runtimeScheduler)
{
#ifndef RCT_REMOVE_LEGACY_ARCH
// Necessary to allow NativeModules to lookup TurboModules
[bridge setRCTTurboModuleRegistry:turboModuleManager];
#if RCT_DEV
/**
* Instantiating DevMenu has the side-effect of registering
* shortcuts for CMD + d, CMD + i, and CMD + n via RCTDevMenu.
* Therefore, when TurboModules are enabled, we must manually create this
* NativeModule.
*/
[turboModuleManager moduleForName:"RCTDevMenu"];
#endif // end RCT_DEV
auto runtimeInstallerLambda = [turboModuleManager, bridge, runtimeScheduler](facebook::jsi::Runtime &runtime) {
if (!bridge || !turboModuleManager) {
return;
}
if (runtimeScheduler) {
facebook::react::RuntimeSchedulerBinding::createAndInstallIfNeeded(runtime, runtimeScheduler);
}
[turboModuleManager installJSBindings:runtime];
};
#if USE_THIRD_PARTY_JSC != 1
return std::make_unique<facebook::react::HermesExecutorFactory>(
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
#endif
#else
// This method should not be invoked in the New Arch. So when Legacy Arch is removed, we can
// safly return a nullptr.
return nullptr;
#endif
}
std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupJsExecutorFactoryForOldArch(
RCTBridge *bridge,
const std::shared_ptr<facebook::react::RuntimeScheduler> &runtimeScheduler)
{
#ifndef RCT_REMOVE_LEGACY_ARCH
auto runtimeInstallerLambda = [bridge, runtimeScheduler](facebook::jsi::Runtime &runtime) {
if (!bridge) {
return;
}
if (runtimeScheduler) {
facebook::react::RuntimeSchedulerBinding::createAndInstallIfNeeded(runtime, runtimeScheduler);
}
};
#if USE_THIRD_PARTY_JSC != 1
return std::make_unique<facebook::react::HermesExecutorFactory>(
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
#endif
#else
// This method should not be invoked in the New Arch. So when Legacy Arch is removed, we can
// safly return a nullptr.
return nullptr;
#endif
}

View File

@@ -0,0 +1,38 @@
/*
* 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.
*/
#import <React/RCTConvert.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
__attribute__((deprecated(
"RCTArchConfiguratorProtocol is deprecated and will be removed when we remove the legacy architecture.")));
@protocol RCTArchConfiguratorProtocol
/// This method controls whether the `turboModules` feature of the New Architecture is turned on or off.
///
/// @note: This is required to be rendering on Fabric (i.e. on the New Architecture).
/// @return: `true` if the Turbo Native Module are enabled. Otherwise, it returns `false`.
- (BOOL)turboModuleEnabled __attribute__((deprecated("This will be removed in a future version of React Native")));
/// This method controls whether the App will use the Fabric renderer of the New Architecture or not.
///
/// @return: `true` if the Fabric Renderer is enabled. Otherwise, it returns `false`.
- (BOOL)fabricEnabled __attribute__((deprecated("This will be removed in a future version of React Native")));
/// This method controls whether React Native's new initialization layer is enabled.
///
/// @return: `true` if the new initialization layer is enabled. Otherwise returns `false`.
- (BOOL)bridgelessEnabled __attribute__((deprecated("This will be removed in a future version of React Native")));
/// This method controls whether React Native uses new Architecture.
///
/// @return: `true` if the new architecture is enabled. Otherwise returns `false`.
- (BOOL)newArchEnabled __attribute__((deprecated("This will be removed in a future version of React Native")));
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,21 @@
/*
* 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.
*/
#import <UIKit/UIKit.h>
#import "RCTReactNativeFactory.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Default delegate for RCTReactNativeFactory.
* Contains default implementation of RCTReactNativeFactoryDelegate methods.
*/
@interface RCTDefaultReactNativeFactoryDelegate : UIResponder <RCTReactNativeFactoryDelegate>
@end
NS_ASSUME_NONNULL_END

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.
*/
#import "RCTDefaultReactNativeFactoryDelegate.h"
#import <ReactCommon/RCTHost.h>
#import "RCTAppSetupUtils.h"
#import "RCTDependencyProvider.h"
#if USE_THIRD_PARTY_JSC != 1
#import <React/RCTHermesInstanceFactory.h>
#endif
#import <react/nativemodule/defaults/DefaultTurboModules.h>
@implementation RCTDefaultReactNativeFactoryDelegate
@synthesize dependencyProvider;
- (NSURL *_Nullable)sourceURLForBridge:(nonnull RCTBridge *)bridge
{
[NSException raise:@"RCTBridgeDelegate::sourceURLForBridge not implemented"
format:@"Subclasses must implement a valid sourceURLForBridge method"];
return nil;
}
- (UIViewController *)createRootViewController
{
return [UIViewController new];
}
- (RCTBridge *)createBridgeWithDelegate:(id<RCTBridgeDelegate>)delegate launchOptions:(NSDictionary *)launchOptions
{
return [[RCTBridge alloc] initWithDelegate:delegate launchOptions:launchOptions];
}
- (void)setRootView:(UIView *)rootView toRootViewController:(UIViewController *)rootViewController
{
rootViewController.view = rootView;
}
- (JSRuntimeFactoryRef)createJSRuntimeFactory
{
#if USE_THIRD_PARTY_JSC != 1
return jsrt_create_hermes_factory();
#endif
}
- (void)customizeRootView:(RCTRootView *)rootView
{
// Override point for customization after application launch.
}
- (UIView *)createRootViewWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initProps:(NSDictionary *)initProps
{
UIView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, YES);
rootView.backgroundColor = [UIColor systemBackgroundColor];
return rootView;
}
- (RCTColorSpace)defaultColorSpace
{
return RCTColorSpaceSRGB;
}
- (NSURL *_Nullable)bundleURL
{
[NSException raise:@"RCTAppDelegate::bundleURL not implemented"
format:@"Subclasses must implement a valid getBundleURL method"];
return nullptr;
}
- (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
{
return (self.dependencyProvider != nullptr) ? self.dependencyProvider.thirdPartyFabricComponents : @{};
}
- (void)hostDidStart:(RCTHost *)host
{
}
- (NSArray<NSString *> *)unstableModulesRequiringMainQueueSetup
{
return (self.dependencyProvider != nullptr)
? RCTAppSetupUnstableModulesRequiringMainQueueSetup(self.dependencyProvider)
: @[];
}
- (nullable id<RCTModuleProvider>)getModuleProvider:(const char *)name
{
NSString *providerName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
return (self.dependencyProvider != nullptr) ? self.dependencyProvider.moduleProviders[providerName] : nullptr;
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
return facebook::react::DefaultTurboModules::getTurboModule(name, jsInvoker);
}
#pragma mark - RCTArchConfiguratorProtocol
- (BOOL)newArchEnabled
{
return YES;
}
- (BOOL)bridgelessEnabled
{
return YES;
}
- (BOOL)fabricEnabled
{
return YES;
}
- (BOOL)turboModuleEnabled
{
return YES;
}
- (Class)getModuleClassFromName:(const char *)name
{
return nullptr;
}
- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
return nullptr;
}
- (void)loadSourceForBridge:(RCTBridge *)bridge
onProgress:(RCTSourceLoadProgressBlock)onProgress
onComplete:(RCTSourceLoadBlock)loadCallback
{
[RCTJavaScriptLoader loadBundleAtURL:[self sourceURLForBridge:bridge] onProgress:onProgress onComplete:loadCallback];
}
@end

View File

@@ -0,0 +1,31 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
@protocol RCTComponentViewProtocol;
@protocol RCTModuleProvider;
NS_ASSUME_NONNULL_BEGIN
@protocol RCTDependencyProvider <NSObject>
- (NSArray<NSString *> *)imageURLLoaderClassNames;
- (NSArray<NSString *> *)imageDataDecoderClassNames;
- (NSArray<NSString *> *)URLRequestHandlerClassNames;
- (NSArray<NSString *> *)unstableModulesRequiringMainQueueSetup;
- (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents;
- (nonnull NSDictionary<NSString *, id<RCTModuleProvider>> *)moduleProviders;
@end
NS_ASSUME_NONNULL_END

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.
*/
#pragma once
NS_ASSUME_NONNULL_BEGIN
// Forward declarations for umbrella headers.
// In implementations, import `<react/runtime/JSRuntimeFactoryCAPI.h>` to obtain the actual type.
typedef void *JSRuntimeFactoryRef;
@protocol RCTJSRuntimeConfiguratorProtocol
- (JSRuntimeFactoryRef)createJSRuntimeFactory;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,124 @@
/*
* 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.
*/
#import <React/RCTBridgeDelegate.h>
#import <React/RCTConvert.h>
#import <UIKit/UIKit.h>
#import "RCTArchConfiguratorProtocol.h"
#import "RCTDependencyProvider.h"
#import "RCTJSRuntimeConfiguratorProtocol.h"
#import "RCTRootViewFactory.h"
#import "RCTUIConfiguratorProtocol.h"
#if defined(__cplusplus) // Don't conform to protocols requiring C++ when it's not defined.
#import <React/RCTComponentViewFactory.h>
#import <ReactCommon/RCTHost.h>
#import <ReactCommon/RCTTurboModuleManager.h>
#endif
@class RCTBridge;
@protocol RCTComponentViewProtocol;
@class RCTSurfacePresenterBridgeAdapter;
@class RCTDevMenuConfiguration;
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, RCTReleaseLevel) { Canary, Experimental, Stable };
@protocol RCTReactNativeFactoryDelegate <
RCTBridgeDelegate,
RCTUIConfiguratorProtocol,
#if defined(__cplusplus) // Don't conform to protocols requiring C++ when it's not defined.
RCTHostDelegate,
RCTTurboModuleManagerDelegate,
RCTComponentViewFactoryComponentProvider,
#endif
RCTJSRuntimeConfiguratorProtocol,
RCTArchConfiguratorProtocol>
/// Return the bundle URL for the main bundle.
- (NSURL *__nullable)bundleURL;
@property (nonatomic, strong) id<RCTDependencyProvider> dependencyProvider;
@optional
/**
* It creates a `RCTBridge` using a delegate and some launch options.
* By default, it is invoked passing `self` as a delegate.
* You can override this function to customize the logic that creates the RCTBridge
*
* @parameter: delegate - an object that implements the `RCTBridgeDelegate` protocol.
* @parameter: launchOptions - a dictionary with a set of options.
*
* @returns: a newly created instance of RCTBridge.
*/
- (RCTBridge *)createBridgeWithDelegate:(id<RCTBridgeDelegate>)delegate
launchOptions:(NSDictionary *)launchOptions
__attribute__((deprecated(
"createBridgeWithDelegate:launchOptions: is deprecated and will be removed when removing the legacy architecture.")));
/**
* It creates a `UIView` starting from a bridge, a module name and a set of initial properties.
* By default, it is invoked using the bridge created by `createBridgeWithDelegate:launchOptions` and
* the name in the `self.moduleName` variable.
* You can override this function to customize the logic that creates the Root View.
*
* @parameter: bridge - an instance of the `RCTBridge` object.
* @parameter: moduleName - the name of the app, used by Metro to resolve the module.
* @parameter: initProps - a set of initial properties.
*
* @returns: a UIView properly configured with a bridge for React Native.
*/
- (UIView *)createRootViewWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initProps:(NSDictionary *)initProps
__attribute__((deprecated(
"createRootViewWithBridge:moduleName:initProps is deprecated and will be removed when removing the legacy architecture.")));
/// This method returns a map of Component Descriptors and Components classes that needs to be registered in the
/// new renderer. The Component Descriptor is a string which represent the name used in JS to refer to the native
/// component. The default implementation returns an empty dictionary. Subclasses can override this method to register
/// the required components.
///
/// @return a dictionary that associate a component for the new renderer with his descriptor.
- (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents;
@end
@interface RCTReactNativeFactory : NSObject
- (instancetype)initWithDelegate:(id<RCTReactNativeFactoryDelegate>)delegate;
- (instancetype)initWithDelegate:(id<RCTReactNativeFactoryDelegate>)delegate releaseLevel:(RCTReleaseLevel)releaseLevel;
- (void)startReactNativeWithModuleName:(NSString *)moduleName inWindow:(UIWindow *_Nullable)window;
- (void)startReactNativeWithModuleName:(NSString *)moduleName
inWindow:(UIWindow *_Nullable)window
launchOptions:(NSDictionary *_Nullable)launchOptions;
- (void)startReactNativeWithModuleName:(NSString *)moduleName
inWindow:(UIWindow *_Nullable)window
initialProperties:(NSDictionary *_Nullable)initialProperties
launchOptions:(NSDictionary *_Nullable)launchOptions;
@property (nonatomic, nullable) RCTBridge *bridge
__attribute__((deprecated("The bridge is deprecated and will be removed when removing the legacy architecture.")));
@property (nonatomic, strong, nonnull) RCTRootViewFactory *rootViewFactory;
@property (nonatomic, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter __attribute__((
deprecated("The bridgeAdapter is deprecated and will be removed when removing the legacy architecture.")));
;
@property (nonatomic, weak) id<RCTReactNativeFactoryDelegate> delegate;
@property (nonatomic, nullable) RCTDevMenuConfiguration *devMenuConfiguration;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,375 @@
/*
* 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.
*/
#import "RCTReactNativeFactory.h"
#import <React/RCTColorSpaceUtils.h>
#import <React/RCTDevMenu.h>
#import <React/RCTLog.h>
#import <React/RCTRootView.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <React/RCTUtils.h>
#import <ReactCommon/RCTHost.h>
#import <objc/runtime.h>
#import <react/featureflags/ReactNativeFeatureFlagsOverridesOSSCanary.h>
#import <react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h>
#import <react/featureflags/ReactNativeFeatureFlagsOverridesOSSStable.h>
#import <react/renderer/graphics/ColorComponents.h>
#import "RCTAppSetupUtils.h"
#if RN_DISABLE_OSS_PLUGIN_HEADER
#import <RCTTurboModulePlugin/RCTTurboModulePlugin.h>
#else
#import <React/CoreModulesPlugins.h>
#endif
#import <React/RCTComponentViewFactory.h>
#import <React/RCTComponentViewProtocol.h>
#import <react/nativemodule/defaults/DefaultTurboModules.h>
#import "RCTDependencyProvider.h"
using namespace facebook::react;
@interface RCTReactNativeFactory () <
RCTComponentViewFactoryComponentProvider,
RCTHostDelegate,
RCTJSRuntimeConfiguratorProtocol,
RCTTurboModuleManagerDelegate>
@end
@implementation RCTReactNativeFactory
- (instancetype)initWithDelegate:(id<RCTReactNativeFactoryDelegate>)delegate
{
return [self initWithDelegate:delegate releaseLevel:Stable];
}
- (instancetype)initWithDelegate:(id<RCTReactNativeFactoryDelegate>)delegate releaseLevel:(RCTReleaseLevel)releaseLevel
{
if (self = [super init]) {
self.delegate = delegate;
[self _setUpFeatureFlags:releaseLevel];
[RCTColorSpaceUtils applyDefaultColorSpace:[self defaultColorSpace]];
RCTEnableTurboModule(YES);
self.rootViewFactory = [self createRCTRootViewFactory];
[RCTComponentViewFactory currentComponentViewFactory].thirdPartyFabricComponentsProvider = self;
}
return self;
}
- (void)startReactNativeWithModuleName:(NSString *)moduleName inWindow:(UIWindow *_Nullable)window
{
[self startReactNativeWithModuleName:moduleName inWindow:window initialProperties:nil launchOptions:nil];
}
- (void)startReactNativeWithModuleName:(NSString *)moduleName
inWindow:(UIWindow *_Nullable)window
launchOptions:(NSDictionary *_Nullable)launchOptions
{
[self startReactNativeWithModuleName:moduleName inWindow:window initialProperties:nil launchOptions:launchOptions];
}
- (void)startReactNativeWithModuleName:(NSString *)moduleName
inWindow:(UIWindow *_Nullable)window
initialProperties:(NSDictionary *_Nullable)initialProperties
launchOptions:(NSDictionary *_Nullable)launchOptions
{
UIView *rootView = [self.rootViewFactory viewWithModuleName:moduleName
initialProperties:initialProperties
launchOptions:launchOptions
devMenuConfiguration:self.devMenuConfiguration];
UIViewController *rootViewController = [_delegate createRootViewController];
[_delegate setRootView:rootView toRootViewController:rootViewController];
window.rootViewController = rootViewController;
[window makeKeyAndVisible];
}
#pragma mark - RCTUIConfiguratorProtocol
- (RCTColorSpace)defaultColorSpace
{
if ([_delegate respondsToSelector:@selector(defaultColorSpace)]) {
return [_delegate defaultColorSpace];
}
return RCTColorSpaceSRGB;
}
- (NSURL *_Nullable)bundleURL
{
if (![_delegate respondsToSelector:@selector(bundleURL)]) {
[NSException raise:@"RCTReactNativeFactoryDelegate::bundleURL not implemented"
format:@"Delegate must implement a valid getBundleURL method"];
}
return _delegate.bundleURL;
}
#pragma mark - RCTJSRuntimeConfiguratorProtocol
- (JSRuntimeFactoryRef)createJSRuntimeFactory
{
return [_delegate createJSRuntimeFactory];
}
#pragma mark - RCTArchConfiguratorProtocol
- (BOOL)newArchEnabled
{
return YES;
}
- (BOOL)fabricEnabled
{
return YES;
}
- (BOOL)turboModuleEnabled
{
return YES;
}
- (BOOL)bridgelessEnabled
{
return YES;
}
#pragma mark - RCTTurboModuleManagerDelegate
- (Class)getModuleClassFromName:(const char *)name
{
#if RN_DISABLE_OSS_PLUGIN_HEADER
return RCTTurboModulePluginClassProvider(name);
#else
if ([_delegate respondsToSelector:@selector(getModuleClassFromName:)]) {
Class moduleClass = [_delegate getModuleClassFromName:name];
if (moduleClass != nil) {
return moduleClass;
}
}
return RCTCoreModulesClassProvider(name);
#endif
}
- (nullable id<RCTModuleProvider>)getModuleProvider:(const char *)name
{
if ([_delegate respondsToSelector:@selector(getModuleProvider:)]) {
return [_delegate getModuleProvider:name];
}
return nil;
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
if ([_delegate respondsToSelector:@selector(getTurboModule:jsInvoker:)]) {
return [_delegate getTurboModule:name jsInvoker:jsInvoker];
}
return facebook::react::DefaultTurboModules::getTurboModule(name, jsInvoker);
}
- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
#if USE_OSS_CODEGEN
if (self.delegate.dependencyProvider == nil) {
[NSException raise:@"ReactNativeFactoryDelegate dependencyProvider is nil"
format:@"Delegate must provide a valid dependencyProvider"];
}
#endif
if ([_delegate respondsToSelector:@selector(getModuleInstanceFromClass:)]) {
id<RCTTurboModule> moduleInstance = [_delegate getModuleInstanceFromClass:moduleClass];
if (moduleInstance != nil) {
return moduleInstance;
}
}
return RCTAppSetupDefaultModuleFromClass(moduleClass, self.delegate.dependencyProvider);
}
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{
if ([_delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
return [_delegate extraModulesForBridge:bridge];
}
return @[];
}
#pragma mark - RCTComponentViewFactoryComponentProvider
- (NSDictionary<NSString *, Class<RCTComponentViewProtocol>> *)thirdPartyFabricComponents
{
if ([_delegate respondsToSelector:@selector(thirdPartyFabricComponents)]) {
return _delegate.thirdPartyFabricComponents;
}
return self.delegate.dependencyProvider ? self.delegate.dependencyProvider.thirdPartyFabricComponents : @{};
}
#pragma mark - RCTHostDelegate
- (void)hostDidStart:(RCTHost *)host
{
if ([_delegate respondsToSelector:@selector(hostDidStart:)]) {
[_delegate hostDidStart:host];
}
}
- (NSArray<NSString *> *)unstableModulesRequiringMainQueueSetup
{
#if RN_DISABLE_OSS_PLUGIN_HEADER
return RCTTurboModulePluginUnstableModulesRequiringMainQueueSetup();
#else
return self.delegate.dependencyProvider
? RCTAppSetupUnstableModulesRequiringMainQueueSetup(self.delegate.dependencyProvider)
: @[];
#endif
}
- (RCTRootViewFactory *)createRCTRootViewFactory
{
__weak __typeof(self) weakSelf = self;
RCTBundleURLBlock bundleUrlBlock = ^{
auto *strongSelf = weakSelf;
return strongSelf.bundleURL;
};
RCTRootViewFactoryConfiguration *configuration =
[[RCTRootViewFactoryConfiguration alloc] initWithBundleURLBlock:bundleUrlBlock
newArchEnabled:YES
turboModuleEnabled:YES
bridgelessEnabled:YES];
configuration.createRootViewWithBridge = ^UIView *(RCTBridge *bridge, NSString *moduleName, NSDictionary *initProps) {
return [weakSelf.delegate createRootViewWithBridge:bridge moduleName:moduleName initProps:initProps];
};
configuration.createBridgeWithDelegate = ^RCTBridge *(id<RCTBridgeDelegate> delegate, NSDictionary *launchOptions) {
return [weakSelf.delegate createBridgeWithDelegate:delegate launchOptions:launchOptions];
};
configuration.customizeRootView = ^(UIView *_Nonnull rootView) {
[weakSelf.delegate customizeRootView:(RCTRootView *)rootView];
};
configuration.sourceURLForBridge = ^NSURL *_Nullable(RCTBridge *_Nonnull bridge)
{
#ifndef RCT_REMOVE_LEGACY_ARCH
return [weakSelf.delegate sourceURLForBridge:bridge];
#else
// When the Legacy Arch is removed, the Delegate does not have a sourceURLForBridge method
return [weakSelf.delegate bundleURL];
#endif
};
if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
configuration.extraModulesForBridge = ^NSArray<id<RCTBridgeModule>> *_Nonnull(RCTBridge *_Nonnull bridge)
{
return [weakSelf.delegate extraModulesForBridge:bridge];
};
}
if ([self.delegate respondsToSelector:@selector(extraLazyModuleClassesForBridge:)]) {
configuration.extraLazyModuleClassesForBridge =
^NSDictionary<NSString *, Class> *_Nonnull(RCTBridge *_Nonnull bridge)
{
#ifndef RCT_REMOVE_LEGACY_ARCH
return [weakSelf.delegate extraLazyModuleClassesForBridge:bridge];
#else
// When the Legacy Arch is removed, the Delegate does not have a extraLazyModuleClassesForBridge method
return @{};
#endif
};
}
if ([self.delegate respondsToSelector:@selector(bridge:didNotFindModule:)]) {
configuration.bridgeDidNotFindModule = ^BOOL(RCTBridge *_Nonnull bridge, NSString *_Nonnull moduleName) {
#ifndef RCT_REMOVE_LEGACY_ARCH
return [weakSelf.delegate bridge:bridge didNotFindModule:moduleName];
#else
// When the Legacy Arch is removed, the Delegate does not have a bridge:didNotFindModule method
// We return NO, because if we have invoked this method is unlikely that the module will be actually registered
return NO;
#endif
};
}
if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:onProgress:onComplete:)]) {
configuration.loadSourceForBridgeWithProgress =
^(RCTBridge *_Nonnull bridge,
RCTSourceLoadProgressBlock _Nonnull onProgress,
RCTSourceLoadBlock _Nonnull loadCallback) {
#ifndef RCT_REMOVE_LEGACY_ARCH
[weakSelf.delegate loadSourceForBridge:bridge onProgress:onProgress onComplete:loadCallback];
#else
// When the Legacy Arch is removed, the Delegate does not have a
// loadSourceForBridge:onProgress:onComplete: method
// We then call the loadBundleAtURL:onProgress:onComplete: instead
[weakSelf.delegate loadBundleAtURL:self.bundleURL onProgress:onProgress onComplete:loadCallback];
#endif
};
}
if ([self.delegate respondsToSelector:@selector(loadSourceForBridge:withBlock:)]) {
configuration.loadSourceForBridge = ^(RCTBridge *_Nonnull bridge, RCTSourceLoadBlock _Nonnull loadCallback) {
#ifndef RCT_REMOVE_LEGACY_ARCH
[weakSelf.delegate loadSourceForBridge:bridge withBlock:loadCallback];
#else
// When the Legacy Arch is removed, the Delegate does not have a
// loadSourceForBridge:withBlock: method
// We then call the loadBundleAtURL:onProgress:onComplete: instead
[weakSelf.delegate loadBundleAtURL:self.bundleURL
onProgress:^(RCTLoadingProgress *progressData) {
}
onComplete:loadCallback];
#endif
};
}
configuration.jsRuntimeConfiguratorDelegate = self;
return [[RCTRootViewFactory alloc] initWithTurboModuleDelegate:self hostDelegate:self configuration:configuration];
}
#pragma mark - Feature Flags
- (void)_setUpFeatureFlags:(RCTReleaseLevel)releaseLevel
{
static BOOL initialized = NO;
static RCTReleaseLevel chosenReleaseLevel;
NSLog(@"_setUpFeatureFlags called with release level %li", releaseLevel);
if (!initialized) {
chosenReleaseLevel = releaseLevel;
initialized = YES;
} else if (chosenReleaseLevel != releaseLevel) {
[NSException
raise:@"RCTReactNativeFactory::_setUpFeatureFlags releaseLevel mismatch between React Native instances"
format:@"The releaseLevel (%li) of the new instance does not match the previous instance's releaseLevel (%li)",
releaseLevel,
chosenReleaseLevel];
}
static dispatch_once_t setupFeatureFlagsToken;
dispatch_once(&setupFeatureFlagsToken, ^{
switch (releaseLevel) {
case Stable:
ReactNativeFeatureFlags::override(std::make_unique<ReactNativeFeatureFlagsOverridesOSSStable>());
break;
case Canary:
ReactNativeFeatureFlags::override(std::make_unique<ReactNativeFeatureFlagsOverridesOSSCanary>());
break;
case Experimental:
ReactNativeFeatureFlags::override(std::make_unique<ReactNativeFeatureFlagsOverridesOSSExperimental>());
break;
}
});
}
@end

View File

@@ -0,0 +1,241 @@
/*
* 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.
*/
#import <React/RCTBridge.h>
#import <React/RCTRootView.h>
#import <React/RCTUtils.h>
#import "RCTJSRuntimeConfiguratorProtocol.h"
@protocol RCTCxxBridgeDelegate;
@protocol RCTComponentViewFactoryComponentProvider;
@protocol RCTTurboModuleManagerDelegate;
@protocol RCTHostDelegate;
@class RCTBridge;
@class RCTHost;
@class RCTRootView;
@class RCTSurfacePresenterBridgeAdapter;
@class RCTDevMenuConfiguration;
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Blocks' definitions
typedef UIView *_Nonnull (
^RCTCreateRootViewWithBridgeBlock)(RCTBridge *bridge, NSString *moduleName, NSDictionary *initProps);
typedef RCTBridge *_Nonnull (
^RCTCreateBridgeWithDelegateBlock)(id<RCTBridgeDelegate> delegate, NSDictionary *launchOptions);
typedef void (^RCTCustomizeRootViewBlock)(UIView *rootView);
typedef NSURL *_Nullable (^RCTSourceURLForBridgeBlock)(RCTBridge *bridge);
typedef NSURL *_Nullable (^RCTBundleURLBlock)(void);
typedef NSArray<id<RCTBridgeModule>> *_Nonnull (^RCTExtraModulesForBridgeBlock)(RCTBridge *bridge);
typedef NSDictionary<NSString *, Class> *_Nonnull (^RCTExtraLazyModuleClassesForBridge)(RCTBridge *bridge);
typedef BOOL (^RCTBridgeDidNotFindModuleBlock)(RCTBridge *bridge, NSString *moduleName);
typedef void (^RCTLoadSourceForBridgeWithProgressBlock)(
RCTBridge *bridge,
RCTSourceLoadProgressBlock onProgress,
RCTSourceLoadBlock loadCallback);
typedef void (^RCTLoadSourceForBridgeBlock)(RCTBridge *bridge, RCTSourceLoadBlock loadCallback);
#pragma mark - RCTRootViewFactory Configuration
@interface RCTRootViewFactoryConfiguration : NSObject
/// This property controls whether the App will use the Fabric renderer of the New Architecture or not.
@property (nonatomic, assign, readonly) BOOL fabricEnabled;
/// This property controls whether React Native's new initialization layer is enabled.
@property (nonatomic, assign, readonly) BOOL bridgelessEnabled;
/// This method controls whether the `turboModules` feature of the New Architecture is turned on or off
@property (nonatomic, assign, readonly) BOOL turboModuleEnabled;
/// Return the bundle URL for the main bundle.
@property (nonatomic, nonnull) RCTBundleURLBlock bundleURLBlock;
/**
* Use this method to initialize a new instance of `RCTRootViewFactoryConfiguration` by passing a `bundleURL`
*
* Which is the location of the JavaScript source file. When running from the packager
* this should be an absolute URL, e.g. `http://localhost:8081/index.ios.bundle`.
* When running from a locally bundled JS file, this should be a `file://` url
* pointing to a path inside the app resources, e.g. `file://.../main.jsbundle`.
*
*/
- (instancetype)initWithBundleURLBlock:(RCTBundleURLBlock)bundleURLBlock
newArchEnabled:(BOOL)newArchEnabled
turboModuleEnabled:(BOOL)turboModuleEnabled
bridgelessEnabled:(BOOL)bridgelessEnabled __deprecated;
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
newArchEnabled:(BOOL)newArchEnabled
turboModuleEnabled:(BOOL)turboModuleEnabled
bridgelessEnabled:(BOOL)bridgelessEnabled __deprecated;
- (instancetype)initWithBundleURLBlock:(RCTBundleURLBlock)bundleURLBlock
newArchEnabled:(BOOL)newArchEnabled NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithBundleURL:(NSURL *)bundleURL newArchEnabled:(BOOL)newArchEnabled;
/**
* Block that allows to override logic of creating root view instance.
* It creates a `UIView` starting from a bridge, a module name and a set of initial properties.
* By default, it is invoked using the bridge created by `RCTCreateBridgeWithDelegateBlock` (or the default
* implementation) and the `moduleName` variable comes from `viewWithModuleName:initialProperties:launchOptions` of
* `RCTRootViewFactory`.
*
* @parameter: bridge - an instance of the `RCTBridge` object.
* @parameter: moduleName - the name of the app, used by Metro to resolve the module.
* @parameter: initProps - a set of initial properties.
*
* @returns: a UIView properly configured with a bridge for React Native.
*/
@property (nonatomic, nullable) RCTCreateRootViewWithBridgeBlock createRootViewWithBridge;
/**
* Block that allows to override default behavior of creating bridge.
* It should return `RCTBridge` using a delegate and some launch options.
*
* By default, it is invoked passing `self` as a delegate.
*
* @parameter: delegate - an object that implements the `RCTBridgeDelegate` protocol.
* @parameter: launchOptions - a dictionary with a set of options.
*
* @returns: a newly created instance of RCTBridge.
*/
@property (nonatomic, nullable) RCTCreateBridgeWithDelegateBlock createBridgeWithDelegate;
/**
* Block that allows to customize the rootView that is passed to React Native.
*
* @parameter: rootView - The root view to customize.
*/
@property (nonatomic, nullable) RCTCustomizeRootViewBlock customizeRootView;
@property (nonatomic, weak, nullable) id<RCTJSRuntimeConfiguratorProtocol> jsRuntimeConfiguratorDelegate;
#pragma mark - RCTBridgeDelegate blocks
/**
* Block that returns the location of the JavaScript source file. When running from the packager
* this should be an absolute URL, e.g. `http://localhost:8081/index.ios.bundle`.
* When running from a locally bundled JS file, this should be a `file://` url
* pointing to a path inside the app resources, e.g. `file://.../main.jsbundle`.
*/
@property (nonatomic, nullable) RCTSourceURLForBridgeBlock sourceURLForBridge;
/**
* The bridge initializes any registered RCTBridgeModules automatically, however
* if you wish to instantiate your own module instances, you can return them
* from this block.
*
* Note: You should always return a new instance for each call, rather than
* returning the same instance each time the bridge is reloaded. Module instances
* should not be shared between bridges, and this may cause unexpected behavior.
*
* It is also possible to override standard modules with your own implementations
* by returning a class with the same `moduleName` from this method, but this is
* not recommended in most cases - if the module methods and behavior do not
* match exactly, it may lead to bugs or crashes.
*/
@property (nonatomic, nullable) RCTExtraModulesForBridgeBlock extraModulesForBridge;
/**
* Retrieve the list of lazy-native-modules names for the given bridge.
*/
@property (nonatomic, nullable) RCTExtraLazyModuleClassesForBridge extraLazyModuleClassesForBridge;
/**
* The bridge will call this block when a module been called from JS
* cannot be found among registered modules.
* It should return YES if the module with name 'moduleName' was registered
* in the implementation, and the system must attempt to look for it again among registered.
* If the module was not registered, return NO to prevent further searches.
*/
@property (nonatomic, nullable) RCTBridgeDidNotFindModuleBlock bridgeDidNotFindModule;
/**
* The bridge will automatically attempt to load the JS source code from the
* location specified by the `sourceURLForBridge:` method, however, if you want
* to handle loading the JS yourself, you can do so by setting this property.
*/
@property (nonatomic, nullable) RCTLoadSourceForBridgeWithProgressBlock loadSourceForBridgeWithProgress;
/**
* Similar to loadSourceForBridgeWithProgress but without progress
* reporting.
*/
@property (nonatomic, nullable) RCTLoadSourceForBridgeBlock loadSourceForBridge;
@end
#pragma mark - RCTRootViewFactory
/**
* The RCTRootViewFactory is an utility class that encapsulates the logic of creating a new RCTRootView based on the
* current state of the environment. It allows you to initialize your app root view for old architecture, new
* architecture and bridgless mode.
*
* This class is used to initalize rootView in RCTAppDelegate, but you can also use it separately.
*
* Create a new instance of this class (make sure to retain it) and call the
* `viewWithModuleName:initialProperties:launchOptions` method to create new RCTRootView.
*/
@interface RCTRootViewFactory : NSObject
@property (nonatomic, strong, nullable) RCTBridge *bridge;
@property (nonatomic, strong, nullable) RCTHost *reactHost;
@property (nonatomic, strong, nullable) RCTSurfacePresenterBridgeAdapter *bridgeAdapter;
- (instancetype)initWithConfiguration:(RCTRootViewFactoryConfiguration *)configuration
andTurboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate> _Nullable)turboModuleManagerDelegate;
- (instancetype)initWithConfiguration:(RCTRootViewFactoryConfiguration *)configuration;
- (instancetype)initWithTurboModuleDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
hostDelegate:(id<RCTHostDelegate>)hostdelegate
configuration:(RCTRootViewFactoryConfiguration *)configuration;
/**
* This method can be used to create new RCTRootViews on demand.
*
* @parameter: moduleName - the name of the app, used by Metro to resolve the module.
* @parameter: initialProperties - a set of initial properties.
* @parameter: launchOptions - a dictionary with a set of options.
* @parameter: devMenuConfiguration - a configuration for enabling/disabling dev menu.
*/
- (UIView *_Nonnull)viewWithModuleName:(NSString *)moduleName
initialProperties:(NSDictionary *__nullable)initialProperties
launchOptions:(NSDictionary *__nullable)launchOptions
devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration;
- (UIView *_Nonnull)viewWithModuleName:(NSString *)moduleName
initialProperties:(NSDictionary *__nullable)initialProperties
launchOptions:(NSDictionary *__nullable)launchOptions;
- (UIView *_Nonnull)viewWithModuleName:(NSString *)moduleName
initialProperties:(NSDictionary *__nullable)initialProperties;
- (UIView *_Nonnull)viewWithModuleName:(NSString *)moduleName;
#pragma mark - RCTRootViewFactory Helpers
/**
* Initialize React Host/Bridge without creating a view.
*
* Use it to speed up later viewWithModuleName: calls.
*
* @parameter: launchOptions - a dictionary with a set of options.
* @parameter: devMenuConfiguration - a configuration for enabling/disabling dev menu.
*/
- (void)initializeReactHostWithLaunchOptions:(NSDictionary *__nullable)launchOptions
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration;
- (RCTHost *)createReactHost:(NSDictionary *__nullable)launchOptions;
- (RCTHost *)createReactHost:(NSDictionary *__nullable)launchOptions
devMenuConfiguration:(RCTDevMenuConfiguration *__nullable)devMenuConfiguration;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,348 @@
/*
* 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.
*/
#import "RCTRootViewFactory.h"
#import <React/RCTCxxBridgeDelegate.h>
#import <React/RCTDevMenu.h>
#import <React/RCTLog.h>
#import <React/RCTRootView.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <React/RCTUtils.h>
#import <react/renderer/runtimescheduler/RuntimeScheduler.h>
#import "RCTAppDelegate.h"
#import "RCTAppSetupUtils.h"
#if RN_DISABLE_OSS_PLUGIN_HEADER
#import <RCTTurboModulePlugin/RCTTurboModulePlugin.h>
#else
#import <React/CoreModulesPlugins.h>
#endif
#import <React/RCTBundleURLProvider.h>
#import <React/RCTComponentViewFactory.h>
#import <React/RCTComponentViewProtocol.h>
#import <React/RCTFabricSurface.h>
#import <React/RCTSurfaceHostingProxyRootView.h>
#import <React/RCTSurfacePresenter.h>
#import <ReactCommon/RCTHost+Internal.h>
#import <ReactCommon/RCTHost.h>
#import <ReactCommon/RCTTurboModuleManager.h>
#import <react/renderer/runtimescheduler/RuntimeScheduler.h>
#import <react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.h>
#import <react/runtime/JSRuntimeFactory.h>
#import <react/runtime/JSRuntimeFactoryCAPI.h>
@implementation RCTRootViewFactoryConfiguration
- (instancetype)initWithBundleURL:(NSURL *)bundleURL newArchEnabled:(BOOL)newArchEnabled
{
return [self initWithBundleURL:bundleURL];
}
- (instancetype)initWithBundleURLBlock:(RCTBundleURLBlock)bundleURLBlock newArchEnabled:(BOOL)newArchEnabled
{
return [self initWithBundleURLBlock:bundleURLBlock];
}
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
newArchEnabled:(BOOL)newArchEnabled
turboModuleEnabled:(BOOL)turboModuleEnabled
bridgelessEnabled:(BOOL)bridgelessEnabled
{
return [self initWithBundleURLBlock:^{
return bundleURL;
}];
}
- (instancetype)initWithBundleURLBlock:(RCTBundleURLBlock)bundleURLBlock
newArchEnabled:(BOOL)newArchEnabled
turboModuleEnabled:(BOOL)turboModuleEnabled
bridgelessEnabled:(BOOL)bridgelessEnabled
{
if (self = [super init]) {
_bundleURLBlock = bundleURLBlock;
_fabricEnabled = YES;
_turboModuleEnabled = YES;
_bridgelessEnabled = YES;
}
return self;
}
- (instancetype)initWithBundleURLBlock:(RCTBundleURLBlock)bundleURLBlock
{
if (self = [super init]) {
_bundleURLBlock = bundleURLBlock;
_fabricEnabled = YES;
_turboModuleEnabled = YES;
_bridgelessEnabled = YES;
}
return self;
}
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
{
return [self initWithBundleURLBlock:^{
return bundleURL;
}];
}
@end
@interface RCTRootViewFactory () <RCTCxxBridgeDelegate> {
std::shared_ptr<const facebook::react::ContextContainer> _contextContainer;
std::shared_ptr<facebook::react::RuntimeScheduler> _runtimeScheduler;
}
@end
@implementation RCTRootViewFactory {
__weak id<RCTTurboModuleManagerDelegate> _turboModuleManagerDelegate;
__weak id<RCTHostDelegate> _hostDelegate;
RCTRootViewFactoryConfiguration *_configuration;
}
- (instancetype)initWithTurboModuleDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
hostDelegate:(id<RCTHostDelegate>)hostdelegate
configuration:(RCTRootViewFactoryConfiguration *)configuration
{
if (self = [super init]) {
_configuration = configuration;
_hostDelegate = hostdelegate;
_contextContainer = std::make_shared<const facebook::react::ContextContainer>();
_turboModuleManagerDelegate = turboModuleManagerDelegate;
}
return self;
}
- (instancetype)initWithConfiguration:(RCTRootViewFactoryConfiguration *)configuration
andTurboModuleManagerDelegate:(id<RCTTurboModuleManagerDelegate>)turboModuleManagerDelegate
{
id<RCTHostDelegate> hostDelegate = [turboModuleManagerDelegate conformsToProtocol:@protocol(RCTHostDelegate)]
? (id<RCTHostDelegate>)turboModuleManagerDelegate
: nil;
return [self initWithTurboModuleDelegate:turboModuleManagerDelegate
hostDelegate:hostDelegate
configuration:configuration];
}
- (instancetype)initWithConfiguration:(RCTRootViewFactoryConfiguration *)configuration
{
return [self initWithConfiguration:configuration andTurboModuleManagerDelegate:nil];
}
- (UIView *)viewWithModuleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties
{
return [self viewWithModuleName:moduleName
initialProperties:initialProperties
launchOptions:nil
devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
}
- (UIView *)viewWithModuleName:(NSString *)moduleName
{
return [self viewWithModuleName:moduleName
initialProperties:nil
launchOptions:nil
devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
}
- (void)initializeReactHostWithLaunchOptions:(NSDictionary *)launchOptions
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
{
// Enable TurboModule interop by default in Bridgeless mode
RCTEnableTurboModuleInterop(YES);
RCTEnableTurboModuleInteropBridgeProxy(YES);
[self createReactHostIfNeeded:launchOptions devMenuConfiguration:devMenuConfiguration];
return;
}
- (UIView *)viewWithModuleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
launchOptions:(NSDictionary *)launchOptions
{
return [self viewWithModuleName:moduleName
initialProperties:initialProperties
launchOptions:launchOptions
devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
}
- (UIView *)viewWithModuleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initProps
launchOptions:(NSDictionary *)launchOptions
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
{
[self initializeReactHostWithLaunchOptions:launchOptions devMenuConfiguration:devMenuConfiguration];
RCTFabricSurface *surface = [self.reactHost createSurfaceWithModuleName:moduleName
initialProperties:initProps ? initProps : @{}];
RCTSurfaceHostingProxyRootView *surfaceHostingProxyRootView =
[[RCTSurfaceHostingProxyRootView alloc] initWithSurface:surface];
surfaceHostingProxyRootView.backgroundColor = [UIColor systemBackgroundColor];
if (_configuration.customizeRootView != nil) {
_configuration.customizeRootView(surfaceHostingProxyRootView);
}
return surfaceHostingProxyRootView;
}
- (RCTBridge *)createBridgeWithDelegate:(id<RCTBridgeDelegate>)delegate launchOptions:(NSDictionary *)launchOptions
{
return [[RCTBridge alloc] initWithDelegate:delegate launchOptions:launchOptions];
}
- (UIView *)createRootViewWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initProps:(NSDictionary *)initProps
{
UIView *rootView = RCTAppSetupDefaultRootView(bridge, moduleName, initProps, YES);
rootView.backgroundColor = [UIColor systemBackgroundColor];
return rootView;
}
#pragma mark - RCTCxxBridgeDelegate
- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge
{
_runtimeScheduler = std::make_shared<facebook::react::RuntimeScheduler>(RCTRuntimeExecutorFromBridge(bridge));
std::shared_ptr<facebook::react::CallInvoker> callInvoker =
std::make_shared<facebook::react::RuntimeSchedulerCallInvoker>(_runtimeScheduler);
RCTTurboModuleManager *turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
delegate:_turboModuleManagerDelegate
jsInvoker:callInvoker];
_contextContainer->erase(facebook::react::RuntimeSchedulerKey);
_contextContainer->insert(facebook::react::RuntimeSchedulerKey, _runtimeScheduler);
return RCTAppSetupDefaultJsExecutorFactory(bridge, turboModuleManager, _runtimeScheduler);
}
- (void)createBridgeIfNeeded:(NSDictionary *)launchOptions
{
if (self.bridge != nil) {
return;
}
if (self->_configuration.createBridgeWithDelegate != nil) {
self.bridge = self->_configuration.createBridgeWithDelegate(self, launchOptions);
} else {
self.bridge = [self createBridgeWithDelegate:self launchOptions:launchOptions];
}
}
- (void)createBridgeAdapterIfNeeded
{
if (self.bridgeAdapter != nullptr) {
return;
}
self.bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:self.bridge
contextContainer:_contextContainer];
self.bridge.surfacePresenter = self.bridgeAdapter.surfacePresenter;
}
#pragma mark - New Arch Utilities
- (void)createReactHostIfNeeded:(NSDictionary *)launchOptions
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
{
if (self.reactHost) {
return;
}
self.reactHost = [self createReactHost:launchOptions devMenuConfiguration:devMenuConfiguration];
}
- (RCTHost *)createReactHost:(NSDictionary *)launchOptions
{
return [self createReactHost:launchOptions devMenuConfiguration:[RCTDevMenuConfiguration defaultConfiguration]];
}
- (RCTHost *)createReactHost:(NSDictionary *)launchOptions
devMenuConfiguration:(RCTDevMenuConfiguration *)devMenuConfiguration
{
__weak __typeof(self) weakSelf = self;
RCTHost *reactHost =
[[RCTHost alloc] initWithBundleURLProvider:self->_configuration.bundleURLBlock
hostDelegate:_hostDelegate
turboModuleManagerDelegate:_turboModuleManagerDelegate
jsEngineProvider:^std::shared_ptr<facebook::react::JSRuntimeFactory>() {
return [weakSelf createJSRuntimeFactory];
}
launchOptions:launchOptions
devMenuConfiguration:devMenuConfiguration];
[reactHost setBundleURLProvider:^NSURL *() {
return [weakSelf bundleURL];
}];
[reactHost start];
return reactHost;
}
- (std::shared_ptr<facebook::react::JSRuntimeFactory>)createJSRuntimeFactory
{
if (_configuration.jsRuntimeConfiguratorDelegate == nil) {
[NSException raise:@"RCTReactNativeFactoryDelegate::createJSRuntimeFactory not implemented"
format:@"Delegate must implement a valid createJSRuntimeFactory method"];
return nullptr;
}
auto jsRuntimeFactory = [_configuration.jsRuntimeConfiguratorDelegate createJSRuntimeFactory];
return std::shared_ptr<facebook::react::JSRuntimeFactory>(
reinterpret_cast<facebook::react::JSRuntimeFactory *>(jsRuntimeFactory), &js_runtime_factory_destroy);
}
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{
if (_configuration.extraModulesForBridge != nil) {
return _configuration.extraModulesForBridge(bridge);
}
return nil;
}
- (NSDictionary<NSString *, Class> *)extraLazyModuleClassesForBridge:(RCTBridge *)bridge
{
if (_configuration.extraLazyModuleClassesForBridge != nil) {
return _configuration.extraLazyModuleClassesForBridge(bridge);
}
return nil;
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
if (_configuration.sourceURLForBridge != nil) {
return _configuration.sourceURLForBridge(bridge);
}
return [self bundleURL];
}
- (BOOL)bridge:(RCTBridge *)bridge didNotFindModule:(NSString *)moduleName
{
if (_configuration.bridgeDidNotFindModule != nil) {
return _configuration.bridgeDidNotFindModule(bridge, moduleName);
}
return NO;
}
- (void)loadSourceForBridge:(RCTBridge *)bridge withBlock:(RCTSourceLoadBlock)loadCallback
{
if (_configuration.loadSourceForBridge != nil) {
_configuration.loadSourceForBridge(bridge, loadCallback);
}
}
- (void)loadSourceForBridge:(RCTBridge *)bridge
onProgress:(RCTSourceLoadProgressBlock)onProgress
onComplete:(RCTSourceLoadBlock)loadCallback
{
if (_configuration.loadSourceForBridgeWithProgress != nil) {
_configuration.loadSourceForBridgeWithProgress(bridge, onProgress, loadCallback);
}
}
- (NSURL *)bundleURL
{
return self->_configuration.bundleURLBlock();
}
@end

View File

@@ -0,0 +1,60 @@
/*
* 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.
*/
#import <React/RCTConvert.h>
#import <UIKit/UIKit.h>
@class RCTRootView;
NS_ASSUME_NONNULL_BEGIN
@protocol RCTUIConfiguratorProtocol
/**
* The default `RCTColorSpace` for the app. It defaults to `RCTColorSpaceSRGB`.
*/
- (RCTColorSpace)defaultColorSpace;
/**
* This method can be used to customize the rootView that is passed to React Native.
* A typical example is to override this method in the AppDelegate to change the background color.
* To achieve this, add in your `AppDelegate.mm`:
* ```
* - (void)customizeRootView:(RCTRootView *)rootView
* {
* rootView.backgroundColor = [UIColor colorWithDynamicProvider:^UIColor *(UITraitCollection *traitCollection) {
* if ([traitCollection userInterfaceStyle] == UIUserInterfaceStyleDark) {
* return [UIColor blackColor];
* } else {
* return [UIColor whiteColor];
* }
* }];
* }
* ```
*
* @parameter: rootView - The root view to customize.
*/
- (void)customizeRootView:(RCTRootView *)rootView;
/**
* It creates the RootViewController.
* By default, it creates a new instance of a `UIViewController`.
* You can override it to provide your own initial ViewController.
*
* @return: an instance of `UIViewController`.
*/
- (UIViewController *)createRootViewController;
/**
* It assigns the rootView to the rootViewController
* By default, it assigns the rootView to the view property of the rootViewController
* If you are not using a simple UIViewController, then there could be other methods to use to setup the rootView.
* For example: UISplitViewController requires `setViewController(_:for:)`
*/
- (void)setRootView:(UIView *)rootView toRootViewController:(UIViewController *)rootViewController;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,88 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
is_new_arch_enabled = ENV["RCT_NEW_ARCH_ENABLED"] != "0"
new_arch_enabled_flag = (is_new_arch_enabled ? " -DRCT_NEW_ARCH_ENABLED=1" : "")
other_cflags = "$(inherited) " + new_arch_enabled_flag + js_engine_flags()
header_search_paths = [
"$(PODS_TARGET_SRCROOT)/../../ReactCommon",
"$(PODS_ROOT)/Headers/Private/React-Core",
"${PODS_ROOT}/Headers/Public/FlipperKit",
"$(PODS_ROOT)/Headers/Public/ReactCommon",
"$(PODS_ROOT)/Headers/Public/React-RCTFabric",
"$(PODS_ROOT)/Headers/Private/Yoga",
].concat(use_hermes() ? [
"$(PODS_ROOT)/Headers/Public/React-hermes",
"$(PODS_ROOT)/Headers/Public/hermes-engine"
] : [])
Pod::Spec.new do |s|
s.name = "React-RCTAppDelegate"
s.version = version
s.summary = "An utility library to simplify common operations for the New Architecture"
s.homepage = "https://reactnative.dev/"
s.documentation_url = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("**/*.{c,h,m,mm,S,cpp}", "**/*.h")
# This guard prevent to install the dependencies when we run `pod install` in the old architecture.
s.compiler_flags = other_cflags
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => header_search_paths,
"OTHER_CPLUSPLUSFLAGS" => other_cflags,
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"DEFINES_MODULE" => "YES"
}
s.user_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/Headers/Private/React-Core\" \"$(PODS_ROOT)/Headers/Private/Yoga\""}
s.dependency "React-Core"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "React-RCTNetwork"
s.dependency "React-RCTImage"
s.dependency "React-CoreModules"
s.dependency "React-RCTFBReactNativeSpec"
s.dependency "React-defaultsnativemodule"
if use_hermes()
s.dependency 'React-hermes'
end
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
add_dependency(s, "React-NativeModulesApple")
add_dependency(s, "React-runtimescheduler")
add_dependency(s, "React-RCTFabric", :framework_name => "RCTFabric")
add_dependency(s, "React-RuntimeCore")
add_dependency(s, "React-RuntimeApple")
add_dependency(s, "React-Fabric", :additional_framework_paths => ["react/renderer/components/view/platform/cxx"])
add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"])
add_dependency(s, "React-utils")
add_dependency(s, "React-debug")
add_dependency(s, "React-rendererdebug")
add_dependency(s, "React-featureflags")
add_dependency(s, "React-jsitooling", :framework_name => "JSITooling")
add_dependency(s, "React-RCTRuntime", :framework_name => "RCTRuntime")
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

View File

@@ -0,0 +1,58 @@
/**
* 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 {NativeEventSubscription} from '../EventEmitter/RCTNativeAppEventEmitter';
/**
* AppState can tell you if the app is in the foreground or background,
* and notify you when the state changes.
*
* AppState is frequently used to determine the intent and proper behavior
* when handling push notifications.
*
* App State Events
* change - This even is received when the app state has changed.
* focus [Android] - Received when the app gains focus (the user is interacting with the app).
* blur [Android] - Received when the user is not actively interacting with the app.
*
* App States
* active - The app is running in the foreground
* background - The app is running in the background. The user is either in another app or on the home screen
* inactive [iOS] - This is a transition state that happens when the app launches, is asking for permissions or when a call or SMS message is received.
* unknown [iOS] - Initial value until the current app state is determined
* extension [iOS] - The app is running as an app extension
*
* For more information, see Apple's documentation: https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html
*
* @see https://reactnative.dev/docs/appstate#app-states
*/
export type AppStateEvent = 'change' | 'memoryWarning' | 'blur' | 'focus';
export type AppStateStatus =
| 'active'
| 'background'
| 'inactive'
| 'unknown'
| 'extension';
export interface AppStateStatic {
currentState: AppStateStatus;
isAvailable: boolean;
/**
* Add a handler to AppState changes by listening to the change event
* type and providing the handler
*/
addEventListener(
type: AppStateEvent,
listener: (state: AppStateStatus) => void,
): NativeEventSubscription;
}
export const AppState: AppStateStatic;
export type AppState = AppStateStatic;

View File

@@ -0,0 +1,154 @@
/**
* 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 NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
import logError from '../Utilities/logError';
import Platform from '../Utilities/Platform';
import {type EventSubscription} from '../vendor/emitter/EventEmitter';
import NativeAppState from './NativeAppState';
/**
* active - The app is running in the foreground
* background - The app is running in the background. The user is either:
* - in another app
* - on the home screen
* - @platform android - on another Activity (even if it was launched by your app)
* @platform ios - inactive - This is a state that occurs when transitioning between foreground & background, and during periods of inactivity such as entering the multitasking view, opening the Notification Center or in the event of an incoming call.
*/
export type AppStateStatus =
| 'inactive'
| 'background'
| 'active'
| 'extension'
| 'unknown';
/**
* change - This even is received when the app state has changed.
* memoryWarning - This event is used in the need of throwing memory warning or releasing it.
* @platform android - focus - Received when the app gains focus (the user is interacting with the app).
* @platform android - blur - Received when the user is not actively interacting with the app.
*/
type AppStateEventDefinitions = {
change: [AppStateStatus],
memoryWarning: [],
blur: [],
focus: [],
};
export type AppStateEvent = $Keys<AppStateEventDefinitions>;
type NativeAppStateEventDefinitions = {
appStateDidChange: [{app_state: AppStateStatus}],
appStateFocusChange: [boolean],
memoryWarning: [],
};
/**
* `AppState` can tell you if the app is in the foreground or background,
* and notify you when the state changes.
*
* See https://reactnative.dev/docs/appstate
*/
class AppStateImpl {
currentState: ?string = null;
isAvailable: boolean;
_emitter: ?NativeEventEmitter<NativeAppStateEventDefinitions>;
constructor() {
if (NativeAppState == null) {
this.isAvailable = false;
} else {
this.isAvailable = true;
const emitter: NativeEventEmitter<NativeAppStateEventDefinitions> =
new NativeEventEmitter(
// T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior
// If you want to use the native module on other platforms, please remove this condition and test its behavior
Platform.OS !== 'ios' ? null : NativeAppState,
);
this._emitter = emitter;
this.currentState = NativeAppState.getConstants().initialAppState;
let eventUpdated = false;
// TODO: this is a terrible solution - in order to ensure `currentState`
// prop is up to date, we have to register an observer that updates it
// whenever the state changes, even if nobody cares. We should just
// deprecate the `currentState` property and get rid of this.
emitter.addListener('appStateDidChange', appStateData => {
eventUpdated = true;
this.currentState = appStateData.app_state;
});
// TODO: see above - this request just populates the value of `currentState`
// when the module is first initialized. Would be better to get rid of the
// prop and expose `getCurrentAppState` method directly.
// $FlowExpectedError[incompatible-call]
NativeAppState.getCurrentAppState(appStateData => {
// It's possible that the state will have changed here & listeners need to be notified
if (!eventUpdated && this.currentState !== appStateData.app_state) {
this.currentState = appStateData.app_state;
// $FlowFixMe[incompatible-type]
emitter.emit('appStateDidChange', appStateData);
}
}, logError);
}
}
/**
* Add a handler to AppState changes by listening to the `change` event type
* and providing the handler.
*
* See https://reactnative.dev/docs/appstate#addeventlistener
*/
addEventListener<K: AppStateEvent>(
type: K,
handler: (...AppStateEventDefinitions[K]) => void,
): EventSubscription {
const emitter = this._emitter;
if (emitter == null) {
throw new Error('Cannot use AppState when `isAvailable` is false.');
}
switch (type) {
case 'change':
// $FlowFixMe[invalid-tuple-arity] Flow cannot refine handler based on the event type
const changeHandler: AppStateStatus => void = handler;
return emitter.addListener('appStateDidChange', appStateData => {
changeHandler(appStateData.app_state);
});
case 'memoryWarning':
// $FlowFixMe[invalid-tuple-arity] Flow cannot refine handler based on the event type
const memoryWarningHandler: () => void = handler;
return emitter.addListener('memoryWarning', memoryWarningHandler);
case 'blur':
case 'focus':
// $FlowFixMe[invalid-tuple-arity] Flow cannot refine handler based on the event type
const focusOrBlurHandler: () => void = handler;
return emitter.addListener('appStateFocusChange', hasFocus => {
/* $FlowFixMe[invalid-compare] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/4oq3zi07. */
if (type === 'blur' && !hasFocus) {
focusOrBlurHandler();
}
/* $FlowFixMe[invalid-compare] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/4oq3zi07. */
if (type === 'focus' && hasFocus) {
focusOrBlurHandler();
}
});
}
throw new Error('Trying to subscribe to unknown event: ' + type);
}
}
const AppState: AppStateImpl = new AppStateImpl();
export default AppState;

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/NativeAppState';
import NativeAppState from '../../src/private/specs_DEPRECATED/modules/NativeAppState';
export default NativeAppState;

View File

@@ -0,0 +1,31 @@
/**
* 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
* @deprecated
*/
'use strict';
import typeof MessageQueueT from './MessageQueue';
const MessageQueue: MessageQueueT = require('./MessageQueue').default;
const BatchedBridge: MessageQueue = new MessageQueue();
// Wire up the batched bridge on the global object so that we can call into it.
// Ideally, this would be the inverse relationship. I.e. the native environment
// provides this global directly with its script embedded. Then this module
// would export it. A possible fix would be to trim the dependencies in
// MessageQueue to its minimal features and embed that in the native runtime.
Object.defineProperty(global, '__fbBatchedBridge', {
configurable: true,
value: BatchedBridge,
});
export default BatchedBridge;

View File

@@ -0,0 +1,498 @@
/**
* 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
* @deprecated
*/
'use strict';
const Systrace = require('../Performance/Systrace');
const deepFreezeAndThrowOnMutationInDev =
require('../Utilities/deepFreezeAndThrowOnMutationInDev').default;
const stringifySafe = require('../Utilities/stringifySafe').default;
const warnOnce = require('../Utilities/warnOnce').default;
const ErrorUtils = require('../vendor/core/ErrorUtils').default;
const invariant = require('invariant');
export type SpyData = {
type: number,
module: ?string,
method: string | number,
args: mixed[],
...
};
const TO_JS = 0;
const TO_NATIVE = 1;
const MODULE_IDS = 0;
const METHOD_IDS = 1;
const PARAMS = 2;
const MIN_TIME_BETWEEN_FLUSHES_MS = 5;
// eslint-disable-next-line no-bitwise
const TRACE_TAG_REACT = 1 << 13;
const DEBUG_INFO_LIMIT = 32;
class MessageQueue {
_lazyCallableModules: {[key: string]: (void) => {...}, ...};
_queue: [number[], number[], mixed[], number];
_successCallbacks: Map<number, ?(...mixed[]) => void>;
_failureCallbacks: Map<number, ?(...mixed[]) => void>;
_callID: number;
_lastFlush: number;
_eventLoopStartTime: number;
_reactNativeMicrotasksCallback: ?() => void;
_debugInfo: {[number]: [number, number], ...};
_remoteModuleTable: {[number]: string, ...};
_remoteMethodTable: {[number]: $ReadOnlyArray<string>, ...};
__spy: ?(data: SpyData) => void;
constructor() {
this._lazyCallableModules = {};
this._queue = [[], [], [], 0];
this._successCallbacks = new Map();
this._failureCallbacks = new Map();
this._callID = 0;
this._lastFlush = 0;
this._eventLoopStartTime = Date.now();
this._reactNativeMicrotasksCallback = null;
if (__DEV__) {
this._debugInfo = {};
this._remoteModuleTable = {};
this._remoteMethodTable = {};
}
// $FlowFixMe[cannot-write]
this.callFunctionReturnFlushedQueue =
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
this.callFunctionReturnFlushedQueue.bind(this);
// $FlowFixMe[cannot-write]
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
this.flushedQueue = this.flushedQueue.bind(this);
// $FlowFixMe[cannot-write]
this.invokeCallbackAndReturnFlushedQueue =
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
this.invokeCallbackAndReturnFlushedQueue.bind(this);
}
/**
* Public APIs
*/
static spy(spyOrToggle: boolean | ((data: SpyData) => void)) {
if (spyOrToggle === true) {
MessageQueue.prototype.__spy = info => {
console.log(
`${info.type === TO_JS ? 'N->JS' : 'JS->N'} : ` +
`${info.module != null ? info.module + '.' : ''}${info.method}` +
`(${JSON.stringify(info.args)})`,
);
};
} else if (spyOrToggle === false) {
MessageQueue.prototype.__spy = null;
} else {
MessageQueue.prototype.__spy = spyOrToggle;
}
}
callFunctionReturnFlushedQueue(
module: string,
method: string,
args: mixed[],
): null | [Array<number>, Array<number>, Array<mixed>, number] {
this.__guard(() => {
this.__callFunction(module, method, args);
});
return this.flushedQueue();
}
invokeCallbackAndReturnFlushedQueue(
cbID: number,
args: mixed[],
): null | [Array<number>, Array<number>, Array<mixed>, number] {
this.__guard(() => {
this.__invokeCallback(cbID, args);
});
return this.flushedQueue();
}
flushedQueue(): null | [Array<number>, Array<number>, Array<mixed>, number] {
this.__guard(() => {
this.__callReactNativeMicrotasks();
});
const queue = this._queue;
this._queue = [[], [], [], this._callID];
return queue[0].length ? queue : null;
}
getEventLoopRunningTime(): number {
return Date.now() - this._eventLoopStartTime;
}
registerCallableModule(name: string, module: {...}) {
this._lazyCallableModules[name] = () => module;
}
registerLazyCallableModule(name: string, factory: void => interface {}) {
let module: interface {};
let getValue: ?(void) => interface {} = factory;
this._lazyCallableModules[name] = () => {
if (getValue) {
module = getValue();
getValue = null;
}
/* $FlowFixMe[class-object-subtyping] added when improving typing for
* this parameters */
return module;
};
}
getCallableModule(name: string): {...} | null {
const getValue = this._lazyCallableModules[name];
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
return getValue ? getValue() : null;
}
callNativeSyncHook(
moduleID: number,
methodID: number,
params: mixed[],
onFail: ?(...mixed[]) => void,
onSucc: ?(...mixed[]) => void,
): mixed {
if (__DEV__) {
invariant(
global.nativeCallSyncHook,
'Calling synchronous methods on native ' +
'modules is not supported in Chrome.\n\n Consider providing alternative ' +
'methods to expose this method in debug mode, e.g. by exposing constants ' +
'ahead-of-time.',
);
}
this.processCallbacks(moduleID, methodID, params, onFail, onSucc);
return global.nativeCallSyncHook(moduleID, methodID, params);
}
processCallbacks(
moduleID: number,
methodID: number,
params: mixed[],
onFail: ?(...mixed[]) => void,
onSucc: ?(...mixed[]) => void,
): void {
if (onFail || onSucc) {
if (__DEV__) {
this._debugInfo[this._callID] = [moduleID, methodID];
if (this._callID > DEBUG_INFO_LIMIT) {
delete this._debugInfo[this._callID - DEBUG_INFO_LIMIT];
}
if (this._successCallbacks.size > 500) {
const info: {[number]: {method: string, module: string}} = {};
this._successCallbacks.forEach((_, callID) => {
const debug = this._debugInfo[callID];
const module = debug && this._remoteModuleTable[debug[0]];
const method = debug && this._remoteMethodTable[debug[0]][debug[1]];
info[callID] = {module, method};
});
warnOnce(
'excessive-number-of-pending-callbacks',
`Excessive number of pending callbacks: ${
this._successCallbacks.size
}. Some pending callbacks that might have leaked by never being called from native code: ${stringifySafe(
info,
)}`,
);
}
}
// Encode callIDs into pairs of callback identifiers by shifting left and using the rightmost bit
// to indicate fail (0) or success (1)
// eslint-disable-next-line no-bitwise
onFail && params.push(this._callID << 1);
// eslint-disable-next-line no-bitwise
onSucc && params.push((this._callID << 1) | 1);
this._successCallbacks.set(this._callID, onSucc);
this._failureCallbacks.set(this._callID, onFail);
}
if (__DEV__) {
global.nativeTraceBeginAsyncFlow &&
global.nativeTraceBeginAsyncFlow(
TRACE_TAG_REACT,
'native',
this._callID,
);
}
this._callID++;
}
enqueueNativeCall(
moduleID: number,
methodID: number,
params: mixed[],
onFail: ?(...mixed[]) => void,
onSucc: ?(...mixed[]) => void,
): void {
this.processCallbacks(moduleID, methodID, params, onFail, onSucc);
this._queue[MODULE_IDS].push(moduleID);
this._queue[METHOD_IDS].push(methodID);
if (__DEV__) {
// Validate that parameters passed over the bridge are
// folly-convertible. As a special case, if a prop value is a
// function it is permitted here, and special-cased in the
// conversion.
const isValidArgument = (val: mixed): boolean => {
switch (typeof val) {
case 'undefined':
case 'boolean':
case 'string':
return true;
case 'number':
return isFinite(val);
case 'object':
if (val == null) {
return true;
}
if (Array.isArray(val)) {
return val.every(isValidArgument);
}
for (const k in val) {
if (typeof val[k] !== 'function' && !isValidArgument(val[k])) {
return false;
}
}
return true;
case 'function':
return false;
default:
return false;
}
};
// Replacement allows normally non-JSON-convertible values to be
// seen. There is ambiguity with string values, but in context,
// it should at least be a strong hint.
const replacer = (key: string, val: $FlowFixMe) => {
const t = typeof val;
if (t === 'function') {
return '<<Function ' + val.name + '>>';
} else if (t === 'number' && !isFinite(val)) {
return '<<' + val.toString() + '>>';
} else {
return val;
}
};
// Note that JSON.stringify
invariant(
isValidArgument(params),
'%s is not usable as a native method argument',
JSON.stringify(params, replacer),
);
// The params object should not be mutated after being queued
deepFreezeAndThrowOnMutationInDev(params);
}
this._queue[PARAMS].push(params);
const now = Date.now();
if (
global.nativeFlushQueueImmediate &&
now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS
) {
const queue = this._queue;
this._queue = [[], [], [], this._callID];
this._lastFlush = now;
global.nativeFlushQueueImmediate(queue);
}
Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
if (__DEV__ && this.__spy && isFinite(moduleID)) {
// $FlowFixMe[not-a-function]
this.__spy({
type: TO_NATIVE,
module: this._remoteModuleTable[moduleID],
method: this._remoteMethodTable[moduleID][methodID],
args: params,
});
} else if (this.__spy) {
this.__spy({
type: TO_NATIVE,
module: moduleID + '',
method: methodID,
args: params,
});
}
}
createDebugLookup(
moduleID: number,
name: string,
methods: ?$ReadOnlyArray<string>,
) {
if (__DEV__) {
this._remoteModuleTable[moduleID] = name;
this._remoteMethodTable[moduleID] = methods || [];
}
}
// For JSTimers to register its callback. Otherwise a circular dependency
// between modules is introduced. Note that only one callback may be
// registered at a time.
setReactNativeMicrotasksCallback(fn: () => void) {
this._reactNativeMicrotasksCallback = fn;
}
/**
* Private methods
*/
__guard(fn: () => void) {
if (this.__shouldPauseOnThrow()) {
fn();
} else {
try {
fn();
} catch (error) {
ErrorUtils.reportFatalError(error);
}
}
}
// MessageQueue installs a global handler to catch all exceptions where JS users can register their own behavior
// This handler makes all exceptions to be propagated from inside MessageQueue rather than by the VM at their origin
// This makes stacktraces to be placed at MessageQueue rather than at where they were launched
// The parameter DebuggerInternal.shouldPauseOnThrow is used to check before catching all exceptions and
// can be configured by the VM or any Inspector
__shouldPauseOnThrow(): boolean {
return (
// $FlowFixMe[cannot-resolve-name]
typeof DebuggerInternal !== 'undefined' &&
// $FlowFixMe[cannot-resolve-name]
DebuggerInternal.shouldPauseOnThrow === true
);
}
__callReactNativeMicrotasks() {
Systrace.beginEvent('JSTimers.callReactNativeMicrotasks()');
try {
if (this._reactNativeMicrotasksCallback != null) {
this._reactNativeMicrotasksCallback();
}
} finally {
Systrace.endEvent();
}
}
__callFunction(module: string, method: string, args: mixed[]): void {
this._lastFlush = Date.now();
this._eventLoopStartTime = this._lastFlush;
if (__DEV__ || this.__spy) {
Systrace.beginEvent(`${module}.${method}(${stringifySafe(args)})`);
} else {
Systrace.beginEvent(`${module}.${method}(...)`);
}
try {
if (this.__spy) {
this.__spy({type: TO_JS, module, method, args});
}
const moduleMethods = this.getCallableModule(module);
if (!moduleMethods) {
const callableModuleNames = Object.keys(this._lazyCallableModules);
const n = callableModuleNames.length;
const callableModuleNameList = callableModuleNames.join(', ');
// TODO(T122225939): Remove after investigation: Why are we getting to this line in bridgeless mode?
const isBridgelessMode =
global.RN$Bridgeless === true ? 'true' : 'false';
invariant(
false,
`Failed to call into JavaScript module method ${module}.${method}(). Module has not been registered as callable. Bridgeless Mode: ${isBridgelessMode}. Registered callable JavaScript modules (n = ${n}): ${callableModuleNameList}.
A frequent cause of the error is that the application entry file path is incorrect. This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native.`,
);
}
// $FlowFixMe[invalid-computed-prop]
if (!moduleMethods[method]) {
invariant(
false,
`Failed to call into JavaScript module method ${module}.${method}(). Module exists, but the method is undefined.`,
);
}
moduleMethods[method].apply(moduleMethods, args);
} finally {
Systrace.endEvent();
}
}
__invokeCallback(cbID: number, args: mixed[]): void {
this._lastFlush = Date.now();
this._eventLoopStartTime = this._lastFlush;
// The rightmost bit of cbID indicates fail (0) or success (1), the other bits are the callID shifted left.
// eslint-disable-next-line no-bitwise
const callID = cbID >>> 1;
// eslint-disable-next-line no-bitwise
const isSuccess = cbID & 1;
const callback = isSuccess
? this._successCallbacks.get(callID)
: this._failureCallbacks.get(callID);
if (__DEV__) {
const debug = this._debugInfo[callID];
const module = debug && this._remoteModuleTable[debug[0]];
const method = debug && this._remoteMethodTable[debug[0]][debug[1]];
invariant(
callback,
`No callback found with cbID ${cbID} and callID ${callID} for ` +
(method
? ` ${module}.${method} - most likely the callback was already invoked`
: `module ${module || '<unknown>'}`) +
`. Args: '${stringifySafe(args)}'`,
);
const profileName = debug
? '<callback for ' + module + '.' + method + '>'
: cbID;
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
if (callback && this.__spy) {
this.__spy({type: TO_JS, module: null, method: profileName, args});
}
Systrace.beginEvent(
`MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`,
);
}
try {
if (!callback) {
return;
}
this._successCallbacks.delete(callID);
this._failureCallbacks.delete(callID);
callback(...args);
} finally {
if (__DEV__) {
Systrace.endEvent();
}
}
}
}
export default MessageQueue;

View File

@@ -0,0 +1,25 @@
/**
* 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
*/
/**
* Interface for NativeModules which allows to augment NativeModules with type information.
* See react-native-sensor-manager for example.
*/
interface NativeModulesStatic {
[name: string]: any;
}
/**
* Native Modules written in ObjectiveC/Swift/Java exposed via the RCTBridge
* Define lazy getters for each module. These will return the module if already loaded, or load it if not.
* See https://reactnative.dev/docs/native-modules-ios
* @example
* const MyModule = NativeModules.ModuleName
*/
export const NativeModules: NativeModulesStatic;

View File

@@ -0,0 +1,216 @@
/**
* 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';
import type {ExtendedError} from '../Core/ExtendedError';
const BatchedBridge = require('./BatchedBridge').default;
const invariant = require('invariant');
export type ModuleConfig = [
string /* name */,
?{...} /* constants */,
?$ReadOnlyArray<string> /* functions */,
?$ReadOnlyArray<number> /* promise method IDs */,
?$ReadOnlyArray<number> /* sync method IDs */,
];
export type MethodType = 'async' | 'promise' | 'sync';
function genModule(
config: ?ModuleConfig,
moduleID: number,
): ?{
name: string,
module?: {...},
...
} {
if (!config) {
return null;
}
const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
invariant(
!moduleName.startsWith('RCT') && !moduleName.startsWith('RK'),
"Module name prefixes should've been stripped by the native side " +
"but wasn't for " +
moduleName,
);
if (!constants && !methods) {
// Module contents will be filled in lazily later
return {name: moduleName};
}
const module: {[string]: mixed} = {};
methods &&
methods.forEach((methodName, methodID) => {
const isPromise =
(promiseMethods && arrayContains(promiseMethods, methodID)) || false;
const isSync =
(syncMethods && arrayContains(syncMethods, methodID)) || false;
invariant(
!isPromise || !isSync,
'Cannot have a method that is both async and a sync hook',
);
const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
module[methodName] = genMethod(moduleID, methodID, methodType);
});
// $FlowFixMe[unsafe-object-assign]
Object.assign(module, constants);
if (module.getConstants == null) {
module.getConstants = () => constants || Object.freeze({});
} else {
console.warn(
`Unable to define method 'getConstants()' on NativeModule '${moduleName}'. NativeModule '${moduleName}' already has a constant or method called 'getConstants'. Please remove it.`,
);
}
if (__DEV__) {
BatchedBridge.createDebugLookup(moduleID, moduleName, methods);
}
return {name: moduleName, module};
}
// export this method as a global so we can call it from native
global.__fbGenNativeModule = genModule;
function loadModule(name: string, moduleID: number): ?{...} {
invariant(
global.nativeRequireModuleConfig,
"Can't lazily create module without nativeRequireModuleConfig",
);
const config = global.nativeRequireModuleConfig(name);
const info = genModule(config, moduleID);
return info && info.module;
}
function genMethod(moduleID: number, methodID: number, type: MethodType) {
let fn = null;
if (type === 'promise') {
fn = function promiseMethodWrapper(...args: Array<mixed>) {
// In case we reject, capture a useful stack trace here.
/* $FlowFixMe[class-object-subtyping] added when improving typing for
* this parameters */
// $FlowFixMe[incompatible-type]
const enqueueingFrameError: ExtendedError = new Error();
return new Promise((resolve, reject) => {
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
args,
data => resolve(data),
errorData =>
reject(
updateErrorWithErrorData(
(errorData: $FlowFixMe),
enqueueingFrameError,
),
),
);
});
};
} else {
fn = function nonPromiseMethodWrapper(...args: Array<mixed>) {
const lastArg = args.length > 0 ? args[args.length - 1] : null;
const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
const hasSuccessCallback = typeof lastArg === 'function';
const hasErrorCallback = typeof secondLastArg === 'function';
hasErrorCallback &&
invariant(
hasSuccessCallback,
'Cannot have a non-function arg after a function arg.',
);
// $FlowFixMe[incompatible-type]
const onSuccess: ?(mixed) => void = hasSuccessCallback ? lastArg : null;
// $FlowFixMe[incompatible-type]
const onFail: ?(mixed) => void = hasErrorCallback ? secondLastArg : null;
// $FlowFixMe[unsafe-addition]
const callbackCount = hasSuccessCallback + hasErrorCallback;
const newArgs = args.slice(0, args.length - callbackCount);
if (type === 'sync') {
return BatchedBridge.callNativeSyncHook(
moduleID,
methodID,
newArgs,
onFail,
onSuccess,
);
} else {
BatchedBridge.enqueueNativeCall(
moduleID,
methodID,
newArgs,
onFail,
onSuccess,
);
}
};
}
// $FlowFixMe[prop-missing]
fn.type = type;
return fn;
}
function arrayContains<T>(array: $ReadOnlyArray<T>, value: T): boolean {
return array.indexOf(value) !== -1;
}
function updateErrorWithErrorData(
errorData: {message: string, ...},
error: ExtendedError,
): ExtendedError {
/* $FlowFixMe[class-object-subtyping] added when improving typing for this
* parameters */
// $FlowFixMe[incompatible-type]
// $FlowFixMe[unsafe-object-assign]
return Object.assign(error, errorData || {});
}
/* $FlowFixMe[unclear-type] unclear type of NativeModules */
let NativeModules: {[moduleName: string]: any, ...} = {};
if (global.nativeModuleProxy) {
NativeModules = global.nativeModuleProxy;
} else {
const bridgeConfig = global.__fbBatchedBridgeConfig;
invariant(
bridgeConfig,
'__fbBatchedBridgeConfig is not set, cannot invoke native modules',
);
const defineLazyObjectProperty =
require('../Utilities/defineLazyObjectProperty').default;
(bridgeConfig.remoteModuleConfig || []).forEach(
(config: ModuleConfig, moduleID: number) => {
// Initially this config will only contain the module name when running in JSC. The actual
// configuration of the module will be lazily loaded.
const info = genModule(config, moduleID);
if (!info) {
return;
}
if (info.module) {
NativeModules[info.name] = info.module;
}
// If there's no module config, define a lazy getter
else {
defineLazyObjectProperty(NativeModules, info.name, {
get: () => loadModule(info.name, moduleID),
});
}
},
);
}
export default NativeModules;

158
node_modules/react-native/Libraries/Blob/Blob.js generated vendored Normal file
View File

@@ -0,0 +1,158 @@
/**
* 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 {BlobData, BlobOptions} from './BlobTypes';
/**
* Opaque JS representation of some binary data in native.
*
* The API is modeled after the W3C Blob API, with one caveat
* regarding explicit deallocation. Refer to the `close()`
* method for further details.
*
* Example usage in a React component:
*
* class WebSocketImage extends React.Component {
* state = {blob: null};
* componentDidMount() {
* let ws = this.ws = new WebSocket(...);
* ws.binaryType = 'blob';
* ws.onmessage = (event) => {
* if (this.state.blob) {
* this.state.blob.close();
* }
* this.setState({blob: event.data});
* };
* }
* componentUnmount() {
* if (this.state.blob) {
* this.state.blob.close();
* }
* this.ws.close();
* }
* render() {
* if (!this.state.blob) {
* return <View />;
* }
* return <Image source={{uri: URL.createObjectURL(this.state.blob)}} />;
* }
* }
*
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob
*/
class Blob {
_data: ?BlobData;
/**
* Constructor for JS consumers.
* Currently we only support creating Blobs from other Blobs.
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob
*/
constructor(parts: Array<Blob | string> = [], options?: BlobOptions) {
const BlobManager = require('./BlobManager').default;
this.data = BlobManager.createFromParts(parts, options).data;
}
/*
* This method is used to create a new Blob object containing
* the data in the specified range of bytes of the source Blob.
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice
*/
// $FlowFixMe[unsafe-getters-setters]
set data(data: ?BlobData) {
this._data = data;
}
// $FlowFixMe[unsafe-getters-setters]
get data(): BlobData {
if (!this._data) {
throw new Error('Blob has been closed and is no longer available');
}
return this._data;
}
slice(start?: number, end?: number, contentType?: string = ''): Blob {
const BlobManager = require('./BlobManager').default;
let {offset, size} = this.data;
if (typeof start === 'number') {
if (start > size) {
// $FlowFixMe[reassign-const]
start = size;
}
offset += start;
size -= start;
if (typeof end === 'number') {
if (end < 0) {
// $FlowFixMe[reassign-const]
end = this.size + end;
}
if (end > this.size) {
// $FlowFixMe[reassign-const]
end = this.size;
}
size = end - start;
}
}
return BlobManager.createFromOptions({
blobId: this.data.blobId,
offset,
size,
type: contentType,
/* Since `blob.slice()` creates a new view onto the same binary
* data as the original blob, we should re-use the same collector
* object so that the underlying resource gets deallocated when
* the last view into the data is released, not the first.
*/
__collector: this.data.__collector,
});
}
/**
* This method is in the standard, but not actually implemented by
* any browsers at this point. It's important for how Blobs work in
* React Native, however, since we cannot de-allocate resources automatically,
* so consumers need to explicitly de-allocate them.
*
* Note that the semantics around Blobs created via `blob.slice()`
* and `new Blob([blob])` are different. `blob.slice()` creates a
* new *view* onto the same binary data, so calling `close()` on any
* of those views is enough to deallocate the data, whereas
* `new Blob([blob, ...])` actually copies the data in memory.
*/
close() {
const BlobManager = require('./BlobManager').default;
BlobManager.release(this.data.blobId);
this.data = null;
}
/**
* Size of the data contained in the Blob object, in bytes.
*/
// $FlowFixMe[unsafe-getters-setters]
get size(): number {
return this.data.size;
}
/*
* String indicating the MIME type of the data contained in the Blob.
* If the type is unknown, this string is empty.
*/
// $FlowFixMe[unsafe-getters-setters]
get type(): string {
return this.data.type || '';
}
}
export default Blob;

185
node_modules/react-native/Libraries/Blob/BlobManager.js generated vendored Normal file
View File

@@ -0,0 +1,185 @@
/**
* 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 BlobT from './Blob';
import type {BlobCollector, BlobData, BlobOptions} from './BlobTypes';
import NativeBlobModule from './NativeBlobModule';
import invariant from 'invariant';
const Blob: BlobT = require('./Blob').default;
const BlobRegistry = require('./BlobRegistry');
/*eslint-disable no-bitwise */
/*eslint-disable eqeqeq */
/**
* Based on the rfc4122-compliant solution posted at
* http://stackoverflow.com/questions/105034
*/
function uuidv4(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
// **Temporary workaround**
// TODO(#24654): Use turbomodules for the Blob module.
// Blob collector is a jsi::HostObject that is used by native to know
// when the a Blob instance is deallocated. This allows to free the
// underlying native resources. This is a hack to workaround the fact
// that the current bridge infra doesn't allow to track js objects
// deallocation. Ideally the whole Blob object should be a jsi::HostObject.
function createBlobCollector(blobId: string): BlobCollector | null {
if (global.__blobCollectorProvider == null) {
return null;
} else {
return global.__blobCollectorProvider(blobId);
}
}
/**
* Module to manage blobs. Wrapper around the native blob module.
*/
class BlobManager {
/**
* If the native blob module is available.
*/
static isAvailable: boolean = !!NativeBlobModule;
/**
* Create blob from existing array of blobs.
*/
static createFromParts(
parts: Array<Blob | string>,
options?: BlobOptions,
): Blob {
invariant(NativeBlobModule, 'NativeBlobModule is available.');
const blobId = uuidv4();
const items = parts.map(part => {
if (part instanceof ArrayBuffer || ArrayBuffer.isView(part)) {
throw new Error(
"Creating blobs from 'ArrayBuffer' and 'ArrayBufferView' are not supported",
);
}
if (part instanceof Blob) {
return {
data: part.data,
type: 'blob',
};
} else {
return {
data: String(part),
type: 'string',
};
}
});
const size = items.reduce((acc, curr) => {
if (curr.type === 'string') {
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return acc + global.unescape(encodeURI(curr.data)).length;
} else {
/* $FlowFixMe[prop-missing] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
return acc + curr.data.size;
}
}, 0);
NativeBlobModule.createFromParts(items, blobId);
return BlobManager.createFromOptions({
blobId,
offset: 0,
size,
type: options ? options.type : '',
lastModified: options ? options.lastModified : Date.now(),
});
}
/**
* Create blob instance from blob data from native.
* Used internally by modules like XHR, WebSocket, etc.
*/
static createFromOptions(options: BlobData): Blob {
BlobRegistry.register(options.blobId);
// $FlowFixMe[prop-missing]
// $FlowFixMe[unsafe-object-assign]
return Object.assign(Object.create(Blob.prototype), {
data:
// Reuse the collector instance when creating from an existing blob.
// This will make sure that the underlying resource is only deallocated
// when all blobs that refer to it are deallocated.
options.__collector == null
? {
...options,
__collector: createBlobCollector(options.blobId),
}
: options,
});
}
/**
* Deallocate resources for a blob.
*/
static release(blobId: string): void {
invariant(NativeBlobModule, 'NativeBlobModule is available.');
BlobRegistry.unregister(blobId);
if (BlobRegistry.has(blobId)) {
return;
}
NativeBlobModule.release(blobId);
}
/**
* Inject the blob content handler in the networking module to support blob
* requests and responses.
*/
static addNetworkingHandler(): void {
invariant(NativeBlobModule, 'NativeBlobModule is available.');
NativeBlobModule.addNetworkingHandler();
}
/**
* Indicate the websocket should return a blob for incoming binary
* messages.
*/
static addWebSocketHandler(socketId: number): void {
invariant(NativeBlobModule, 'NativeBlobModule is available.');
NativeBlobModule.addWebSocketHandler(socketId);
}
/**
* Indicate the websocket should no longer return a blob for incoming
* binary messages.
*/
static removeWebSocketHandler(socketId: number): void {
invariant(NativeBlobModule, 'NativeBlobModule is available.');
NativeBlobModule.removeWebSocketHandler(socketId);
}
/**
* Send a blob message to a websocket.
*/
static sendOverSocket(blob: Blob, socketId: number): void {
invariant(NativeBlobModule, 'NativeBlobModule is available.');
NativeBlobModule.sendOverSocket(blob.data, socketId);
}
}
export default BlobManager;

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.
*
* @flow strict
* @format
*/
const registry: Map<string, number> = new Map();
export const register = (id: string) => {
const used = registry.get(id);
if (used != null) {
registry.set(id, used + 1);
} else {
registry.set(id, 1);
}
};
export const unregister = (id: string) => {
const used = registry.get(id);
if (used != null) {
if (used <= 1) {
registry.delete(id);
} else {
registry.set(id, used - 1);
}
}
};
export const has = (id: string): number | boolean => {
return registry.get(id) || false;
};

30
node_modules/react-native/Libraries/Blob/BlobTypes.js generated vendored Normal file
View File

@@ -0,0 +1,30 @@
/**
* 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';
export opaque type BlobCollector = {...};
export type BlobData = {
blobId: string,
offset: number,
size: number,
name?: string,
type?: string,
lastModified?: number,
__collector?: ?BlobCollector,
...
};
export type BlobOptions = {
type: string,
lastModified: number,
...
};

56
node_modules/react-native/Libraries/Blob/File.js generated vendored Normal file
View File

@@ -0,0 +1,56 @@
/**
* 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 {BlobOptions} from './BlobTypes';
import Blob from './Blob';
const invariant = require('invariant');
/**
* The File interface provides information about files.
*/
class File extends Blob {
/**
* Constructor for JS consumers.
*/
constructor(
parts: Array<Blob | string>,
name: string,
options?: BlobOptions,
) {
invariant(
parts != null && name != null,
'Failed to construct `File`: Must pass both `parts` and `name` arguments.',
);
super(parts, options);
this.data.name = name;
}
/**
* Name of the file.
*/
get name(): string {
invariant(this.data.name != null, 'Files must have a name set.');
return this.data.name;
}
/*
* Last modified time of the file.
*/
get lastModified(): number {
return this.data.lastModified || 0;
}
}
export default File;

231
node_modules/react-native/Libraries/Blob/FileReader.js generated vendored Normal file
View File

@@ -0,0 +1,231 @@
/**
* 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 {EventCallback} from '../../src/private/webapis/dom/events/EventTarget';
import type Blob from './Blob';
import Event from '../../src/private/webapis/dom/events/Event';
import {
getEventHandlerAttribute,
setEventHandlerAttribute,
} from '../../src/private/webapis/dom/events/EventHandlerAttributes';
import EventTarget from '../../src/private/webapis/dom/events/EventTarget';
import NativeFileReaderModule from './NativeFileReaderModule';
import {toByteArray} from 'base64-js';
type ReadyState =
| 0 // EMPTY
| 1 // LOADING
| 2; // DONE
type ReaderResult = string | ArrayBuffer;
const EMPTY = 0;
const LOADING = 1;
const DONE = 2;
class FileReader extends EventTarget {
static EMPTY: number = EMPTY;
static LOADING: number = LOADING;
static DONE: number = DONE;
EMPTY: number = EMPTY;
LOADING: number = LOADING;
DONE: number = DONE;
_readyState: ReadyState;
_error: ?Error;
_result: ?ReaderResult;
_aborted: boolean = false;
constructor() {
super();
this._reset();
}
_reset(): void {
this._readyState = EMPTY;
this._error = null;
this._result = null;
}
_setReadyState(newState: ReadyState) {
this._readyState = newState;
this.dispatchEvent(new Event('readystatechange'));
if (newState === DONE) {
if (this._aborted) {
this.dispatchEvent(new Event('abort'));
} else if (this._error) {
this.dispatchEvent(new Event('error'));
} else {
this.dispatchEvent(new Event('load'));
}
this.dispatchEvent(new Event('loadend'));
}
}
readAsArrayBuffer(blob: ?Blob): void {
this._aborted = false;
if (blob == null) {
throw new TypeError(
"Failed to execute 'readAsArrayBuffer' on 'FileReader': parameter 1 is not of type 'Blob'",
);
}
NativeFileReaderModule.readAsDataURL(blob.data).then(
(text: string) => {
if (this._aborted) {
return;
}
const base64 = text.split(',')[1];
const typedArray = toByteArray(base64);
this._result = typedArray.buffer;
this._setReadyState(DONE);
},
error => {
if (this._aborted) {
return;
}
this._error = error;
this._setReadyState(DONE);
},
);
}
readAsDataURL(blob: ?Blob): void {
this._aborted = false;
if (blob == null) {
throw new TypeError(
"Failed to execute 'readAsDataURL' on 'FileReader': parameter 1 is not of type 'Blob'",
);
}
NativeFileReaderModule.readAsDataURL(blob.data).then(
(text: string) => {
if (this._aborted) {
return;
}
this._result = text;
this._setReadyState(DONE);
},
error => {
if (this._aborted) {
return;
}
this._error = error;
this._setReadyState(DONE);
},
);
}
readAsText(blob: ?Blob, encoding: string = 'UTF-8'): void {
this._aborted = false;
if (blob == null) {
throw new TypeError(
"Failed to execute 'readAsText' on 'FileReader': parameter 1 is not of type 'Blob'",
);
}
NativeFileReaderModule.readAsText(blob.data, encoding).then(
(text: string) => {
if (this._aborted) {
return;
}
this._result = text;
this._setReadyState(DONE);
},
error => {
if (this._aborted) {
return;
}
this._error = error;
this._setReadyState(DONE);
},
);
}
abort() {
this._aborted = true;
// only call onreadystatechange if there is something to abort, as per spec
if (this._readyState !== EMPTY && this._readyState !== DONE) {
this._reset();
this._setReadyState(DONE);
}
// Reset again after, in case modified in handler
this._reset();
}
get readyState(): ReadyState {
return this._readyState;
}
get error(): ?Error {
return this._error;
}
get result(): ?ReaderResult {
return this._result;
}
get onabort(): EventCallback | null {
return getEventHandlerAttribute(this, 'abort');
}
set onabort(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'abort', listener);
}
get onerror(): EventCallback | null {
return getEventHandlerAttribute(this, 'error');
}
set onerror(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'error', listener);
}
get onload(): EventCallback | null {
return getEventHandlerAttribute(this, 'load');
}
set onload(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'load', listener);
}
get onloadstart(): EventCallback | null {
return getEventHandlerAttribute(this, 'loadstart');
}
set onloadstart(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'loadstart', listener);
}
get onloadend(): EventCallback | null {
return getEventHandlerAttribute(this, 'loadend');
}
set onloadend(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'loadend', listener);
}
get onprogress(): EventCallback | null {
return getEventHandlerAttribute(this, 'progress');
}
set onprogress(listener: ?EventCallback) {
setEventHandlerAttribute(this, 'progress', listener);
}
}
export default FileReader;

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/NativeBlobModule';
import NativeBlobModule from '../../src/private/specs_DEPRECATED/modules/NativeBlobModule';
export default NativeBlobModule;

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/NativeFileReaderModule';
import NativeFileReaderModule from '../../src/private/specs_DEPRECATED/modules/NativeFileReaderModule';
export default NativeFileReaderModule;

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.
*/
#import <jsi/jsi.h>
@class RCTBlobManager;
namespace facebook::react {
class JSI_EXPORT RCTBlobCollector : public jsi::HostObject {
public:
RCTBlobCollector(RCTBlobManager *blobManager, const std::string &blobId);
~RCTBlobCollector();
static void install(RCTBlobManager *blobManager);
private:
const std::string blobId_;
RCTBlobManager *blobManager_;
};
} // namespace facebook::react

View File

@@ -0,0 +1,57 @@
/*
* 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.
*/
#import "RCTBlobCollector.h"
#import <React/RCTBlobManager.h>
#import <React/RCTBridge+Private.h>
namespace facebook::react {
RCTBlobCollector::RCTBlobCollector(RCTBlobManager *blobManager, const std::string &blobId)
: blobId_(blobId), blobManager_(blobManager)
{
}
RCTBlobCollector::~RCTBlobCollector()
{
RCTBlobManager *blobManager = blobManager_;
NSString *blobId = [NSString stringWithUTF8String:blobId_.c_str()];
dispatch_async(blobManager_.methodQueue, ^{
[blobManager remove:blobId];
});
}
void RCTBlobCollector::install(RCTBlobManager *blobManager)
{
__weak RCTCxxBridge *cxxBridge = (RCTCxxBridge *)blobManager.bridge;
[cxxBridge
dispatchBlock:^{
if ((cxxBridge == nullptr) || cxxBridge.runtime == nullptr) {
return;
}
jsi::Runtime &runtime = *(jsi::Runtime *)cxxBridge.runtime;
runtime.global().setProperty(
runtime,
"__blobCollectorProvider",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "__blobCollectorProvider"),
1,
[blobManager](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
auto blobId = args[0].asString(rt).utf8(rt);
auto blobCollector = std::make_shared<RCTBlobCollector>(blobManager, blobId);
auto blobCollectorJsObject = jsi::Object::createFromHostObject(rt, blobCollector);
blobCollectorJsObject.setExternalMemoryPressure(
rt, [blobManager lengthOfBlobWithId:[NSString stringWithUTF8String:blobId.c_str()]]);
return blobCollectorJsObject;
}));
}
queue:RCTJSThread];
}
} // namespace facebook::react

31
node_modules/react-native/Libraries/Blob/RCTBlobManager.h generated vendored Executable file
View File

@@ -0,0 +1,31 @@
/*
* 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.
*/
#import <React/RCTBridge.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTInitializing.h>
#import <React/RCTURLRequestHandler.h>
@interface RCTBlobManager : NSObject <RCTBridgeModule, RCTURLRequestHandler, RCTInitializing>
- (NSString *)store:(NSData *)data;
- (void)store:(NSData *)data withId:(NSString *)blobId;
- (NSUInteger)lengthOfBlobWithId:(NSString *)blobId;
- (NSData *)resolve:(NSDictionary<NSString *, id> *)blob;
- (NSData *)resolve:(NSString *)blobId offset:(NSInteger)offset size:(NSInteger)size;
- (NSData *)resolveURL:(NSURL *)url;
- (void)remove:(NSString *)blobId;
- (void)createFromParts:(NSArray<NSDictionary<NSString *, id> *> *)parts withId:(NSString *)blobId;
@end

341
node_modules/react-native/Libraries/Blob/RCTBlobManager.mm generated vendored Executable file
View File

@@ -0,0 +1,341 @@
/*
* 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.
*/
#import <React/RCTBlobManager.h>
#import <mutex>
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTConvert.h>
#import <React/RCTMockDef.h>
#import <React/RCTNetworking.h>
#import <React/RCTUtils.h>
#import <React/RCTWebSocketModule.h>
#import "RCTBlobCollector.h"
#import "RCTBlobPlugins.h"
RCT_MOCK_DEF(RCTBlobManager, dispatch_async);
#define dispatch_async RCT_MOCK_USE(RCTBlobManager, dispatch_async)
static NSString *const kBlobURIScheme = @"blob";
@interface RCTBlobManager () <
RCTNetworkingRequestHandler,
RCTNetworkingResponseHandler,
RCTWebSocketContentHandler,
NativeBlobModuleSpec>
@end
@implementation RCTBlobManager {
// Blobs should be thread safe since they are used from the websocket and networking module,
// make sure to use proper locking when accessing this.
NSMutableDictionary<NSString *, NSData *> *_blobs;
std::mutex _blobsMutex;
NSOperationQueue *_queue;
dispatch_queue_t _processingQueue;
}
RCT_EXPORT_MODULE(BlobModule)
@synthesize bridge = _bridge;
@synthesize moduleRegistry = _moduleRegistry;
@synthesize methodQueue = _methodQueue;
- (void)initialize
{
std::lock_guard<std::mutex> lock(_blobsMutex);
_blobs = [NSMutableDictionary new];
facebook::react::RCTBlobCollector::install(self);
}
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
- (NSDictionary<NSString *, id> *)constantsToExport
{
return [self getConstants];
}
- (NSDictionary<NSString *, id> *)getConstants
{
return @{
@"BLOB_URI_SCHEME" : kBlobURIScheme,
@"BLOB_URI_HOST" : [NSNull null],
};
}
- (NSString *)store:(NSData *)data
{
NSString *blobId = [NSUUID UUID].UUIDString;
[self store:data withId:blobId];
return blobId;
}
- (void)store:(NSData *)data withId:(NSString *)blobId
{
std::lock_guard<std::mutex> lock(_blobsMutex);
_blobs[blobId] = data;
}
- (NSUInteger)lengthOfBlobWithId:(NSString *)blobId
{
std::lock_guard<std::mutex> lock(_blobsMutex);
return _blobs[blobId].length;
}
- (NSData *)resolve:(NSDictionary<NSString *, id> *)blob
{
NSString *blobId = [RCTConvert NSString:blob[@"blobId"]];
NSNumber *offset = [RCTConvert NSNumber:blob[@"offset"]];
NSNumber *size = [RCTConvert NSNumber:blob[@"size"]];
return [self resolve:blobId offset:offset ? [offset integerValue] : 0 size:size ? [size integerValue] : -1];
}
- (NSData *)resolve:(NSString *)blobId offset:(NSInteger)offset size:(NSInteger)size
{
NSData *data;
{
std::lock_guard<std::mutex> lock(_blobsMutex);
data = _blobs[blobId];
}
if (!data) {
return nil;
}
if (offset != 0 || (size != -1 && size != data.length)) {
data = [data subdataWithRange:NSMakeRange(offset, size)];
}
return data;
}
- (NSData *)resolveURL:(NSURL *)url
{
NSURLComponents *components = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];
NSString *blobId = components.path;
NSInteger offset = 0;
NSInteger size = -1;
if (components.queryItems) {
for (NSURLQueryItem *queryItem in components.queryItems) {
if ([queryItem.name isEqualToString:@"offset"]) {
offset = [queryItem.value integerValue];
}
if ([queryItem.name isEqualToString:@"size"]) {
size = [queryItem.value integerValue];
}
}
}
if (blobId) {
return [self resolve:blobId offset:offset size:size];
}
return nil;
}
- (void)remove:(NSString *)blobId
{
std::lock_guard<std::mutex> lock(_blobsMutex);
[_blobs removeObjectForKey:blobId];
}
RCT_EXPORT_METHOD(addNetworkingHandler)
{
RCTNetworking *const networking = [_moduleRegistry moduleForName:"Networking"];
// TODO(T63516227): Why can methodQueue be nil here?
// We don't want to do anything when methodQueue is nil.
if (!networking.methodQueue) {
return;
}
dispatch_async(networking.methodQueue, ^{
[networking addRequestHandler:self];
[networking addResponseHandler:self];
});
}
RCT_EXPORT_METHOD(addWebSocketHandler : (double)socketID)
{
dispatch_async(((RCTWebSocketModule *)[_moduleRegistry moduleForName:"WebSocketModule"]).methodQueue, ^{
[[self->_moduleRegistry moduleForName:"WebSocketModule"] setContentHandler:self
forSocketID:[NSNumber numberWithDouble:socketID]];
});
}
RCT_EXPORT_METHOD(removeWebSocketHandler : (double)socketID)
{
dispatch_async(((RCTWebSocketModule *)[_moduleRegistry moduleForName:"WebSocketModule"]).methodQueue, ^{
[[self->_moduleRegistry moduleForName:"WebSocketModule"] setContentHandler:nil
forSocketID:[NSNumber numberWithDouble:socketID]];
});
}
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
RCT_EXPORT_METHOD(sendOverSocket : (NSDictionary *)blob socketID : (double)socketID)
{
dispatch_async(((RCTWebSocketModule *)[_moduleRegistry moduleForName:"WebSocketModule"]).methodQueue, ^{
[[self->_moduleRegistry moduleForName:"WebSocketModule"] sendData:[self resolve:blob]
forSocketID:[NSNumber numberWithDouble:socketID]];
});
}
RCT_EXPORT_METHOD(createFromParts : (NSArray<NSDictionary<NSString *, id> *> *)parts withId : (NSString *)blobId)
{
NSMutableData *data = [NSMutableData new];
for (NSDictionary<NSString *, id> *part in parts) {
NSString *type = [RCTConvert NSString:part[@"type"]];
if ([type isEqualToString:@"blob"]) {
NSData *partData = [self resolve:part[@"data"]];
[data appendData:partData];
} else if ([type isEqualToString:@"string"]) {
NSData *partData = [[RCTConvert NSString:part[@"data"]] dataUsingEncoding:NSUTF8StringEncoding];
[data appendData:partData];
} else {
[NSException raise:@"Invalid type for blob" format:@"%@ is invalid", type];
}
}
dispatch_async(_methodQueue, ^{
[self store:data withId:blobId];
});
}
RCT_EXPORT_METHOD(release : (NSString *)blobId)
{
dispatch_async(_methodQueue, ^{
[self remove:blobId];
});
}
#pragma mark - RCTURLRequestHandler methods
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return [request.URL.scheme caseInsensitiveCompare:kBlobURIScheme] == NSOrderedSame;
}
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
{
// Lazy setup
if (!_queue) {
_queue = [NSOperationQueue new];
_queue.maxConcurrentOperationCount = 2;
}
__weak __typeof(self) weakSelf = self;
__weak __block NSBlockOperation *weakOp;
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
__typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:nil
expectedContentLength:-1
textEncodingName:nil];
[delegate URLRequest:weakOp didReceiveResponse:response];
NSData *data = [strongSelf resolveURL:response.URL];
NSError *error;
if (data) {
[delegate URLRequest:weakOp didReceiveData:data];
} else {
error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
}
[delegate URLRequest:weakOp didCompleteWithError:error];
}];
weakOp = op;
[_queue addOperation:op];
return op;
}
- (void)cancelRequest:(NSOperation *)op
{
[op cancel];
}
#pragma mark - RCTNetworkingRequestHandler methods
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
- (BOOL)canHandleNetworkingRequest:(NSDictionary *)data
{
return data[@"blob"] != nil;
}
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
- (NSDictionary *)handleNetworkingRequest:(NSDictionary *)data
{
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
NSDictionary *blob = [RCTConvert NSDictionary:data[@"blob"]];
NSString *contentType = @"application/octet-stream";
NSString *blobType = [RCTConvert NSString:RCTNilIfNull(blob[@"type"])];
if (blobType != nil && blobType.length > 0) {
contentType = blob[@"type"];
}
return @{@"body" : [self resolve:blob], @"contentType" : contentType};
}
- (BOOL)canHandleNetworkingResponse:(NSString *)responseType
{
return [responseType isEqualToString:@"blob"];
}
- (id)handleNetworkingResponse:(NSURLResponse *)response data:(NSData *)data
{
// An empty body will have nil for data, in this case we need to return
// an empty blob as per the XMLHttpRequest spec.
data = data ?: [NSData new];
return @{
@"blobId" : [self store:data],
@"offset" : @0,
@"size" : @(data.length),
@"name" : RCTNullIfNil([response suggestedFilename]),
@"type" : RCTNullIfNil([response MIMEType]),
};
}
#pragma mark - RCTWebSocketContentHandler methods
- (id)processWebsocketMessage:(id)message
forSocketID:(NSNumber *)socketID
withType:(NSString *__autoreleasing _Nonnull *)type
{
if (![message isKindOfClass:[NSData class]]) {
*type = @"text";
return message;
}
*type = @"blob";
return @{
@"blobId" : [self store:message],
@"offset" : @0,
@"size" : @(((NSData *)message).length),
};
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeBlobModuleSpecJSI>(params);
}
@end
Class RCTBlobManagerCls(void)
{
return RCTBlobManager.class;
}

View File

@@ -0,0 +1,41 @@
/**
* 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.
*
* @generated by an internal plugin build system
*/
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
// FB Internal: FBRCTBlobPlugins.h is autogenerated by the build system.
#import <React/FBRCTBlobPlugins.h>
#else
// OSS-compatibility layer
#import <Foundation/Foundation.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
#ifdef __cplusplus
extern "C" {
#endif
// RCTTurboModuleManagerDelegate should call this to resolve module classes.
Class RCTBlobClassProvider(const char *name);
// Lookup functions
Class RCTBlobManagerCls(void) __attribute__((used));
Class RCTFileReaderModuleCls(void) __attribute__((used));
#ifdef __cplusplus
}
#endif
#pragma GCC diagnostic pop
#endif // RN_DISABLE_OSS_PLUGIN_HEADER

View File

@@ -0,0 +1,34 @@
/**
* 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.
*
* @generated by an internal plugin build system
*/
#ifndef RN_DISABLE_OSS_PLUGIN_HEADER
// OSS-compatibility layer
#import "RCTBlobPlugins.h"
#import <string>
#import <unordered_map>
Class RCTBlobClassProvider(const char *name) {
// Intentionally leak to avoid crashing after static destructors are run.
static const auto sCoreModuleClassMap = new const std::unordered_map<std::string, Class (*)(void)>{
{"BlobModule", RCTBlobManagerCls},
{"FileReaderModule", RCTFileReaderModuleCls},
};
auto p = sCoreModuleClassMap->find(name);
if (p != sCoreModuleClassMap->end()) {
auto classFunc = p->second;
return classFunc();
}
return nil;
}
#endif // RN_DISABLE_OSS_PLUGIN_HEADER

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.
*/
#import <React/RCTBridgeModule.h>
@interface RCTFileReaderModule : NSObject <RCTBridgeModule>
@end

View File

@@ -0,0 +1,93 @@
/*
* 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.
*/
#import <React/RCTFileReaderModule.h>
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTBridge.h>
#import <React/RCTConvert.h>
#import <React/RCTBlobManager.h>
#import "RCTBlobPlugins.h"
@interface RCTFileReaderModule () <NativeFileReaderModuleSpec>
@end
@implementation RCTFileReaderModule
RCT_EXPORT_MODULE(FileReaderModule)
@synthesize moduleRegistry = _moduleRegistry;
RCT_EXPORT_METHOD(
readAsText : (NSDictionary<NSString *, id> *)blob encoding : (NSString *)encoding resolve : (RCTPromiseResolveBlock)
resolve reject : (RCTPromiseRejectBlock)reject)
{
RCTBlobManager *blobManager = [_moduleRegistry moduleForName:"BlobModule"];
dispatch_async(blobManager.methodQueue, ^{
NSData *data = [blobManager resolve:blob];
if (data == nil) {
reject(
RCTErrorUnspecified,
[NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]],
nil);
} else {
NSStringEncoding stringEncoding;
if (encoding == nil) {
stringEncoding = NSUTF8StringEncoding;
} else {
stringEncoding =
CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)encoding));
}
NSString *text = [[NSString alloc] initWithData:data encoding:stringEncoding];
resolve(text);
}
});
}
RCT_EXPORT_METHOD(
readAsDataURL : (NSDictionary<NSString *, id> *)blob resolve : (RCTPromiseResolveBlock)
resolve reject : (RCTPromiseRejectBlock)reject)
{
RCTBlobManager *blobManager = [_moduleRegistry moduleForName:"BlobModule"];
dispatch_async(blobManager.methodQueue, ^{
NSData *data = [blobManager resolve:blob];
if (data == nil) {
reject(
RCTErrorUnspecified,
[NSString stringWithFormat:@"Unable to resolve data for blob: %@", [RCTConvert NSString:blob[@"blobId"]]],
nil);
} else {
NSString *type = [RCTConvert NSString:blob[@"type"]];
NSString *text = [NSString
stringWithFormat:@"data:%@;base64,%@",
![type isEqual:[NSNull null]] && [type length] > 0 ? type : @"application/octet-stream",
[data base64EncodedStringWithOptions:0]];
resolve(text);
}
});
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeFileReaderModuleSpecJSI>(params);
}
@end
Class RCTFileReaderModuleCls(void)
{
return RCTFileReaderModule.class;
}

View File

@@ -0,0 +1,59 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
header_search_paths = [
"\"${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\"",
]
Pod::Spec.new do |s|
s.name = "React-RCTBlob"
s.version = version
s.summary = "An API for displaying iOS action sheets and share sheets."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.compiler_flags = '-Wno-nullability-completeness'
s.source = source
s.source_files = podspec_sources("*.{h,m,mm}", "**/*.h")
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
s.header_dir = "RCTBlob"
s.pod_target_xcconfig = {
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' ')
}
s.dependency "React-jsi"
s.dependency "React-Core/RCTBlobHeaders"
s.dependency "React-Core/RCTWebSocket"
s.dependency "React-RCTNetwork"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-NativeModulesApple")
add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern')
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
if use_hermes()
s.dependency "hermes-engine"
end
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

198
node_modules/react-native/Libraries/Blob/URL.js generated vendored Normal file
View File

@@ -0,0 +1,198 @@
/**
* 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 Blob from './Blob';
import NativeBlobModule from './NativeBlobModule';
let BLOB_URL_PREFIX = null;
if (
NativeBlobModule &&
typeof NativeBlobModule.getConstants().BLOB_URI_SCHEME === 'string'
) {
const constants = NativeBlobModule.getConstants();
// $FlowFixMe[incompatible-type] asserted above
// $FlowFixMe[unsafe-addition]
BLOB_URL_PREFIX = constants.BLOB_URI_SCHEME + ':';
if (typeof constants.BLOB_URI_HOST === 'string') {
BLOB_URL_PREFIX += `//${constants.BLOB_URI_HOST}/`;
}
}
/*
* To allow Blobs be accessed via `content://` URIs,
* you need to register `BlobProvider` as a ContentProvider in your app's `AndroidManifest.xml`:
*
* ```xml
* <manifest>
* <application>
* <provider
* android:name="com.facebook.react.modules.blob.BlobProvider"
* android:authorities="@string/blob_provider_authority"
* android:exported="false"
* />
* </application>
* </manifest>
* ```
* And then define the `blob_provider_authority` string in `res/values/strings.xml`.
* Use a dotted name that's entirely unique to your app:
*
* ```xml
* <resources>
* <string name="blob_provider_authority">your.app.package.blobs</string>
* </resources>
* ```
*/
export {URLSearchParams} from './URLSearchParams';
function validateBaseUrl(url: string) {
// from this MIT-licensed gist: https://gist.github.com/dperini/729294
return /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)*(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/.test(
url,
);
}
export class URL {
_url: string;
_searchParamsInstance: ?URLSearchParams = null;
static createObjectURL(blob: Blob): string {
if (BLOB_URL_PREFIX === null) {
throw new Error('Cannot create URL for blob!');
}
return `${BLOB_URL_PREFIX}${blob.data.blobId}?offset=${blob.data.offset}&size=${blob.size}`;
}
static revokeObjectURL(url: string) {
// Do nothing.
}
// $FlowFixMe[missing-local-annot]
constructor(url: string, base?: string | URL) {
let baseUrl = null;
if (!base || validateBaseUrl(url)) {
this._url = url;
if (this._url.includes('#')) {
const split = this._url.split('#');
const beforeHash = split[0];
const website = beforeHash.split('://')[1];
if (!website.includes('/')) {
this._url = split.join('/#');
}
}
if (
!this._url.endsWith('/') &&
!(this._url.includes('?') || this._url.includes('#'))
) {
this._url += '/';
}
} else {
if (typeof base === 'string') {
baseUrl = base;
if (!validateBaseUrl(baseUrl)) {
throw new TypeError(`Invalid base URL: ${baseUrl}`);
}
} else {
baseUrl = base.toString();
}
if (baseUrl.endsWith('/')) {
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
}
if (!url.startsWith('/')) {
url = `/${url}`;
}
if (baseUrl.endsWith(url)) {
url = '';
}
this._url = `${baseUrl}${url}`;
}
}
get hash(): string {
const hashMatch = this._url.match(/#([^/]*)/);
return hashMatch ? `#${hashMatch[1]}` : '';
}
get host(): string {
const hostMatch = this._url.match(/^https?:\/\/(?:[^@]+@)?([^:/?#]+)/);
const portMatch = this._url.match(/:(\d+)(?=[/?#]|$)/);
return hostMatch
? hostMatch[1] + (portMatch ? `:${portMatch[1]}` : '')
: '';
}
get hostname(): string {
const hostnameMatch = this._url.match(/^https?:\/\/(?:[^@]+@)?([^:/?#]+)/);
return hostnameMatch ? hostnameMatch[1] : '';
}
get href(): string {
return this.toString();
}
get origin(): string {
const matches = this._url.match(/^(https?:\/\/[^/]+)/);
return matches ? matches[1] : '';
}
get password(): string {
const passwordMatch = this._url.match(/https?:\/\/.*:(.*)@/);
return passwordMatch ? passwordMatch[1] : '';
}
get pathname(): string {
const pathMatch = this._url.match(/https?:\/\/[^/]+(\/[^?#]*)?/);
return pathMatch ? pathMatch[1] || '/' : '/';
}
get port(): string {
const portMatch = this._url.match(/:(\d+)(?=[/?#]|$)/);
return portMatch ? portMatch[1] : '';
}
get protocol(): string {
const protocolMatch = this._url.match(/^([a-zA-Z][a-zA-Z\d+\-.]*):/);
return protocolMatch ? protocolMatch[1] + ':' : '';
}
get search(): string {
const searchMatch = this._url.match(/\?([^#]*)/);
return searchMatch ? `?${searchMatch[1]}` : '';
}
get searchParams(): URLSearchParams {
if (this._searchParamsInstance == null) {
this._searchParamsInstance = new URLSearchParams(this.search);
}
return this._searchParamsInstance;
}
toJSON(): string {
return this.toString();
}
toString(): string {
if (this._searchParamsInstance === null) {
return this._url;
}
// $FlowFixMe[incompatible-use]
const instanceString = this._searchParamsInstance.toString();
const separator = this._url.indexOf('?') > -1 ? '&' : '?';
return this._url + separator + instanceString;
}
get username(): string {
const usernameMatch = this._url.match(/^https?:\/\/([^:@]+)(?::[^@]*)?@/);
return usernameMatch ? usernameMatch[1] : '';
}
}

View File

@@ -0,0 +1,151 @@
/**
* 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
*/
// Small subset from whatwg-url: https://github.com/jsdom/whatwg-url/tree/master/src
// The reference code bloat comes from Unicode issues with URLs, so those won't work here.
export class URLSearchParams {
_searchParams: Map<string, string[]> = new Map();
get size(): number {
return this._searchParams.size;
}
constructor(params?: Record<string, string> | string | [string, string][]) {
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/5whu3i34. */
if (params === null) {
return;
}
if (typeof params === 'string') {
// URLSearchParams("key1=value1&key2=value2");
params
.replace(/^\?/, '')
.split('&')
.forEach(pair => {
if (!pair) {
return;
}
const [key, value] = pair
.split('=')
.map(part => decodeURIComponent(part.replace(/\+/g, ' ')));
this.append(key, value);
});
} else if (Array.isArray(params)) {
//URLSearchParams([["key1", "value1"], ["key2", "value2"]]);
params.forEach(([key, value]) => this.append(key, value));
} else if (typeof params === 'object') {
//URLSearchParams({ key1: "value1", key2: "value2" });
Object.entries(params).forEach(([key, value]) => this.append(key, value));
}
}
append(key: string, value: string): void {
if (!this._searchParams.has(key)) {
this._searchParams.set(key, [value]); // Initialize with an array if key is missing
} else {
this._searchParams.get(key)?.push(value); // Else push the value to the array
}
}
delete(name: string): void {
this._searchParams.delete(name);
}
get(name: string): string | null {
const values = this._searchParams.get(name);
return values ? values[0] : null;
}
getAll(name: string): string[] {
return this._searchParams.get(name) ?? [];
}
has(name: string): boolean {
return this._searchParams.has(name);
}
set(name: string, value: string): void {
this._searchParams.set(name, [value]);
}
keys(): Iterator<string> {
return this._searchParams.keys();
}
values(): Iterator<string> {
function* generateValues(params: Map<string, string[]>): Iterator<string> {
for (const valueArray of params.values()) {
for (const value of valueArray) {
yield value;
}
}
}
return generateValues(this._searchParams);
}
entries(): Iterator<[string, string]> {
function* generateEntries(
params: Map<string, string[]>,
): Iterator<[string, string]> {
for (const [key, values] of params) {
for (const value of values) {
yield [key, value];
}
}
}
return generateEntries(this._searchParams);
}
forEach(
callback: (value: string, key: string, searchParams: this) => void,
): void {
for (const [key, values] of this._searchParams) {
for (const value of values) {
callback(value, key, this);
}
}
}
sort(): void {
this._searchParams = new Map(
[...this._searchParams.entries()].sort(([a], [b]) => a.localeCompare(b)),
);
}
// $FlowFixMe[unsupported-syntax]
[Symbol.iterator](): Iterator<[string, string]> {
const entries: [string, string][] = [];
for (const [key, values] of this._searchParams) {
for (const value of values) {
entries.push([key, value]);
}
}
return entries[Symbol.iterator]();
}
toString(): string {
return Array.from(this._searchParams.entries())
.map(([key, values]) =>
values
.map(
value =>
`${encodeURIComponent(key).replace(/%20/g, '+')}=${encodeURIComponent(
value,
).replace(/%20/g, '+')}`, // Convert only spaces to '+'
)
.join('&'),
)
.join('&');
}
}

Some files were not shown because too many files have changed in this diff Show More