chore: add mobile app

This commit is contained in:
2026-02-07 22:33:02 +01:00
parent db34640acc
commit e89384ad4e
29327 changed files with 3886944 additions and 12 deletions
@@ -0,0 +1,460 @@
/**
* 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 {EventConfig} from '../../../Libraries/Animated/AnimatedEvent';
import type {
AnimationConfig,
EndCallback,
} from '../../../Libraries/Animated/animations/Animation';
import type {
AnimatedNodeConfig,
EventMapping,
} from '../../../Libraries/Animated/NativeAnimatedModule';
import type {EventSubscription} from '../../../Libraries/vendor/emitter/EventEmitter';
import NativeAnimatedNonTurboModule from '../../../Libraries/Animated/NativeAnimatedModule';
import NativeAnimatedTurboModule from '../../../Libraries/Animated/NativeAnimatedTurboModule';
import NativeEventEmitter from '../../../Libraries/EventEmitter/NativeEventEmitter';
import RCTDeviceEventEmitter from '../../../Libraries/EventEmitter/RCTDeviceEventEmitter';
import Platform from '../../../Libraries/Utilities/Platform';
import * as ReactNativeFeatureFlags from '../featureflags/ReactNativeFeatureFlags';
import invariant from 'invariant';
import nullthrows from 'nullthrows';
// TODO T69437152 @petetheheat - Delete this fork when Fabric ships to 100%.
const NativeAnimatedModule: typeof NativeAnimatedTurboModule =
NativeAnimatedNonTurboModule ?? NativeAnimatedTurboModule;
let __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */
let __nativeAnimationIdCount = 1; /* used for started animations */
let nativeEventEmitter;
let waitingForQueuedOperations = new Set<string>();
let queueOperations = false;
let queue: Array<() => void> = [];
let singleOpQueue: Array<mixed> = [];
const isSingleOpBatching =
Platform.OS === 'android' &&
NativeAnimatedModule?.queueAndExecuteBatchedOperations != null &&
ReactNativeFeatureFlags.animatedShouldUseSingleOp();
let flushQueueImmediate = null;
const eventListenerGetValueCallbacks: {
[number]: (value: number) => void,
} = {};
const eventListenerAnimationFinishedCallbacks: {
[number]: EndCallback,
} = {};
let globalEventEmitterGetValueListener: ?EventSubscription = null;
let globalEventEmitterAnimationFinishedListener: ?EventSubscription = null;
const shouldSignalBatch: boolean =
ReactNativeFeatureFlags.animatedShouldSignalBatch() ||
ReactNativeFeatureFlags.cxxNativeAnimatedEnabled();
function createNativeOperations(): $NonMaybeType<typeof NativeAnimatedModule> {
const methodNames = [
'createAnimatedNode', // 1
'updateAnimatedNodeConfig', // 2
'getValue', // 3
'startListeningToAnimatedNodeValue', // 4
'stopListeningToAnimatedNodeValue', // 5
'connectAnimatedNodes', // 6
'disconnectAnimatedNodes', // 7
'startAnimatingNode', // 8
'stopAnimation', // 9
'setAnimatedNodeValue', // 10
'setAnimatedNodeOffset', // 11
'flattenAnimatedNodeOffset', // 12
'extractAnimatedNodeOffset', // 13
'connectAnimatedNodeToView', // 14
'disconnectAnimatedNodeFromView', // 15
'restoreDefaultValues', // 16
'dropAnimatedNode', // 17
'addAnimatedEventToView', // 18
'removeAnimatedEventFromView', // 19
'addListener', // 20
'removeListener', // 21
];
const nativeOperations: {
[$Values<typeof methodNames>]: (...$ReadOnlyArray<mixed>) => void,
} = {};
if (isSingleOpBatching) {
for (let ii = 0, length = methodNames.length; ii < length; ii++) {
const methodName = methodNames[ii];
const operationID = ii + 1;
nativeOperations[methodName] = (...args) => {
// `singleOpQueue` is a flat array of operation IDs and arguments, which
// is possible because # arguments is fixed for each operation. For more
// details, see `NativeAnimatedModule.queueAndExecuteBatchedOperations`.
singleOpQueue.push(operationID, ...args);
if (shouldSignalBatch) {
clearImmediate(flushQueueImmediate);
flushQueueImmediate = setImmediate(API.flushQueue);
}
};
}
} else {
for (let ii = 0, length = methodNames.length; ii < length; ii++) {
const methodName = methodNames[ii];
nativeOperations[methodName] = (...args) => {
/* $FlowFixMe[prop-missing] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
const method = nullthrows(NativeAnimatedModule)[methodName];
// If queueing is explicitly on, *or* the queue has not yet
// been flushed, use the queue. This is to prevent operations
// from being executed out of order.
if (queueOperations || queue.length !== 0) {
// $FlowExpectedError[incompatible-call] - Dynamism.
queue.push(() => method(...args));
} else if (shouldSignalBatch) {
// $FlowExpectedError[incompatible-call] - Dynamism.
queue.push(() => method(...args));
clearImmediate(flushQueueImmediate);
flushQueueImmediate = setImmediate(API.flushQueue);
} else {
// $FlowExpectedError[incompatible-call] - Dynamism.
method(...args);
}
};
}
}
// $FlowExpectedError[incompatible-return] - Dynamism.
return nativeOperations;
}
const NativeOperations = createNativeOperations();
/**
* Wrappers around NativeAnimatedModule to provide flow and autocomplete support for
* the native module methods, and automatic queue management on Android
*/
const API = {
getValue: (isSingleOpBatching
? (tag, saveValueCallback) => {
if (saveValueCallback) {
eventListenerGetValueCallbacks[tag] = saveValueCallback;
}
/* $FlowExpectedError[incompatible-call] - `saveValueCallback` is handled
differently when `isSingleOpBatching` is enabled. */
NativeOperations.getValue(tag);
}
: (tag, saveValueCallback) => {
NativeOperations.getValue(tag, saveValueCallback);
}) as $NonMaybeType<typeof NativeAnimatedModule>['getValue'],
setWaitingForIdentifier(id: string): void {
if (shouldSignalBatch) {
return;
}
waitingForQueuedOperations.add(id);
queueOperations = true;
if (
ReactNativeFeatureFlags.animatedShouldDebounceQueueFlush() &&
flushQueueImmediate
) {
clearImmediate(flushQueueImmediate);
}
},
unsetWaitingForIdentifier(id: string): void {
if (shouldSignalBatch) {
return;
}
waitingForQueuedOperations.delete(id);
if (waitingForQueuedOperations.size === 0) {
queueOperations = false;
API.disableQueue();
}
},
disableQueue(): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
if (ReactNativeFeatureFlags.animatedShouldDebounceQueueFlush()) {
const prevImmediate = flushQueueImmediate;
clearImmediate(prevImmediate);
flushQueueImmediate = setImmediate(API.flushQueue);
} else {
API.flushQueue();
}
},
flushQueue: (isSingleOpBatching
? (): void => {
invariant(
NativeAnimatedModule,
'Native animated module is not available',
);
flushQueueImmediate = null;
if (singleOpQueue.length === 0) {
return;
}
// Set up event listener for callbacks if it's not set up
ensureGlobalEventEmitterListeners();
// Single op batching doesn't use callback functions, instead we
// use RCTDeviceEventEmitter. This reduces overhead of sending lots of
// JSI functions across to native code; but also, TM infrastructure currently
// does not support packing a function into native arrays.
NativeAnimatedModule?.queueAndExecuteBatchedOperations?.(singleOpQueue);
singleOpQueue.length = 0;
}
: (): void => {
invariant(
NativeAnimatedModule,
'Native animated module is not available',
);
flushQueueImmediate = null;
if (queue.length === 0) {
return;
}
if (Platform.OS === 'android' || shouldSignalBatch) {
NativeAnimatedModule?.startOperationBatch?.();
}
for (let q = 0, l = queue.length; q < l; q++) {
queue[q]();
}
queue.length = 0;
if (Platform.OS === 'android' || shouldSignalBatch) {
NativeAnimatedModule?.finishOperationBatch?.();
}
}) as () => void,
createAnimatedNode(tag: number, config: AnimatedNodeConfig): void {
NativeOperations.createAnimatedNode(tag, config);
},
updateAnimatedNodeConfig(tag: number, config: AnimatedNodeConfig): void {
NativeOperations.updateAnimatedNodeConfig?.(tag, config);
},
startListeningToAnimatedNodeValue(tag: number): void {
NativeOperations.startListeningToAnimatedNodeValue(tag);
},
stopListeningToAnimatedNodeValue(tag: number): void {
NativeOperations.stopListeningToAnimatedNodeValue(tag);
},
connectAnimatedNodes(parentTag: number, childTag: number): void {
NativeOperations.connectAnimatedNodes(parentTag, childTag);
},
disconnectAnimatedNodes(parentTag: number, childTag: number): void {
NativeOperations.disconnectAnimatedNodes(parentTag, childTag);
},
startAnimatingNode: (isSingleOpBatching
? (animationId, nodeTag, config, endCallback) => {
if (endCallback) {
eventListenerAnimationFinishedCallbacks[animationId] = endCallback;
}
/* $FlowExpectedError[incompatible-call] - `endCallback` is handled
differently when `isSingleOpBatching` is enabled. */
NativeOperations.startAnimatingNode(animationId, nodeTag, config);
}
: (animationId, nodeTag, config, endCallback) => {
NativeOperations.startAnimatingNode(
animationId,
nodeTag,
config,
endCallback,
);
}) as $NonMaybeType<typeof NativeAnimatedModule>['startAnimatingNode'],
stopAnimation(animationId: number) {
NativeOperations.stopAnimation(animationId);
},
setAnimatedNodeValue(nodeTag: number, value: number): void {
NativeOperations.setAnimatedNodeValue(nodeTag, value);
},
setAnimatedNodeOffset(nodeTag: number, offset: number): void {
NativeOperations.setAnimatedNodeOffset(nodeTag, offset);
},
flattenAnimatedNodeOffset(nodeTag: number): void {
NativeOperations.flattenAnimatedNodeOffset(nodeTag);
},
extractAnimatedNodeOffset(nodeTag: number): void {
NativeOperations.extractAnimatedNodeOffset(nodeTag);
},
connectAnimatedNodeToView(nodeTag: number, viewTag: number): void {
NativeOperations.connectAnimatedNodeToView(nodeTag, viewTag);
},
disconnectAnimatedNodeFromView(nodeTag: number, viewTag: number): void {
NativeOperations.disconnectAnimatedNodeFromView(nodeTag, viewTag);
},
restoreDefaultValues(nodeTag: number): void {
NativeOperations.restoreDefaultValues?.(nodeTag);
},
dropAnimatedNode(tag: number): void {
NativeOperations.dropAnimatedNode(tag);
},
addAnimatedEventToView(
viewTag: number,
eventName: string,
eventMapping: EventMapping,
) {
NativeOperations.addAnimatedEventToView(viewTag, eventName, eventMapping);
},
removeAnimatedEventFromView(
viewTag: number,
eventName: string,
animatedNodeTag: number,
) {
NativeOperations.removeAnimatedEventFromView(
viewTag,
eventName,
animatedNodeTag,
);
},
};
function ensureGlobalEventEmitterListeners() {
if (
globalEventEmitterGetValueListener &&
globalEventEmitterAnimationFinishedListener
) {
return;
}
globalEventEmitterGetValueListener = RCTDeviceEventEmitter.addListener(
'onNativeAnimatedModuleGetValue',
params => {
const {tag} = params;
const callback = eventListenerGetValueCallbacks[tag];
if (!callback) {
return;
}
callback(params.value);
delete eventListenerGetValueCallbacks[tag];
},
);
globalEventEmitterAnimationFinishedListener =
RCTDeviceEventEmitter.addListener(
'onNativeAnimatedModuleAnimationFinished',
params => {
// TODO: remove Array.isArray once native changes have propagated
const animations = Array.isArray(params) ? params : [params];
for (const animation of animations) {
const {animationId} = animation;
const callback = eventListenerAnimationFinishedCallbacks[animationId];
if (callback) {
callback(animation);
delete eventListenerAnimationFinishedCallbacks[animationId];
}
}
},
);
}
function generateNewNodeTag(): number {
return __nativeAnimatedNodeTagCount++;
}
function generateNewAnimationId(): number {
return __nativeAnimationIdCount++;
}
function assertNativeAnimatedModule(): void {
invariant(NativeAnimatedModule, 'Native animated module is not available');
}
let _warnedMissingNativeAnimated = false;
function shouldUseNativeDriver(
config: $ReadOnly<{...AnimationConfig, ...}> | EventConfig<mixed>,
): boolean {
if (config.useNativeDriver == null) {
console.warn(
'Animated: `useNativeDriver` was not specified. This is a required ' +
'option and must be explicitly set to `true` or `false`',
);
}
if (config.useNativeDriver === true && !NativeAnimatedModule) {
if (process.env.NODE_ENV !== 'test') {
if (!_warnedMissingNativeAnimated) {
console.warn(
'Animated: `useNativeDriver` is not supported because the native ' +
'animated module is missing. Falling back to JS-based animation. To ' +
'resolve this, add `RCTAnimation` module to this app, or remove ' +
'`useNativeDriver`. ' +
'Make sure to run `bundle exec pod install` first. Read more about autolinking: https://github.com/react-native-community/cli/blob/master/docs/autolinking.md',
);
_warnedMissingNativeAnimated = true;
}
}
return false;
}
return config.useNativeDriver || false;
}
function transformDataType(value: number | string): number | string {
// Change the string type to number type so we can reuse the same logic in
// iOS and Android platform
if (typeof value !== 'string') {
return value;
}
// Normalize degrees and radians to a number expressed in radians
if (value.endsWith('deg')) {
const degrees = parseFloat(value) || 0;
return (degrees * Math.PI) / 180.0;
} else if (value.endsWith('rad')) {
return parseFloat(value) || 0;
} else {
return value;
}
}
export default {
API,
generateNewNodeTag,
generateNewAnimationId,
assertNativeAnimatedModule,
shouldUseNativeDriver,
shouldSignalBatch,
transformDataType,
// $FlowExpectedError[unsafe-getters-setters] - unsafe getter lint suppression
// $FlowExpectedError[missing-type-arg] - unsafe getter lint suppression
get nativeEventEmitter(): NativeEventEmitter {
if (!nativeEventEmitter) {
// $FlowFixMe[underconstrained-implicit-instantiation]
nativeEventEmitter = 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 : NativeAnimatedModule,
);
}
return nativeEventEmitter;
},
};
@@ -0,0 +1,64 @@
/**
* 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 {InterpolationConfigType} from '../../../Libraries/Animated/nodes/AnimatedInterpolation';
import {
isSupportedInterpolationParam,
isSupportedStyleProp,
isSupportedTransformProp,
} from '../../../Libraries/Animated/NativeAnimatedAllowlist';
export function validateInterpolation<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): void {
for (const key in config) {
if (key !== 'debugID' && !isSupportedInterpolationParam(key)) {
console.error(
`Interpolation property '${key}' is not supported by native animated module`,
);
}
}
}
export function validateStyles(styles: {[key: string]: ?number, ...}): void {
for (const key in styles) {
if (!isSupportedStyleProp(key)) {
console.error(
`Style property '${key}' is not supported by native animated module`,
);
}
}
}
export function validateTransform(
configs: Array<
| {
type: 'animated',
property: string,
nodeTag: ?number,
...
}
| {
type: 'static',
property: string,
value: number | string,
...
},
>,
): void {
configs.forEach(config => {
if (!isSupportedTransformProp(config.property)) {
console.error(
`Property '${config.property}' is not supported by native animated module`,
);
}
});
}
@@ -0,0 +1,365 @@
/**
* 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 '../../../Libraries/Animated/nodes/AnimatedProps';
import type {EventSubscription} from '../../../Libraries/EventEmitter/NativeEventEmitter';
import AnimatedNode from '../../../Libraries/Animated/nodes/AnimatedNode';
import AnimatedProps from '../../../Libraries/Animated/nodes/AnimatedProps';
import AnimatedValue from '../../../Libraries/Animated/nodes/AnimatedValue';
import {isPublicInstance as isFabricPublicInstance} from '../../../Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstanceUtils';
import useRefEffect from '../../../Libraries/Utilities/useRefEffect';
import * as ReactNativeFeatureFlags from '../featureflags/ReactNativeFeatureFlags';
import {createAnimatedPropsMemoHook} from './createAnimatedPropsMemoHook';
import NativeAnimatedHelper from './NativeAnimatedHelper';
import {
useCallback,
useEffect,
useInsertionEffect,
useReducer,
useRef,
} from 'react';
type ReducedProps<TProps> = {
...TProps,
collapsable: boolean,
...
};
type CallbackRef<T> = T => mixed;
export type AnimatedPropsHook = <TProps: {...}, TInstance>(
props: TProps,
) => [ReducedProps<TProps>, CallbackRef<TInstance | null>];
type UpdateCallback = () => void;
type AnimatedValueListeners = Array<{
propValue: AnimatedValue,
listenerId: string,
}>;
export default function createAnimatedPropsHook(
allowlist: ?AnimatedPropsAllowlist,
): AnimatedPropsHook {
const useAnimatedPropsMemo = createAnimatedPropsMemoHook(allowlist);
const useNativePropsInFabric =
ReactNativeFeatureFlags.shouldUseSetNativePropsInFabric();
return function useAnimatedProps<TProps: {...}, TInstance>(
props: TProps,
): [ReducedProps<TProps>, CallbackRef<TInstance | null>] {
const [, scheduleUpdate] = useReducer<number, void>(count => count + 1, 0);
const onUpdateRef = useRef<UpdateCallback | null>(null);
const timerRef = useRef<TimeoutID | null>(null);
const node = useAnimatedPropsMemo(
() => new AnimatedProps(props, () => onUpdateRef.current?.(), allowlist),
props,
);
useEffect(() => {
// Animated queue flush is handled deterministically in setImmediate for the following feature flags:
// animatedShouldSignalBatch, cxxNativeAnimatedEnabled
if (!NativeAnimatedHelper.shouldSignalBatch) {
// If multiple components call `flushQueue`, the first one will flush the
// queue and subsequent ones will do nothing.
NativeAnimatedHelper.API.flushQueue();
}
let drivenAnimationEndedListener: ?EventSubscription = null;
if (node.__isNative) {
drivenAnimationEndedListener =
NativeAnimatedHelper.nativeEventEmitter.addListener(
'onUserDrivenAnimationEnded',
data => {
node.update();
},
);
}
return () => {
drivenAnimationEndedListener?.remove();
};
});
// NOTE: This feature flag must be evaluated inside the hook because this
// module factory can be evaluated much sooner, before overrides are set.
const useAnimatedPropsLifecycle =
ReactNativeFeatureFlags.scheduleAnimatedCleanupInMicrotask()
? useAnimatedPropsLifecycleWithCleanupInMicrotask
: useAnimatedPropsLifecycleWithPrevNodeRef;
useAnimatedPropsLifecycle(node);
// TODO: This "effect" does three things:
//
// 1) Call `setNativeView`.
// 2) Update `onUpdateRef`.
// 3) Update listeners for `AnimatedEvent` props.
//
// Ideally, each of these would be separate "effects" so that they are not
// unnecessarily re-run when irrelevant dependencies change. For example, we
// should be able to hoist all `AnimatedEvent` props and only do #3 if either
// the `AnimatedEvent` props change or `instance` changes.
//
// But there is no way to transparently compose three separate callback refs,
// so we just combine them all into one for now.
const refEffect = useCallback(
(instance: TInstance) => {
// NOTE: This may be called more often than necessary (e.g. when `props`
// changes), but `setNativeView` already optimizes for that.
// $FlowFixMe[incompatible-call]
node.setNativeView(instance);
// NOTE: When using the JS animation driver, this callback is called on
// every animation frame. When using the native driver, this callback is
// called when the animation completes.
onUpdateRef.current = () => {
if (process.env.NODE_ENV === 'test') {
// Check 1: this is a test.
// call `scheduleUpdate` to bypass use of setNativeProps.
return scheduleUpdate();
}
const isFabricNode = isFabricInstance(instance);
if (node.__isNative) {
// Check 2: this is an animation driven by native.
// In native driven animations, this callback is only called once the animation completes.
if (
isFabricNode &&
!(
ReactNativeFeatureFlags.cxxNativeAnimatedEnabled() &&
ReactNativeFeatureFlags.cxxNativeAnimatedRemoveJsSync()
)
) {
// Call `scheduleUpdate` to synchronise Fiber and Shadow tree.
// Must not be called in Paper.
scheduleUpdate();
}
return;
}
if (
typeof instance !== 'object' ||
typeof instance?.setNativeProps !== 'function'
) {
// Check 3: the instance does not support setNativeProps. Call `scheduleUpdate`.
return scheduleUpdate();
}
if (!isFabricNode) {
// Check 4: this is a paper instance, call setNativeProps.
// $FlowIgnore[not-a-function] - Assume it's still a function.
// $FlowFixMe[incompatible-use]
return instance.setNativeProps(node.__getAnimatedValue());
}
if (!useNativePropsInFabric) {
// Check 5: setNativeProps are disabled.
return scheduleUpdate();
}
// This is a Fabric instance and setNativeProps is supported.
// $FlowIgnore[not-a-function] - Assume it's still a function.
// $FlowFixMe[incompatible-use]
instance.setNativeProps(node.__getAnimatedValue());
// Keeping state of Fiber tree and Shadow tree in sync.
//
// This is done by calling `scheduleUpdate` which will trigger a commit.
// However, React commit is not fast enough to drive animations.
// This is where setNativeProps comes in handy but the state between
// Fiber tree and Shadow tree needs to be kept in sync.
// The goal is to call `scheduleUpdate` as little as possible to maintain
// performance but frequently enough to keep state in sync.
// Debounce is set to 48ms, which is 3 * the duration of a frame.
// 3 frames was the highest value where flickering state was not observed.
if (timerRef.current != null) {
clearTimeout(timerRef.current);
}
timerRef.current = setTimeout(() => {
timerRef.current = null;
scheduleUpdate();
}, 48);
};
const target = getEventTarget(instance);
const animatedValueListeners: AnimatedValueListeners = [];
const eventTuples = node.__getNativeAnimatedEventTuples();
for (const [propName, propValue] of eventTuples) {
propValue.__attach(target, propName);
// $FlowFixMe[incompatible-call] - the `addListenersToPropsValue` drills down the propValue.
addListenersToPropsValue(propValue, animatedValueListeners);
}
return () => {
onUpdateRef.current = null;
for (const [propName, propValue] of eventTuples) {
propValue.__detach(target, propName);
}
for (const {propValue, listenerId} of animatedValueListeners) {
propValue.removeListener(listenerId);
}
};
},
[node],
);
const callbackRef = useRefEffect<TInstance>(refEffect);
return [reduceAnimatedProps<TProps>(node, props), callbackRef];
};
}
function reduceAnimatedProps<TProps>(
node: AnimatedProps,
props: TProps,
): ReducedProps<TProps> {
// Force `collapsable` to be false so that the native view is not flattened.
// Flattened views cannot be accurately referenced by the native driver.
return {
...node.__getValueWithStaticProps(props),
collapsable: false,
};
}
function addListenersToPropsValue(
propValue: AnimatedValue,
accumulator: AnimatedValueListeners,
) {
// propValue can be a scalar value, an array or an object.
if (propValue instanceof AnimatedValue) {
const listenerId = propValue.addListener(() => {});
accumulator.push({propValue, listenerId});
} else if (Array.isArray(propValue)) {
// An array can be an array of scalar values, arrays of arrays, or arrays of objects
for (const prop of propValue) {
addListenersToPropsValue(prop, accumulator);
}
} else if (propValue instanceof Object) {
addAnimatedValuesListenersToProps(propValue, accumulator);
}
}
function addAnimatedValuesListenersToProps(
props: AnimatedNode,
accumulator: AnimatedValueListeners,
) {
for (const propName in props) {
// $FlowFixMe[prop-missing] - This is an object contained in a prop, but we don't know the exact type.
const propValue = props[propName];
addListenersToPropsValue(propValue, accumulator);
}
}
/**
* Manages the lifecycle of the supplied `AnimatedProps` by invoking `__attach`
* and `__detach`. However, this is more complicated because `AnimatedProps`
* uses reference counting to determine when to recursively detach its children
* nodes. So in order to optimize this, we avoid detaching until the next attach
* unless we are unmounting.
*/
function useAnimatedPropsLifecycleWithPrevNodeRef(node: AnimatedProps): void {
const prevNodeRef = useRef<?AnimatedProps>(null);
const isUnmountingRef = useRef<boolean>(false);
useInsertionEffect(() => {
isUnmountingRef.current = false;
return () => {
isUnmountingRef.current = true;
};
}, []);
useInsertionEffect(() => {
node.__attach();
if (prevNodeRef.current != null) {
const prevNode = prevNodeRef.current;
// TODO: Stop restoring default values (unless `reset` is called).
prevNode.__restoreDefaultValues();
prevNode.__detach();
prevNodeRef.current = null;
}
return () => {
if (isUnmountingRef.current) {
// NOTE: Do not restore default values on unmount, see D18197735.
node.__detach();
} else {
prevNodeRef.current = node;
}
};
}, [node]);
}
/**
* Manages the lifecycle of the supplied `AnimatedProps` by invoking `__attach`
* and `__detach`. However, `__detach` occurs in a microtask for these reasons:
*
* 1. Optimizes detaching and attaching `AnimatedNode` instances that rely on
* reference counting to cleanup state, by causing detach to be scheduled
* after any subsequent attach.
* 2. Avoids calling `detach` during the insertion effect phase (which
* occurs during the commit phase), which may invoke completion callbacks.
*
* We should avoid invoking completion callbacks during the commit phase because
* callbacks may update state, which is unsupported and will force synchronous
* updates.
*/
function useAnimatedPropsLifecycleWithCleanupInMicrotask(
node: AnimatedProps,
): void {
const isMounted = useRef<boolean>(false);
useInsertionEffect(() => {
isMounted.current = true;
node.__attach();
return () => {
isMounted.current = false;
queueMicrotask(() => {
// NOTE: Do not restore default values on unmount, see D18197735.
if (isMounted.current) {
// TODO: Stop restoring default values (unless `reset` is called).
node.__restoreDefaultValues();
}
node.__detach();
});
};
}, [node]);
}
function getEventTarget<TInstance>(instance: TInstance): TInstance {
return typeof instance === 'object' &&
typeof instance?.getScrollableNode === 'function'
? // $FlowFixMe[incompatible-use] - Legacy instance assumptions.
instance.getScrollableNode()
: instance;
}
// $FlowFixMe[unclear-type] - Legacy instance assumptions.
function isFabricInstance(instance: any): boolean {
return (
isFabricPublicInstance(instance) ||
// Some components have a setNativeProps function but aren't a host component
// such as lists like FlatList and SectionList. These should also use
// forceUpdate in Fabric since setNativeProps doesn't exist on the underlying
// host component. This crazy hack is essentially special casing those lists and
// ScrollView itself to use forceUpdate in Fabric.
// If these components end up using forwardRef then these hacks can go away
// as instance would actually be the underlying host component and the above check
// would be sufficient.
isFabricPublicInstance(instance?.getNativeScrollRef?.()) ||
isFabricPublicInstance(
instance?.getScrollResponder?.()?.getNativeScrollRef?.(),
)
);
}
@@ -0,0 +1,406 @@
/**
* 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 '../../../Libraries/Animated/nodes/AnimatedProps';
import type {AnimatedPropsAllowlist} from '../../../Libraries/Animated/nodes/AnimatedProps';
import type {AnimatedStyleAllowlist} from '../../../Libraries/Animated/nodes/AnimatedStyle';
import {AnimatedEvent} from '../../../Libraries/Animated/AnimatedEvent';
import AnimatedNode from '../../../Libraries/Animated/nodes/AnimatedNode';
import {isPlainObject} from '../../../Libraries/Animated/nodes/AnimatedObject';
import flattenStyle from '../../../Libraries/StyleSheet/flattenStyle';
import * as ReactNativeFeatureFlags from '../featureflags/ReactNativeFeatureFlags';
import nullthrows from 'nullthrows';
import {useInsertionEffect, useMemo, useRef, useState} from 'react';
type CompositeKey = {
style?: {[string]: CompositeKeyComponent},
[string]:
| CompositeKeyComponent
| AnimatedEvent
| $ReadOnlyArray<mixed>
| $ReadOnly<{[string]: mixed}>,
};
type CompositeKeyComponent =
| AnimatedNode
| $ReadOnlyArray<CompositeKeyComponent | null>
| $ReadOnly<{[string]: CompositeKeyComponent}>;
type $ReadOnlyCompositeKey = $ReadOnly<{
style?: $ReadOnly<{[string]: CompositeKeyComponent}>,
[string]:
| $ReadOnlyCompositeKeyComponent
| AnimatedEvent
| $ReadOnlyArray<mixed>
| $ReadOnly<{[string]: mixed}>,
}>;
type $ReadOnlyCompositeKeyComponent =
| AnimatedNode
| $ReadOnlyArray<$ReadOnlyCompositeKeyComponent | null>
| $ReadOnly<{[string]: $ReadOnlyCompositeKeyComponent}>;
type AnimatedPropsMemoHook = (
() => AnimatedProps,
props: $ReadOnly<{[string]: mixed}>,
) => AnimatedProps;
/**
* Creates a hook that returns an `AnimatedProps` object that is memoized based
* on the subset of `props` that are instances of `AnimatedNode` or
* `AnimatedEvent`.
*/
export function createAnimatedPropsMemoHook(
allowlist: ?AnimatedPropsAllowlist,
): AnimatedPropsMemoHook {
return function useAnimatedPropsMemo(
create: () => AnimatedProps,
props: $ReadOnly<{[string]: mixed}>,
): AnimatedProps {
// NOTE: This feature flag must be evaluated inside the hook because this
// module factory can be evaluated much sooner, before overrides are set.
const useAnimatedPropsImpl =
ReactNativeFeatureFlags.avoidStateUpdateInAnimatedPropsMemo()
? useAnimatedPropsMemo_ref
: useAnimatedPropsMemo_state;
return useAnimatedPropsImpl(create, props);
};
function useAnimatedPropsMemo_ref(
create: () => AnimatedProps,
props: $ReadOnly<{[string]: mixed}>,
): AnimatedProps {
const compositeKey = useMemo(
() => createCompositeKeyForProps(props, allowlist),
[props],
);
const prevRef = useRef<?$ReadOnly<{
compositeKey: typeof compositeKey,
node: AnimatedProps,
}>>();
const prev = prevRef.current;
const next =
prev != null && areCompositeKeysEqual(prev.compositeKey, compositeKey)
? prev
: {
compositeKey,
node: create(),
};
useInsertionEffect(() => {
prevRef.current = next;
}, [next]);
return next.node;
}
function useAnimatedPropsMemo_state(
create: () => AnimatedProps,
props: $ReadOnly<{[string]: mixed}>,
): AnimatedProps {
const compositeKey = useMemo(
() => createCompositeKeyForProps(props, allowlist),
[props],
);
const [state, setState] = useState<{
allowlist: ?AnimatedPropsAllowlist,
compositeKey: $ReadOnlyCompositeKey | null,
value: AnimatedProps,
}>(() => ({
allowlist,
compositeKey,
value: create(),
}));
if (
state.allowlist !== allowlist ||
!areCompositeKeysEqual(state.compositeKey, compositeKey)
) {
setState({
allowlist,
compositeKey,
value: create(),
});
}
return state.value;
}
}
/**
* Creates a new composite key for a `props` object that can be used to detect
* whether a new `AnimatedProps` instance must be created.
*
* - With an allowlist, those props are searched for `AnimatedNode` instances.
* - Without an allowlist, `style` is searched for `AnimatedNode` instances,
* but all other objects and arrays are included (not searched). We do not
* search objects and arrays without an allowlist in case they are very large
* data structures. We safely traverse `style` becuase it is bounded.
*
* Any `AnimatedEvent` instances at the first depth are always included.
*
* If `props` contains no `AnimatedNode` or `AnimatedEvent` instances, this
* returns null.
*/
export function createCompositeKeyForProps(
props: $ReadOnly<{[string]: mixed}>,
allowlist: ?AnimatedPropsAllowlist,
): $ReadOnlyCompositeKey | null {
let compositeKey: CompositeKey | null = null;
const keys = Object.keys(props);
for (let ii = 0, length = keys.length; ii < length; ii++) {
const key = keys[ii];
const value = props[key];
if (allowlist == null || hasOwn(allowlist, key)) {
let compositeKeyComponent;
if (key === 'style') {
// $FlowFixMe[incompatible-call] - `style` is a valid argument.
// $FlowFixMe[incompatible-type] - `flattenStyle` returns an object.
const flatStyle: ?{[string]: mixed} = flattenStyle(value);
if (flatStyle != null) {
compositeKeyComponent = createCompositeKeyForObject(
flatStyle,
allowlist?.style,
);
}
} else if (
value instanceof AnimatedNode ||
value instanceof AnimatedEvent
) {
compositeKeyComponent = value;
} else if (Array.isArray(value)) {
compositeKeyComponent =
allowlist == null ? value : createCompositeKeyForArray(value);
} else if (isPlainObject(value)) {
compositeKeyComponent =
allowlist == null ? value : createCompositeKeyForObject(value);
}
if (compositeKeyComponent != null) {
if (compositeKey == null) {
compositeKey = {} as CompositeKey;
}
compositeKey[key] = compositeKeyComponent;
}
}
}
return compositeKey;
}
/**
* Creates a new composite key for an array that retains all values that are or
* contain `AnimatedNode` instances, and `null` for the rest.
*
* If `array` contains no `AnimatedNode` instances, this returns null.
*/
function createCompositeKeyForArray(
array: $ReadOnlyArray<mixed>,
): $ReadOnlyArray<$ReadOnlyCompositeKeyComponent | null> | null {
let compositeKey: Array<$ReadOnlyCompositeKeyComponent | null> | null = null;
for (let ii = 0, length = array.length; ii < length; ii++) {
const value = array[ii];
let compositeKeyComponent;
if (value instanceof AnimatedNode) {
compositeKeyComponent = value;
} else if (Array.isArray(value)) {
compositeKeyComponent = createCompositeKeyForArray(value);
} else if (isPlainObject(value)) {
compositeKeyComponent = createCompositeKeyForObject(value);
}
if (compositeKeyComponent != null) {
if (compositeKey == null) {
compositeKey = new Array<$ReadOnlyCompositeKeyComponent | null>(
array.length,
).fill(null);
}
compositeKey[ii] = compositeKeyComponent;
}
}
return compositeKey;
}
/**
* Creates a new composite key for an object that retains only properties that
* are or contain `AnimatedNode` instances.
*
* When used to create composite keys for `style` props:
*
* - With an allowlist, those properties are searched.
* - Without an allowlist, every property is searched.
*
* If `object` contains no `AnimatedNode` instances, this returns null.
*/
function createCompositeKeyForObject(
object: $ReadOnly<{[string]: mixed}>,
allowlist?: ?AnimatedStyleAllowlist,
): $ReadOnly<{[string]: $ReadOnlyCompositeKeyComponent}> | null {
let compositeKey: {[string]: $ReadOnlyCompositeKeyComponent} | null = null;
const keys = Object.keys(object);
for (let ii = 0, length = keys.length; ii < length; ii++) {
const key = keys[ii];
if (allowlist == null || hasOwn(allowlist, key)) {
const value = object[key];
let compositeKeyComponent;
if (value instanceof AnimatedNode) {
compositeKeyComponent = value;
} else if (Array.isArray(value)) {
compositeKeyComponent = createCompositeKeyForArray(value);
} else if (isPlainObject(value)) {
compositeKeyComponent = createCompositeKeyForObject(value);
}
if (compositeKeyComponent != null) {
if (compositeKey == null) {
compositeKey = {} as {[string]: $ReadOnlyCompositeKeyComponent};
}
compositeKey[key] = compositeKeyComponent;
}
}
}
return compositeKey;
}
export function areCompositeKeysEqual(
maybePrev: $ReadOnlyCompositeKey | null,
maybeNext: $ReadOnlyCompositeKey | null,
allowlist: ?AnimatedPropsAllowlist,
): boolean {
if (maybePrev === maybeNext) {
return true;
}
if (maybePrev === null || maybeNext === null) {
return false;
}
// Help Flow retain the type refinements of these.
const prev = maybePrev;
const next = maybeNext;
const keys = Object.keys(prev);
const length = keys.length;
if (length !== Object.keys(next).length) {
return false;
}
for (let ii = 0; ii < length; ii++) {
const key = keys[ii];
if (!hasOwn(next, key)) {
return false;
}
const prevComponent = prev[key];
const nextComponent = next[key];
if (key === 'style') {
// We know style components are objects with non-mixed values.
if (
!areCompositeKeyComponentsEqual(
// $FlowIgnore[incompatible-cast]
prevComponent as $ReadOnlyCompositeKeyComponent,
// $FlowIgnore[incompatible-cast]
nextComponent as $ReadOnlyCompositeKeyComponent,
)
) {
return false;
}
} else if (
prevComponent instanceof AnimatedNode ||
prevComponent instanceof AnimatedEvent
) {
if (prevComponent !== nextComponent) {
return false;
}
} else {
// When `allowlist` is null, the components must be the same. Otherwise,
// we created the components using deep traversal, so deep compare them.
if (allowlist == null) {
if (prevComponent !== nextComponent) {
return false;
}
} else {
if (
!areCompositeKeyComponentsEqual(
// $FlowIgnore[incompatible-cast]
prevComponent as $ReadOnlyCompositeKeyComponent,
// $FlowIgnore[incompatible-cast]
nextComponent as $ReadOnlyCompositeKeyComponent,
)
) {
return false;
}
}
}
}
return true;
}
function areCompositeKeyComponentsEqual(
prev: $ReadOnlyCompositeKeyComponent | null,
next: $ReadOnlyCompositeKeyComponent | null,
): boolean {
if (prev === next) {
return true;
}
if (prev instanceof AnimatedNode) {
return prev === next;
}
if (Array.isArray(prev)) {
if (!Array.isArray(next)) {
return false;
}
const length = prev.length;
if (length !== next.length) {
return false;
}
for (let ii = 0; ii < length; ii++) {
if (!areCompositeKeyComponentsEqual(prev[ii], next[ii])) {
return false;
}
}
return true;
}
if (isPlainObject(prev)) {
if (!isPlainObject(next)) {
return false;
}
const keys = Object.keys(prev);
const length = keys.length;
if (length !== Object.keys(next).length) {
return false;
}
for (let ii = 0; ii < length; ii++) {
const key = keys[ii];
if (
!hasOwn(nullthrows(next), key) ||
!areCompositeKeyComponentsEqual(prev[key], next[key])
) {
return false;
}
}
return true;
}
return false;
}
// Supported versions of JSC do not implement the newer Object.hasOwn. Remove
// this shim when they do.
// $FlowIgnore[method-unbinding]
const _hasOwnProp = Object.prototype.hasOwnProperty;
const hasOwn: (obj: $ReadOnly<{...}>, prop: string) => boolean =
// $FlowIgnore[method-unbinding]
Object.hasOwn ?? ((obj, prop) => _hasOwnProp.call(obj, prop));
@@ -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-local
* @format
*/
import type {ViewProps} from '../../../../Libraries/Components/View/ViewPropTypes';
import View from '../../../../Libraries/Components/View/View';
import UIManager from '../../../../Libraries/ReactNative/UIManager';
import Platform from '../../../../Libraries/Utilities/Platform';
import * as React from 'react';
const exported: component(
ref?: React.RefSetter<React.ElementRef<typeof View>>,
...ViewProps
) = Platform.select({
ios: require('../../../../src/private/specs_DEPRECATED/components/RCTSafeAreaViewNativeComponent')
.default,
android: UIManager.hasViewManagerConfig('RCTSafeAreaView')
? require('../../../../src/private/specs_DEPRECATED/components/RCTSafeAreaViewNativeComponent')
.default
: View,
default: View,
});
export default exported;
@@ -0,0 +1,29 @@
/**
* 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 {ScrollViewNativeProps} from '../../../../Libraries/Components/ScrollView/ScrollViewNativeComponentType';
import type {ViewProps} from '../../../../Libraries/Components/View/ViewPropTypes';
import type {HostComponent} from '../../types/HostComponent';
import AndroidHorizontalScrollViewNativeComponent from '../../../../Libraries/Components/ScrollView/AndroidHorizontalScrollViewNativeComponent';
import ScrollContentViewNativeComponent from '../../../../Libraries/Components/ScrollView/ScrollContentViewNativeComponent';
import ScrollViewNativeComponent from '../../../../Libraries/Components/ScrollView/ScrollViewNativeComponent';
import Platform from '../../../../Libraries/Utilities/Platform';
import AndroidHorizontalScrollContentViewNativeComponent from '../../specs_DEPRECATED/components/AndroidHorizontalScrollContentViewNativeComponent';
export const HScrollViewNativeComponent: HostComponent<ScrollViewNativeProps> =
Platform.OS === 'android'
? AndroidHorizontalScrollViewNativeComponent
: ScrollViewNativeComponent;
export const HScrollContentViewNativeComponent: HostComponent<ViewProps> =
Platform.OS === 'android'
? AndroidHorizontalScrollContentViewNativeComponent
: ScrollContentViewNativeComponent;
@@ -0,0 +1,24 @@
/**
* 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 {ScrollViewNativeProps} from '../../../../Libraries/Components/ScrollView/ScrollViewNativeComponentType';
import type {ViewProps} from '../../../../Libraries/Components/View/ViewPropTypes';
import type {HostComponent} from '../../types/HostComponent';
import ScrollContentViewNativeComponent from '../../../../Libraries/Components/ScrollView/ScrollContentViewNativeComponent';
import ScrollViewNativeComponent from '../../../../Libraries/Components/ScrollView/ScrollViewNativeComponent';
import View from '../../../../Libraries/Components/View/View';
import Platform from '../../../../Libraries/Utilities/Platform';
export const VScrollViewNativeComponent: HostComponent<ScrollViewNativeProps> =
ScrollViewNativeComponent;
export const VScrollContentViewNativeComponent: HostComponent<ViewProps> =
Platform.OS === 'android' ? View : ScrollContentViewNativeComponent;
@@ -0,0 +1,145 @@
/**
* 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 {ViewStyleProp} from '../../../../Libraries/StyleSheet/StyleSheet';
import type {NativeSyntheticEvent} from '../../../../Libraries/Types/CoreEventTypes';
import type ReadOnlyElement from '../../webapis/dom/nodes/ReadOnlyElement';
import type {NativeModeChangeEvent} from './VirtualViewNativeComponent';
import StyleSheet from '../../../../Libraries/StyleSheet/StyleSheet';
import VirtualViewNativeComponent from './VirtualViewNativeComponent';
import nullthrows from 'nullthrows';
import * as React from 'react';
import {startTransition, useState} from 'react';
// @see VirtualViewNativeComponent
export enum VirtualViewMode {
Visible = 0,
Prerender = 1,
Hidden = 2,
}
// @see VirtualViewNativeComponent
export enum VirtualViewRenderState {
Unknown = 0,
Rendered = 1,
None = 2,
}
export type Rect = $ReadOnly<{
x: number,
y: number,
width: number,
height: number,
}>;
export type ModeChangeEvent = $ReadOnly<{
...Omit<NativeModeChangeEvent, 'mode'>,
mode: VirtualViewMode,
target: ReadOnlyElement,
}>;
type VirtualViewComponent = component(
children?: React.Node,
nativeID?: string,
ref?: ?React.RefSetter<React.ElementRef<typeof VirtualViewNativeComponent>>,
style?: ?ViewStyleProp,
onModeChange?: (event: ModeChangeEvent) => void,
);
type HiddenHeight = number;
const NotHidden = null;
type State = HiddenHeight | typeof NotHidden;
function createVirtualView(initialState: State): VirtualViewComponent {
const initialHidden = initialState !== NotHidden;
component VirtualView(
children?: React.Node,
nativeID?: string,
ref?: ?React.RefSetter<React.ElementRef<typeof VirtualViewNativeComponent>>,
style?: ?ViewStyleProp,
onModeChange?: (event: ModeChangeEvent) => void,
) {
const [state, setState] = useState<State>(initialState);
if (__DEV__) {
_logs.states?.push(state);
}
const isHidden = state !== NotHidden;
const handleModeChange = (
event: NativeSyntheticEvent<NativeModeChangeEvent>,
) => {
const mode = nullthrows(VirtualViewMode.cast(event.nativeEvent.mode));
const emitModeChange =
onModeChange == null
? null
: onModeChange.bind(null, {
mode,
// $FlowIgnore[incompatible-cast]
target: event.currentTarget as ReadOnlyElement,
targetRect: event.nativeEvent.targetRect,
thresholdRect: event.nativeEvent.thresholdRect,
});
match (mode) {
VirtualViewMode.Visible => {
setState(NotHidden);
emitModeChange?.();
}
VirtualViewMode.Prerender => {
startTransition(() => {
setState(NotHidden);
emitModeChange?.();
});
}
VirtualViewMode.Hidden => {
const {height} = event.nativeEvent.targetRect;
startTransition(() => {
setState(height as HiddenHeight);
emitModeChange?.();
});
}
}
};
return (
<VirtualViewNativeComponent
initialHidden={initialHidden}
nativeID={nativeID}
ref={ref}
renderState={
(isHidden
? VirtualViewRenderState.None
: VirtualViewRenderState.Rendered) as number
}
style={
isHidden
? StyleSheet.compose(style, {
height: Math.abs(nullthrows(state) as HiddenHeight),
})
: style
}
onModeChange={handleModeChange}>
{isHidden ? null : children}
</VirtualViewNativeComponent>
);
}
return VirtualView;
}
export default createVirtualView(NotHidden) as VirtualViewComponent;
export function createHiddenVirtualView(height: number): VirtualViewComponent {
return createVirtualView(height as HiddenHeight);
}
export const _logs: {states?: Array<State>} = {};
@@ -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.
*
* @flow strict-local
* @format
*/
import type {ViewProps} from '../../../../Libraries/Components/View/ViewPropTypes';
import type {
DirectEventHandler,
Double,
Int32,
} from '../../../../Libraries/Types/CodegenTypes';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
export type NativeModeChangeEvent = $ReadOnly<{
/**
* Virtualization mode of the target view.
*
* - `0`: Target view is visible.
* - `1`: Target view is hidden, but can be prerendered.
* - `2`: Target view is hidden.
*
* WORKAROUND: As of this writing, codegen doesn't support enums, so we need
* to convert `number` into an enum in `VirtualView`.
*/
mode: Int32,
/**
* Rect of the target view, relative to the nearest ancestor scroll container.
*/
targetRect: $ReadOnly<{
x: Double,
y: Double,
width: Double,
height: Double,
}>,
/**
* Rect of the threshold that determines the mode of the target view, relative
* to the nearest ancestor scroll container.
*
* - `Visible`: Rect in which the target view is visible.
* - `Prerender`: Rect in which the target view is prerendered.
* - `Hidden`: Unused, without any guarantees.
*
* This can be used to determine whether and how much new content to render.
*/
thresholdRect: $ReadOnly<{
x: Double,
y: Double,
width: Double,
height: Double,
}>,
}>;
type VirtualViewNativeProps = $ReadOnly<{
...ViewProps,
/**
* Whether the initial mode should be `Hidden`.
*/
initialHidden?: boolean,
/**
* Render state of children.
*
* - `0`: Reserved to represent unknown future values.
* - `1`: Children are rendered.
* - `2`: Children are not rendered.
*
* WORKAROUND: As of this writing, codegen doesn't support enums, so we need
* to convert `number` into an enum in `VirtualView`.
*/
renderState: Int32,
/**
* See `NativeModeChangeEvent`.
*/
onModeChange?: ?DirectEventHandler<NativeModeChangeEvent>,
}>;
export default codegenNativeComponent<VirtualViewNativeProps>('VirtualView', {
interfaceOnly: true,
}) as HostComponent<VirtualViewNativeProps>;
@@ -0,0 +1,20 @@
/**
* 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
*/
/**
* The DevMenu module exposes methods for interacting with the Dev Menu in development.
*/
export interface DevMenuStatic {
/**
* Show the Dev Menu.
*/
show(): void;
}
export const DevMenu: DevMenuStatic;
@@ -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-local
* @format
*/
import NativeDevMenu from './specs/NativeDevMenu';
/**
* The DevMenu module exposes methods for interacting with the Dev Menu in development.
*/
type DevMenuStatic = {
/**
* Show the Dev Menu.
*/
show(): void,
};
const DevMenu: DevMenuStatic = {
show(): void {
if (__DEV__) {
NativeDevMenu.show?.();
}
},
};
export default DevMenu;
@@ -0,0 +1,44 @@
/**
* 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 {ViewStyleProp} from '../../../../../Libraries/StyleSheet/StyleSheet';
import * as React from 'react';
const View = require('../../../../../Libraries/Components/View/View').default;
type Props = $ReadOnly<{
children: React.Node,
box?: ?$ReadOnly<{
top: number,
right: number,
bottom: number,
left: number,
...
}>,
style?: ViewStyleProp,
}>;
function BorderBox({children, box, style}: Props): React.Node {
if (!box) {
return children;
}
const borderStyle = {
borderTopWidth: box.top,
borderBottomWidth: box.bottom,
borderLeftWidth: box.left,
borderRightWidth: box.right,
};
return <View style={[borderStyle, style]}>{children}</View>;
}
export default BorderBox;
@@ -0,0 +1,131 @@
/**
* 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 {
TextStyleProp,
ViewStyleProp,
} from '../../../../../Libraries/StyleSheet/StyleSheet';
import type {InspectedElementFrame} from './Inspector';
import * as React from 'react';
const View = require('../../../../../Libraries/Components/View/View').default;
const StyleSheet =
require('../../../../../Libraries/StyleSheet/StyleSheet').default;
const Text = require('../../../../../Libraries/Text/Text').default;
const resolveBoxStyle = require('./resolveBoxStyle').default;
const blank = {
top: 0,
left: 0,
right: 0,
bottom: 0,
};
type BoxInspectorProps = $ReadOnly<{
style: ViewStyleProp,
frame: ?InspectedElementFrame,
}>;
function BoxInspector({style, frame}: BoxInspectorProps): React.Node {
const margin = (style && resolveBoxStyle('margin', style)) || blank;
const padding = (style && resolveBoxStyle('padding', style)) || blank;
return (
<BoxContainer title="margin" titleStyle={styles.marginLabel} box={margin}>
<BoxContainer title="padding" box={padding}>
<View>
<Text style={styles.innerText}>
({(frame?.left || 0).toFixed(1)}, {(frame?.top || 0).toFixed(1)})
</Text>
<Text style={styles.innerText}>
{(frame?.width || 0).toFixed(1)} &times;{' '}
{(frame?.height || 0).toFixed(1)}
</Text>
</View>
</BoxContainer>
</BoxContainer>
);
}
type BoxContainerProps = $ReadOnly<{
title: string,
titleStyle?: TextStyleProp,
box: $ReadOnly<{
top: number,
left: number,
right: number,
bottom: number,
}>,
children: React.Node,
}>;
function BoxContainer({
title,
titleStyle,
box,
children,
}: BoxContainerProps): React.Node {
return (
<View style={styles.box}>
<View style={styles.row}>
{}
<Text style={[titleStyle, styles.label]}>{title}</Text>
<Text style={styles.boxText}>{box.top}</Text>
</View>
<View style={styles.row}>
<Text style={styles.boxText}>{box.left}</Text>
{children}
<Text style={styles.boxText}>{box.right}</Text>
</View>
<Text style={styles.boxText}>{box.bottom}</Text>
</View>
);
}
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
},
marginLabel: {
width: 60,
},
label: {
fontSize: 10,
color: 'rgb(255,100,0)',
marginLeft: 5,
flex: 1,
textAlign: 'left',
top: -3,
},
innerText: {
color: 'yellow',
fontSize: 12,
textAlign: 'center',
width: 70,
},
box: {
borderWidth: 1,
borderColor: 'grey',
},
boxText: {
color: 'white',
fontSize: 12,
marginHorizontal: 3,
marginVertical: 2,
textAlign: 'center',
},
});
export default BoxInspector;
@@ -0,0 +1,153 @@
/**
* 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 {ViewStyleProp} from '../../../../../Libraries/StyleSheet/StyleSheet';
import type {InspectedElementFrame} from './Inspector';
import * as React from 'react';
const View = require('../../../../../Libraries/Components/View/View').default;
const flattenStyle =
require('../../../../../Libraries/StyleSheet/flattenStyle').default;
const StyleSheet =
require('../../../../../Libraries/StyleSheet/StyleSheet').default;
const Dimensions =
require('../../../../../Libraries/Utilities/Dimensions').default;
const BorderBox = require('./BorderBox').default;
const resolveBoxStyle = require('./resolveBoxStyle').default;
type Props = $ReadOnly<{
frame: InspectedElementFrame,
style?: ?ViewStyleProp,
}>;
function ElementBox({frame, style}: Props): React.Node {
const flattenedStyle = flattenStyle(style) || {};
let margin: ?$ReadOnly<Style> = resolveBoxStyle('margin', flattenedStyle);
let padding: ?$ReadOnly<Style> = resolveBoxStyle('padding', flattenedStyle);
const frameStyle = {...frame};
const contentStyle: {width: number, height: number} = {
width: frame.width,
height: frame.height,
};
if (margin != null) {
margin = resolveRelativeSizes(margin);
frameStyle.top -= margin.top;
frameStyle.left -= margin.left;
frameStyle.height += margin.top + margin.bottom;
frameStyle.width += margin.left + margin.right;
if (margin.top < 0) {
contentStyle.height += margin.top;
}
if (margin.bottom < 0) {
contentStyle.height += margin.bottom;
}
if (margin.left < 0) {
contentStyle.width += margin.left;
}
if (margin.right < 0) {
contentStyle.width += margin.right;
}
}
if (padding != null) {
padding = resolveRelativeSizes(padding);
contentStyle.width -= padding.left + padding.right;
contentStyle.height -= padding.top + padding.bottom;
}
return (
<View style={[styles.frame, frameStyle]} pointerEvents="none">
<BorderBox box={margin} style={styles.margin}>
<BorderBox box={padding} style={styles.padding}>
<View style={[styles.content, contentStyle]} />
</BorderBox>
</BorderBox>
</View>
);
}
const styles = StyleSheet.create({
frame: {
position: 'absolute',
},
content: {
backgroundColor: 'rgba(200, 230, 255, 0.8)', // blue
},
padding: {
borderColor: 'rgba(77, 255, 0, 0.3)', // green
},
margin: {
borderColor: 'rgba(255, 132, 0, 0.3)', // orange
},
});
type Style = {
top: number,
right: number,
bottom: number,
left: number,
...
};
/**
* Resolves relative sizes (percentages and auto) in a style object.
*
* @param style the style to resolve
* @return a modified copy
*/
function resolveRelativeSizes(style: $ReadOnly<Style>): Style {
let resolvedStyle = {...style};
resolveSizeInPlace(resolvedStyle, 'top', 'height');
resolveSizeInPlace(resolvedStyle, 'right', 'width');
resolveSizeInPlace(resolvedStyle, 'bottom', 'height');
resolveSizeInPlace(resolvedStyle, 'left', 'width');
return resolvedStyle;
}
/**
* Resolves the given size of a style object in place.
*
* @param style the style object to modify
* @param direction the direction to resolve (e.g. 'top')
* @param dimension the window dimension that this direction belongs to (e.g. 'height')
*/
function resolveSizeInPlace(
style: Style,
direction: string,
dimension: string,
) {
// $FlowFixMe[invalid-computed-prop]
if (style[direction] !== null && typeof style[direction] === 'string') {
if (style[direction].indexOf('%') !== -1) {
// $FlowFixMe[prop-missing]
style[direction] =
// $FlowFixMe[invalid-computed-prop]
(parseFloat(style[direction]) / 100.0) *
// $FlowFixMe[invalid-computed-prop]
Dimensions.get('window')[dimension];
}
// $FlowFixMe[invalid-computed-prop]
if (style[direction] === 'auto') {
// Ignore auto sizing in frame drawing due to complexity of correctly rendering this
// $FlowFixMe[prop-missing]
style[direction] = 0;
}
}
}
export default ElementBox;
@@ -0,0 +1,126 @@
/**
* 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 {InspectorData} from '../../../../../Libraries/Renderer/shims/ReactNativeTypes';
import type {ViewStyleProp} from '../../../../../Libraries/StyleSheet/StyleSheet';
import * as React from 'react';
const TouchableHighlight =
require('../../../../../Libraries/Components/Touchable/TouchableHighlight').default;
const TouchableWithoutFeedback =
require('../../../../../Libraries/Components/Touchable/TouchableWithoutFeedback').default;
const View = require('../../../../../Libraries/Components/View/View').default;
const flattenStyle =
require('../../../../../Libraries/StyleSheet/flattenStyle').default;
const StyleSheet =
require('../../../../../Libraries/StyleSheet/StyleSheet').default;
const Text = require('../../../../../Libraries/Text/Text').default;
const mapWithSeparator =
require('../../../../../Libraries/Utilities/mapWithSeparator').default;
const BoxInspector = require('./BoxInspector').default;
const StyleInspector = require('./StyleInspector').default;
type Props = $ReadOnly<{
hierarchy: ?InspectorData['hierarchy'],
style?: ?ViewStyleProp,
frame?: ?Object,
selection?: ?number,
setSelection?: number => mixed,
}>;
class ElementProperties extends React.Component<Props> {
render(): React.Node {
const style = flattenStyle(this.props.style);
const selection = this.props.selection;
// Without the `TouchableWithoutFeedback`, taps on this inspector pane
// would change the inspected element to whatever is under the inspector
return (
<TouchableWithoutFeedback>
<View style={styles.info}>
<View style={styles.breadcrumb}>
{this.props.hierarchy != null &&
mapWithSeparator(
this.props.hierarchy,
(hierarchyItem, i): React.MixedElement => (
<TouchableHighlight
key={'item-' + i}
style={[
styles.breadItem,
i === selection && styles.selected,
]}
// $FlowFixMe[not-a-function] found when converting React.createClass to ES6
onPress={() => this.props.setSelection(i)}>
<Text style={styles.breadItemText}>
{hierarchyItem.name}
</Text>
</TouchableHighlight>
),
(i): React.MixedElement => (
<Text key={'sep-' + i} style={styles.breadSep}>
&#9656;
</Text>
),
)}
</View>
<View style={styles.row}>
<View style={styles.col}>
<StyleInspector style={style} />
</View>
<BoxInspector style={style} frame={this.props.frame} />
</View>
</View>
</TouchableWithoutFeedback>
);
}
}
const styles = StyleSheet.create({
breadSep: {
fontSize: 8,
color: 'white',
},
breadcrumb: {
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'flex-start',
marginBottom: 5,
},
selected: {
borderColor: 'white',
borderRadius: 5,
},
breadItem: {
borderWidth: 1,
borderColor: 'transparent',
marginHorizontal: 2,
},
breadItemText: {
fontSize: 10,
color: 'white',
marginHorizontal: 5,
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
col: {
flex: 1,
},
info: {
padding: 10,
},
});
export default ElementProperties;
@@ -0,0 +1,211 @@
/**
* 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 {InspectedViewRef} from '../../../../../Libraries/ReactNative/AppContainer-dev';
import type {
InspectorData,
TouchedViewDataAtPoint,
} from '../../../../../Libraries/Renderer/shims/ReactNativeTypes';
import type {ViewStyleProp} from '../../../../../Libraries/StyleSheet/StyleSheet';
import type {ReactDevToolsAgent} from '../../../../../Libraries/Types/ReactDevToolsTypes';
import SafeAreaView from '../../../components/safeareaview/SafeAreaView_INTERNAL_DO_NOT_USE';
import * as React from 'react';
const View = require('../../../../../Libraries/Components/View/View').default;
const PressabilityDebug = require('../../../../../Libraries/Pressability/PressabilityDebug');
const {
findNodeHandle,
} = require('../../../../../Libraries/ReactNative/RendererProxy');
const StyleSheet =
require('../../../../../Libraries/StyleSheet/StyleSheet').default;
const Dimensions =
require('../../../../../Libraries/Utilities/Dimensions').default;
const Platform = require('../../../../../Libraries/Utilities/Platform').default;
const getInspectorDataForViewAtPoint =
require('./getInspectorDataForViewAtPoint').default;
const InspectorOverlay = require('./InspectorOverlay').default;
const InspectorPanel = require('./InspectorPanel').default;
const {useState} = React;
type PanelPosition = 'top' | 'bottom';
type SelectedTab =
| 'elements-inspector'
| 'network-profiling'
| 'performance-profiling';
export type InspectedElementFrame = TouchedViewDataAtPoint['frame'];
export type InspectedElement = $ReadOnly<{
frame: InspectedElementFrame,
style?: ViewStyleProp,
}>;
export type ElementsHierarchy = InspectorData['hierarchy'];
type Props = {
inspectedViewRef: InspectedViewRef,
onRequestRerenderApp: () => void,
reactDevToolsAgent?: ReactDevToolsAgent,
};
function Inspector({
inspectedViewRef,
onRequestRerenderApp,
reactDevToolsAgent,
}: Props): React.Node {
const [selectedTab, setSelectedTab] =
useState<?SelectedTab>('elements-inspector');
const [panelPosition, setPanelPosition] = useState<PanelPosition>('bottom');
const [inspectedElement, setInspectedElement] =
useState<?InspectedElement>(null);
const [selectionIndex, setSelectionIndex] = useState<?number>(null);
const [elementsHierarchy, setElementsHierarchy] =
useState<?ElementsHierarchy>(null);
const setSelection = (i: number) => {
const hierarchyItem = elementsHierarchy?.[i];
if (hierarchyItem == null) {
return;
}
// We pass in findNodeHandle as the method is injected
const {measure, props} = hierarchyItem.getInspectorData(findNodeHandle);
measure((x, y, width, height, left, top) => {
// $FlowFixMe[incompatible-call] `props` from InspectorData are defined as <string, string> dictionary, which is incompatible with ViewStyleProp
setInspectedElement({
frame: {left, top, width, height},
style: props.style,
});
setSelectionIndex(i);
});
};
const onTouchPoint = (locationX: number, locationY: number) => {
const setTouchedViewData = (viewData: TouchedViewDataAtPoint) => {
const {
hierarchy,
props,
selectedIndex,
frame,
pointerY,
touchedViewTag,
closestInstance,
} = viewData;
// Sync the touched view with React DevTools.
// Note: This is Paper only. To support Fabric,
// DevTools needs to be updated to not rely on view tags.
if (reactDevToolsAgent) {
reactDevToolsAgent.selectNode(findNodeHandle(touchedViewTag));
if (closestInstance != null) {
reactDevToolsAgent.selectNode(closestInstance);
}
}
setPanelPosition(
pointerY > Dimensions.get('window').height / 2 ? 'top' : 'bottom',
);
setSelectionIndex(selectedIndex);
setElementsHierarchy(hierarchy);
// $FlowFixMe[incompatible-call] `props` from InspectorData are defined as <string, string> dictionary, which is incompatible with ViewStyleProp
setInspectedElement({
frame,
style: props.style,
});
};
getInspectorDataForViewAtPoint(
inspectedViewRef.current,
locationX,
locationY,
viewData => {
setTouchedViewData(viewData);
return false;
},
);
};
const setInspecting = (enabled: boolean) => {
setSelectedTab(enabled ? 'elements-inspector' : null);
setInspectedElement(null);
};
const setPerfing = (enabled: boolean) => {
setSelectedTab(enabled ? 'performance-profiling' : null);
setInspectedElement(null);
};
const setNetworking = (enabled: boolean) => {
setSelectedTab(enabled ? 'network-profiling' : null);
setInspectedElement(null);
};
const setTouchTargeting = (val: boolean) => {
PressabilityDebug.setEnabled(val);
onRequestRerenderApp();
};
const panelContainerStyle =
panelPosition === 'bottom'
? {bottom: 0}
: Platform.select({ios: {top: 0}, default: {top: 0}});
return (
<View style={styles.container} pointerEvents="box-none">
{selectedTab === 'elements-inspector' && (
<InspectorOverlay
inspected={inspectedElement}
onTouchPoint={onTouchPoint}
/>
)}
<SafeAreaView style={[styles.panelContainer, panelContainerStyle]}>
<InspectorPanel
devtoolsIsOpen={!!reactDevToolsAgent}
inspecting={selectedTab === 'elements-inspector'}
perfing={selectedTab === 'performance-profiling'}
setPerfing={setPerfing}
setInspecting={setInspecting}
inspected={inspectedElement}
hierarchy={elementsHierarchy}
selection={selectionIndex}
setSelection={setSelection}
touchTargeting={PressabilityDebug.isEnabled()}
setTouchTargeting={setTouchTargeting}
networking={selectedTab === 'network-profiling'}
setNetworking={setNetworking}
/>
</SafeAreaView>
</View>
);
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
backgroundColor: 'transparent',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
panelContainer: {
position: 'absolute',
left: 0,
right: 0,
},
});
export default Inspector;
@@ -0,0 +1,67 @@
/**
* 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 {GestureResponderEvent} from '../../../../../Libraries/Types/CoreEventTypes';
import type {InspectedElement} from './Inspector';
import * as React from 'react';
const View = require('../../../../../Libraries/Components/View/View').default;
const StyleSheet =
require('../../../../../Libraries/StyleSheet/StyleSheet').default;
const ElementBox = require('./ElementBox').default;
type Props = $ReadOnly<{
inspected?: ?InspectedElement,
onTouchPoint: (locationX: number, locationY: number) => void,
}>;
function InspectorOverlay({inspected, onTouchPoint}: Props): React.Node {
const findViewForTouchEvent = (e: GestureResponderEvent) => {
const {locationX, locationY} = e.nativeEvent.touches[0];
onTouchPoint(locationX, locationY);
};
const handleStartShouldSetResponder = (e: GestureResponderEvent): boolean => {
findViewForTouchEvent(e);
return true;
};
let content = null;
if (inspected) {
content = <ElementBox frame={inspected.frame} style={inspected.style} />;
}
return (
<View
onStartShouldSetResponder={handleStartShouldSetResponder}
onResponderMove={findViewForTouchEvent}
nativeID="inspectorOverlay" /* TODO: T68258846. */
style={styles.inspector}>
{content}
</View>
);
}
const styles = StyleSheet.create({
inspector: {
backgroundColor: 'transparent',
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
},
});
export default InspectorOverlay;
@@ -0,0 +1,167 @@
/**
* 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 {ElementsHierarchy, InspectedElement} from './Inspector';
import SafeAreaView from '../../../../../Libraries/Components/SafeAreaView/SafeAreaView';
import * as React from 'react';
const ScrollView =
require('../../../../../Libraries/Components/ScrollView/ScrollView').default;
const TouchableHighlight =
require('../../../../../Libraries/Components/Touchable/TouchableHighlight').default;
const View = require('../../../../../Libraries/Components/View/View').default;
const StyleSheet =
require('../../../../../Libraries/StyleSheet/StyleSheet').default;
const Text = require('../../../../../Libraries/Text/Text').default;
const PerformanceOverlay = require('../perfmonitor/PerformanceOverlay').default;
const ElementProperties = require('./ElementProperties').default;
const NetworkOverlay = require('./NetworkOverlay').default;
type Props = $ReadOnly<{
devtoolsIsOpen: boolean,
inspecting: boolean,
setInspecting: (val: boolean) => void,
perfing: boolean,
setPerfing: (val: boolean) => void,
touchTargeting: boolean,
setTouchTargeting: (val: boolean) => void,
networking: boolean,
setNetworking: (val: boolean) => void,
hierarchy?: ?ElementsHierarchy,
selection?: ?number,
setSelection: number => mixed,
inspected?: ?InspectedElement,
}>;
class InspectorPanel extends React.Component<Props> {
renderWaiting(): React.Node {
if (this.props.inspecting) {
return (
<Text style={styles.waitingText}>Tap something to inspect it</Text>
);
}
return <Text style={styles.waitingText}>Nothing is inspected</Text>;
}
render(): React.Node {
let contents;
if (this.props.inspected) {
contents = (
<ScrollView style={styles.properties}>
<ElementProperties
style={this.props.inspected.style}
frame={this.props.inspected.frame}
hierarchy={this.props.hierarchy}
selection={this.props.selection}
setSelection={this.props.setSelection}
/>
</ScrollView>
);
} else if (this.props.perfing) {
contents = <PerformanceOverlay />;
} else if (this.props.networking) {
contents = <NetworkOverlay />;
} else {
contents = <View style={styles.waiting}>{this.renderWaiting()}</View>;
}
return (
<SafeAreaView style={styles.container}>
{!this.props.devtoolsIsOpen && contents}
<View style={styles.buttonRow}>
<InspectorPanelButton
title={'Inspect'}
pressed={this.props.inspecting}
onClick={this.props.setInspecting}
/>
{global.RN$Bridgeless === true ? null : (
// These Inspector Panel sub-features are removed under the New Arch.
// See https://github.com/react-native-community/discussions-and-proposals/pull/777
<>
<InspectorPanelButton
title={'Perf'}
pressed={this.props.perfing}
onClick={this.props.setPerfing}
/>
<InspectorPanelButton
title={'Network'}
pressed={this.props.networking}
onClick={this.props.setNetworking}
/>
</>
)}
<InspectorPanelButton
title={'Touchables'}
pressed={this.props.touchTargeting}
onClick={this.props.setTouchTargeting}
/>
</View>
</SafeAreaView>
);
}
}
type InspectorPanelButtonProps = $ReadOnly<{
onClick: (val: boolean) => void,
pressed: boolean,
title: string,
}>;
class InspectorPanelButton extends React.Component<InspectorPanelButtonProps> {
render(): React.Node {
return (
<TouchableHighlight
onPress={() => this.props.onClick(!this.props.pressed)}
style={[styles.button, this.props.pressed && styles.buttonPressed]}>
<Text style={styles.buttonText}>{this.props.title}</Text>
</TouchableHighlight>
);
}
}
const styles = StyleSheet.create({
buttonRow: {
flexDirection: 'row',
},
button: {
backgroundColor: 'rgba(0, 0, 0, 0.3)',
margin: 2,
height: 30,
justifyContent: 'center',
alignItems: 'center',
},
buttonPressed: {
backgroundColor: 'rgba(255, 255, 255, 0.3)',
},
buttonText: {
textAlign: 'center',
color: 'white',
margin: 5,
},
container: {
backgroundColor: 'rgba(0, 0, 0, 0.7)',
},
properties: {
height: 200,
},
waiting: {
height: 100,
},
waitingText: {
fontSize: 20,
textAlign: 'center',
marginVertical: 20,
color: 'white',
},
});
export default InspectorPanel;
@@ -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.
*
* @flow
* @format
*/
'use strict';
import type XMLHttpRequest from '../../../../../Libraries/Network/XMLHttpRequest';
import type {ListRenderItemInfo} from '@react-native/virtualized-lists';
import ScrollView from '../../../../../Libraries/Components/ScrollView/ScrollView';
import * as React from 'react';
const TouchableHighlight =
require('../../../../../Libraries/Components/Touchable/TouchableHighlight').default;
const View = require('../../../../../Libraries/Components/View/View').default;
const FlatList = require('../../../../../Libraries/Lists/FlatList').default;
const StyleSheet =
require('../../../../../Libraries/StyleSheet/StyleSheet').default;
const Text = require('../../../../../Libraries/Text/Text').default;
const WebSocketInterceptor =
require('../../../../../Libraries/WebSocket/WebSocketInterceptor').default;
const XHRInterceptor = require('./XHRInterceptor').default;
const LISTVIEW_CELL_HEIGHT = 15;
// Global id for the intercepted XMLHttpRequest objects.
let nextXHRId = 0;
type NetworkRequestInfo = {
id: number,
type?: string,
url?: string,
method?: string,
status?: number,
dataSent?: any,
responseContentType?: string,
responseSize?: number,
requestHeaders?: Object,
responseHeaders?: string,
response?: Object | string,
responseURL?: string,
responseType?: string,
timeout?: number,
closeReason?: string,
messages?: string,
serverClose?: Object,
serverError?: Object,
...
};
type Props = $ReadOnly<{}>;
type State = {
detailRowId: ?number,
requests: Array<NetworkRequestInfo>,
};
function getStringByValue(value: any): string {
if (value === undefined) {
return 'undefined';
}
if (typeof value === 'object') {
return JSON.stringify(value);
}
if (typeof value === 'string' && value.length > 500) {
return String(value)
.slice(0, 500)
.concat('\n***TRUNCATED TO 500 CHARACTERS***');
}
return value;
}
function getTypeShortName(type: any): string {
if (type === 'XMLHttpRequest') {
return 'XHR';
} else if (type === 'WebSocket') {
return 'WS';
}
return '';
}
function keyExtractor(request: NetworkRequestInfo): string {
return String(request.id);
}
const XHR_ID_KEY = Symbol('XHR_ID');
function getXHRId(xhr: XMLHttpRequest): number {
// $FlowExpectedError[prop-missing]
return xhr[XHR_ID_KEY];
}
function setXHRId(xhr: XMLHttpRequest, id: number) {
// $FlowExpectedError[prop-missing]
xhr[XHR_ID_KEY] = id;
}
/**
* Show all the intercepted network requests over the InspectorPanel.
*/
class NetworkOverlay extends React.Component<Props, State> {
_requestsListView: ?React.ElementRef<Class<FlatList<NetworkRequestInfo>>>;
_detailScrollView: ?React.ElementRef<typeof ScrollView>;
// Metrics are used to decide when if the request list should be sticky, and
// scroll to the bottom as new network requests come in, or if the user has
// intentionally scrolled away from the bottom - to instead flash the scroll bar
// and keep the current position
_requestsListViewScrollMetrics: {
contentLength: number,
offset: number,
visibleLength: number,
} = {
offset: 0,
visibleLength: 0,
contentLength: 0,
};
// Map of `socketId` -> `index in `this.state.requests`.
_socketIdMap: {[number]: number} = {};
// Map of `xhr[XHR_ID_KEY]` -> `index in `this.state.requests`.
_xhrIdMap: {[key: number]: number, ...} = {};
state: State = {
detailRowId: null,
requests: [],
};
_enableXHRInterception(): void {
if (XHRInterceptor.isInterceptorEnabled()) {
return;
}
// Show the XHR request item in listView as soon as it was opened.
XHRInterceptor.setOpenCallback((method, url, xhr) => {
// Generate a global id for each intercepted xhr object, add this id
// to the xhr object as a private `_index` property to identify it,
// so that we can distinguish different xhr objects in callbacks.
setXHRId(xhr, nextXHRId++);
const xhrIndex = this.state.requests.length;
this._xhrIdMap[getXHRId(xhr)] = xhrIndex;
const _xhr: NetworkRequestInfo = {
id: xhrIndex,
type: 'XMLHttpRequest',
method: method,
url: url,
};
this.setState(
{
requests: this.state.requests.concat(_xhr),
},
this._indicateAdditionalRequests,
);
});
XHRInterceptor.setRequestHeaderCallback((header, value, xhr) => {
// $FlowFixMe[prop-missing]
const xhrIndex = this._getRequestIndexByXHRID(getXHRId(xhr));
if (xhrIndex === -1) {
return;
}
this.setState(({requests}) => {
const networkRequestInfo = requests[xhrIndex];
if (!networkRequestInfo.requestHeaders) {
networkRequestInfo.requestHeaders = ({}: {[any]: any});
}
networkRequestInfo.requestHeaders[header] = value;
return {requests};
});
});
XHRInterceptor.setSendCallback((data, xhr) => {
// $FlowFixMe[prop-missing]
const xhrIndex = this._getRequestIndexByXHRID(getXHRId(xhr));
if (xhrIndex === -1) {
return;
}
this.setState(({requests}) => {
const networkRequestInfo = requests[xhrIndex];
networkRequestInfo.dataSent = data;
return {requests};
});
});
XHRInterceptor.setHeaderReceivedCallback(
(type, size, responseHeaders, xhr) => {
// $FlowFixMe[prop-missing]
const xhrIndex = this._getRequestIndexByXHRID(getXHRId(xhr));
if (xhrIndex === -1) {
return;
}
this.setState(({requests}) => {
const networkRequestInfo = requests[xhrIndex];
networkRequestInfo.responseContentType = type;
networkRequestInfo.responseSize = size;
networkRequestInfo.responseHeaders = responseHeaders;
return {requests};
});
},
);
XHRInterceptor.setResponseCallback(
(status, timeout, response, responseURL, responseType, xhr) => {
// $FlowFixMe[prop-missing]
const xhrIndex = this._getRequestIndexByXHRID(getXHRId(xhr));
if (xhrIndex === -1) {
return;
}
this.setState(({requests}) => {
const networkRequestInfo = requests[xhrIndex];
networkRequestInfo.status = status;
networkRequestInfo.timeout = timeout;
networkRequestInfo.response = response;
networkRequestInfo.responseURL = responseURL;
networkRequestInfo.responseType = responseType;
return {requests};
});
},
);
// Fire above callbacks.
XHRInterceptor.enableInterception();
}
_enableWebSocketInterception(): void {
if (WebSocketInterceptor.isInterceptorEnabled()) {
return;
}
// Show the WebSocket request item in listView when 'connect' is called.
WebSocketInterceptor.setConnectCallback(
(url, protocols, options, socketId) => {
const socketIndex = this.state.requests.length;
this._socketIdMap[socketId] = socketIndex;
const _webSocket: NetworkRequestInfo = {
id: socketIndex,
type: 'WebSocket',
url: url,
protocols: protocols,
};
this.setState(
{
requests: this.state.requests.concat(_webSocket),
},
this._indicateAdditionalRequests,
);
},
);
WebSocketInterceptor.setCloseCallback(
(statusCode, closeReason, socketId) => {
const socketIndex = this._socketIdMap[socketId];
if (socketIndex === undefined) {
return;
}
if (statusCode !== null && closeReason !== null) {
this.setState(({requests}) => {
const networkRequestInfo = requests[socketIndex];
networkRequestInfo.status = statusCode;
networkRequestInfo.closeReason = closeReason;
return {requests};
});
}
},
);
WebSocketInterceptor.setSendCallback((data, socketId) => {
const socketIndex = this._socketIdMap[socketId];
if (socketIndex === undefined) {
return;
}
this.setState(({requests}) => {
const networkRequestInfo = requests[socketIndex];
if (!networkRequestInfo.messages) {
networkRequestInfo.messages = '';
}
networkRequestInfo.messages += 'Sent: ' + JSON.stringify(data) + '\n';
return {requests};
});
});
WebSocketInterceptor.setOnMessageCallback((message, socketId) => {
const socketIndex = this._socketIdMap[socketId];
if (socketIndex === undefined) {
return;
}
this.setState(({requests}) => {
const networkRequestInfo = requests[socketIndex];
if (!networkRequestInfo.messages) {
networkRequestInfo.messages = '';
}
networkRequestInfo.messages +=
'Received: ' + JSON.stringify(message) + '\n';
return {requests};
});
});
WebSocketInterceptor.setOnCloseCallback((message, socketId) => {
const socketIndex = this._socketIdMap[socketId];
if (socketIndex === undefined) {
return;
}
this.setState(({requests}) => {
const networkRequestInfo = requests[socketIndex];
networkRequestInfo.serverClose = message;
return {requests};
});
});
WebSocketInterceptor.setOnErrorCallback((message, socketId) => {
const socketIndex = this._socketIdMap[socketId];
if (socketIndex === undefined) {
return;
}
this.setState(({requests}) => {
const networkRequestInfo = requests[socketIndex];
networkRequestInfo.serverError = message;
return {requests};
});
});
// Fire above callbacks.
WebSocketInterceptor.enableInterception();
}
componentDidMount() {
this._enableXHRInterception();
this._enableWebSocketInterception();
}
componentWillUnmount() {
XHRInterceptor.disableInterception();
WebSocketInterceptor.disableInterception();
}
_renderItem = ({
item,
index,
}: ListRenderItemInfo<NetworkRequestInfo>): React.MixedElement => {
const tableRowViewStyle = [
styles.tableRow,
index % 2 === 1 ? styles.tableRowOdd : styles.tableRowEven,
index === this.state.detailRowId && styles.tableRowPressed,
];
const urlCellViewStyle = styles.urlCellView;
const methodCellViewStyle = styles.methodCellView;
return (
<TouchableHighlight
onPress={() => {
this._pressRow(index);
}}>
<View>
<View style={tableRowViewStyle}>
<View style={urlCellViewStyle}>
<Text style={styles.cellText} numberOfLines={1}>
{item.url}
</Text>
</View>
<View style={methodCellViewStyle}>
<Text style={styles.cellText} numberOfLines={1}>
{getTypeShortName(item.type)}
</Text>
</View>
</View>
</View>
</TouchableHighlight>
);
};
_renderItemDetail(id: number): React.Node {
const requestItem = this.state.requests[id];
const details = Object.keys(requestItem).map(key => {
if (key === 'id') {
return;
}
return (
<View style={styles.detailViewRow} key={key}>
<Text style={[styles.detailViewText, styles.detailKeyCellView]}>
{key}
</Text>
<Text style={[styles.detailViewText, styles.detailValueCellView]}>
{getStringByValue(requestItem[key])}
</Text>
</View>
);
});
return (
<View>
<TouchableHighlight
style={styles.closeButton}
onPress={this._closeButtonClicked}>
<View>
<Text style={styles.closeButtonText}>v</Text>
</View>
</TouchableHighlight>
<ScrollView
style={styles.detailScrollView}
ref={scrollRef => (this._detailScrollView = scrollRef)}>
{details}
</ScrollView>
</View>
);
}
_indicateAdditionalRequests = (): void => {
if (this._requestsListView) {
const distanceFromEndThreshold = LISTVIEW_CELL_HEIGHT * 2;
const {offset, visibleLength, contentLength} =
this._requestsListViewScrollMetrics;
const distanceFromEnd = contentLength - visibleLength - offset;
const isCloseToEnd = distanceFromEnd <= distanceFromEndThreshold;
if (isCloseToEnd) {
this._requestsListView.scrollToEnd();
} else {
this._requestsListView.flashScrollIndicators();
}
}
};
_captureRequestsListView = (listRef: ?FlatList<NetworkRequestInfo>): void => {
this._requestsListView = listRef;
};
_requestsListViewOnScroll = (e: Object): void => {
this._requestsListViewScrollMetrics.offset = e.nativeEvent.contentOffset.y;
this._requestsListViewScrollMetrics.visibleLength =
e.nativeEvent.layoutMeasurement.height;
this._requestsListViewScrollMetrics.contentLength =
e.nativeEvent.contentSize.height;
};
/**
* Popup a scrollView to dynamically show detailed information of
* the request, when pressing a row in the network flow listView.
*/
_pressRow(rowId: number): void {
this.setState({detailRowId: rowId}, this._scrollDetailToTop);
}
_scrollDetailToTop = (): void => {
if (this._detailScrollView) {
this._detailScrollView.scrollTo({
y: 0,
animated: false,
});
}
};
_closeButtonClicked = () => {
this.setState({detailRowId: null});
};
_getRequestIndexByXHRID(index: number): number {
if (index === undefined) {
return -1;
}
const xhrIndex = this._xhrIdMap[index];
if (xhrIndex === undefined) {
return -1;
} else {
return xhrIndex;
}
}
render(): React.Node {
const {requests, detailRowId} = this.state;
return (
<View style={styles.container}>
{detailRowId != null && this._renderItemDetail(detailRowId)}
<View style={styles.listViewTitle}>
{requests.length > 0 && (
<View style={styles.tableRow}>
<View style={styles.urlTitleCellView}>
<Text style={styles.cellText} numberOfLines={1}>
URL
</Text>
</View>
<View style={styles.methodTitleCellView}>
<Text style={styles.cellText} numberOfLines={1}>
Type
</Text>
</View>
</View>
)}
</View>
<FlatList
ref={this._captureRequestsListView}
onScroll={this._requestsListViewOnScroll}
style={styles.listView}
data={requests}
renderItem={this._renderItem}
keyExtractor={keyExtractor}
extraData={this.state}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
paddingTop: 10,
paddingBottom: 10,
paddingLeft: 5,
paddingRight: 5,
},
listViewTitle: {
height: 20,
},
listView: {
flex: 1,
height: 60,
},
tableRow: {
flexDirection: 'row',
flex: 1,
height: LISTVIEW_CELL_HEIGHT,
},
tableRowEven: {
backgroundColor: '#555',
},
tableRowOdd: {
backgroundColor: '#000',
},
tableRowPressed: {
backgroundColor: '#3B5998',
},
cellText: {
color: 'white',
fontSize: 12,
},
methodTitleCellView: {
height: 18,
borderColor: '#DCD7CD',
borderTopWidth: 1,
borderBottomWidth: 1,
borderRightWidth: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#444',
flex: 1,
},
urlTitleCellView: {
height: 18,
borderColor: '#DCD7CD',
borderTopWidth: 1,
borderBottomWidth: 1,
borderLeftWidth: 1,
borderRightWidth: 1,
justifyContent: 'center',
backgroundColor: '#444',
flex: 5,
paddingLeft: 3,
},
methodCellView: {
height: 15,
borderColor: '#DCD7CD',
borderRightWidth: 1,
alignItems: 'center',
justifyContent: 'center',
flex: 1,
},
urlCellView: {
height: 15,
borderColor: '#DCD7CD',
borderLeftWidth: 1,
borderRightWidth: 1,
justifyContent: 'center',
flex: 5,
paddingLeft: 3,
},
detailScrollView: {
flex: 1,
height: 180,
marginTop: 5,
marginBottom: 5,
},
detailKeyCellView: {
flex: 1.3,
},
detailValueCellView: {
flex: 2,
},
detailViewRow: {
flexDirection: 'row',
paddingHorizontal: 3,
},
detailViewText: {
color: 'white',
fontSize: 11,
},
closeButtonText: {
color: 'white',
fontSize: 10,
},
closeButton: {
marginTop: 5,
backgroundColor: '#888',
justifyContent: 'center',
alignItems: 'center',
},
});
export default NetworkOverlay;
@@ -0,0 +1,166 @@
/**
* 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 {InspectedViewRef} from '../../../../../Libraries/ReactNative/AppContainer-dev';
import type {PointerEvent} from '../../../../../Libraries/Types/CoreEventTypes';
import type {GestureResponderEvent} from '../../../../../Libraries/Types/CoreEventTypes';
import type {ReactDevToolsAgent} from '../../../../../Libraries/Types/ReactDevToolsTypes';
import type {InspectedElement} from './Inspector';
import View from '../../../../../Libraries/Components/View/View';
import ReactNativeFeatureFlags from '../../../../../Libraries/ReactNative/ReactNativeFeatureFlags';
import StyleSheet from '../../../../../Libraries/StyleSheet/StyleSheet';
import ElementBox from './ElementBox';
import * as React from 'react';
const getInspectorDataForViewAtPoint =
require('./getInspectorDataForViewAtPoint').default;
const {useEffect, useState, useCallback} = React;
type Props = {
inspectedViewRef: InspectedViewRef,
reactDevToolsAgent: ReactDevToolsAgent,
};
export default function ReactDevToolsOverlay({
inspectedViewRef,
reactDevToolsAgent,
}: Props): React.Node {
const [inspected, setInspected] = useState<?InspectedElement>(null);
const [isInspecting, setIsInspecting] = useState(false);
useEffect(() => {
function cleanup() {
reactDevToolsAgent.removeListener('shutdown', cleanup);
reactDevToolsAgent.removeListener(
'startInspectingNative',
onStartInspectingNative,
);
reactDevToolsAgent.removeListener(
'stopInspectingNative',
onStopInspectingNative,
);
}
function onStartInspectingNative() {
setIsInspecting(true);
}
function onStopInspectingNative() {
setIsInspecting(false);
}
reactDevToolsAgent.addListener('shutdown', cleanup);
reactDevToolsAgent.addListener(
'startInspectingNative',
onStartInspectingNative,
);
reactDevToolsAgent.addListener(
'stopInspectingNative',
onStopInspectingNative,
);
return cleanup;
}, [reactDevToolsAgent]);
const findViewForLocation = useCallback(
(x: number, y: number) => {
getInspectorDataForViewAtPoint(
inspectedViewRef.current,
x,
y,
viewData => {
const {frame, closestPublicInstance} = viewData;
if (closestPublicInstance == null) {
return false;
}
reactDevToolsAgent.selectNode(closestPublicInstance);
setInspected({frame});
return true;
},
);
},
[inspectedViewRef, reactDevToolsAgent],
);
const stopInspecting = useCallback(() => {
reactDevToolsAgent.stopInspectingNative(true);
setIsInspecting(false);
setInspected(null);
}, [reactDevToolsAgent]);
const onPointerMove = useCallback(
(e: PointerEvent) => {
findViewForLocation(e.nativeEvent.x, e.nativeEvent.y);
},
[findViewForLocation],
);
const onResponderMove = useCallback(
(e: GestureResponderEvent) => {
findViewForLocation(
e.nativeEvent.touches[0].locationX,
e.nativeEvent.touches[0].locationY,
);
},
[findViewForLocation],
);
const shouldSetResponder = useCallback(
(e: GestureResponderEvent): boolean => {
onResponderMove(e);
return true;
},
[onResponderMove],
);
const highlight = inspected ? <ElementBox frame={inspected.frame} /> : null;
if (isInspecting) {
const events =
// Pointer events only work on fabric
ReactNativeFeatureFlags.shouldEmitW3CPointerEvents()
? {
onPointerMove,
onPointerDown: onPointerMove,
onPointerUp: stopInspecting,
}
: {
onStartShouldSetResponder: shouldSetResponder,
onResponderMove: onResponderMove,
onResponderRelease: stopInspecting,
};
return (
<View
nativeID="devToolsInspectorOverlay"
style={styles.inspector}
{...events}>
{highlight}
</View>
);
}
return highlight;
}
const styles = StyleSheet.create({
inspector: {
backgroundColor: 'transparent',
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
},
});
@@ -0,0 +1,77 @@
/**
* 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 {ViewStyleProp} from '../../../../../Libraries/StyleSheet/StyleSheet';
import type {____FlattenStyleProp_Internal} from '../../../../../Libraries/StyleSheet/StyleSheetTypes';
import * as React from 'react';
const View = require('../../../../../Libraries/Components/View/View').default;
const StyleSheet =
require('../../../../../Libraries/StyleSheet/StyleSheet').default;
const Text = require('../../../../../Libraries/Text/Text').default;
type Props = $ReadOnly<{
style?: ?____FlattenStyleProp_Internal<ViewStyleProp>,
}>;
function StyleInspector({style}: Props): React.Node {
if (!style) {
return <Text style={styles.noStyle}>No style</Text>;
}
const names = Object.keys(style);
return (
<View style={styles.container}>
<View>
{names.map(name => (
<Text key={name} style={styles.attr}>
{name}:
</Text>
))}
</View>
<View>
{names.map(name => {
const value = style?.[name];
return (
<Text key={name} style={styles.value}>
{typeof value !== 'string' && typeof value !== 'number'
? JSON.stringify(value)
: value}
</Text>
);
})}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
},
attr: {
fontSize: 10,
color: '#ccc',
},
value: {
fontSize: 10,
color: 'white',
marginLeft: 10,
},
noStyle: {
color: 'white',
fontSize: 10,
},
});
export default StyleInspector;
@@ -0,0 +1,217 @@
/**
* 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 typeof XMLHttpRequestT from '../../../../../Libraries/Network/XMLHttpRequest';
const XMLHttpRequest: XMLHttpRequestT =
require('../../../../../Libraries/Network/XMLHttpRequest').default;
// $FlowFixMe[method-unbinding]
const originalXHROpen = XMLHttpRequest.prototype.open;
// $FlowFixMe[method-unbinding]
const originalXHRSend = XMLHttpRequest.prototype.send;
// $FlowFixMe[method-unbinding]
const originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
type XHRInterceptorOpenCallback = (
method: string,
url: string,
request: XMLHttpRequest,
) => void;
type XHRInterceptorSendCallback = (
data: string,
request: XMLHttpRequest,
) => void;
type XHRInterceptorRequestHeaderCallback = (
header: string,
value: string,
request: XMLHttpRequest,
) => void;
type XHRInterceptorHeaderReceivedCallback = (
responseContentType: string | void,
responseSize: number | void,
allHeaders: string,
request: XMLHttpRequest,
) => void;
type XHRInterceptorResponseCallback = (
status: number,
timeout: number,
response: string,
responseURL: string,
responseType: string,
request: XMLHttpRequest,
) => void;
let openCallback: XHRInterceptorOpenCallback | null;
let sendCallback: XHRInterceptorSendCallback | null;
let requestHeaderCallback: XHRInterceptorRequestHeaderCallback | null;
let headerReceivedCallback: XHRInterceptorHeaderReceivedCallback | null;
let responseCallback: XHRInterceptorResponseCallback | null;
let isInterceptorEnabled = false;
/**
* A network interceptor which monkey-patches XMLHttpRequest methods
* to gather all network requests/responses, in order to show their
* information in the React Native inspector development tool.
* This supports interception with XMLHttpRequest API, including Fetch API
* and any other third party libraries that depend on XMLHttpRequest.
*/
const XHRInterceptor = {
/**
* Invoked before XMLHttpRequest.open(...) is called.
*/
setOpenCallback(callback: XHRInterceptorOpenCallback) {
openCallback = callback;
},
/**
* Invoked before XMLHttpRequest.send(...) is called.
*/
setSendCallback(callback: XHRInterceptorSendCallback) {
sendCallback = callback;
},
/**
* Invoked after xhr's readyState becomes xhr.HEADERS_RECEIVED.
*/
setHeaderReceivedCallback(callback: XHRInterceptorHeaderReceivedCallback) {
headerReceivedCallback = callback;
},
/**
* Invoked after xhr's readyState becomes xhr.DONE.
*/
setResponseCallback(callback: XHRInterceptorResponseCallback) {
responseCallback = callback;
},
/**
* Invoked before XMLHttpRequest.setRequestHeader(...) is called.
*/
setRequestHeaderCallback(callback: XHRInterceptorRequestHeaderCallback) {
requestHeaderCallback = callback;
},
isInterceptorEnabled(): boolean {
return isInterceptorEnabled;
},
enableInterception() {
if (isInterceptorEnabled) {
return;
}
// Override `open` method for all XHR requests to intercept the request
// method and url, then pass them through the `openCallback`.
// $FlowFixMe[cannot-write]
// $FlowFixMe[missing-this-annot]
XMLHttpRequest.prototype.open = function (method: string, url: string) {
if (openCallback) {
openCallback(method, url, this);
}
originalXHROpen.apply(this, arguments);
};
// Override `setRequestHeader` method for all XHR requests to intercept
// the request headers, then pass them through the `requestHeaderCallback`.
// $FlowFixMe[cannot-write]
// $FlowFixMe[missing-this-annot]
XMLHttpRequest.prototype.setRequestHeader = function (
header: string,
value: string,
) {
if (requestHeaderCallback) {
requestHeaderCallback(header, value, this);
}
originalXHRSetRequestHeader.apply(this, arguments);
};
// Override `send` method of all XHR requests to intercept the data sent,
// register listeners to intercept the response, and invoke the callbacks.
// $FlowFixMe[cannot-write]
// $FlowFixMe[missing-this-annot]
XMLHttpRequest.prototype.send = function (data: string) {
if (sendCallback) {
sendCallback(data, this);
}
if (this.addEventListener) {
this.addEventListener(
'readystatechange',
() => {
if (!isInterceptorEnabled) {
return;
}
if (this.readyState === this.HEADERS_RECEIVED) {
const contentTypeString = this.getResponseHeader('Content-Type');
const contentLengthString =
this.getResponseHeader('Content-Length');
let responseContentType, responseSize;
if (contentTypeString) {
responseContentType = contentTypeString.split(';')[0];
}
if (contentLengthString) {
responseSize = parseInt(contentLengthString, 10);
}
if (headerReceivedCallback) {
headerReceivedCallback(
responseContentType,
responseSize,
this.getAllResponseHeaders(),
this,
);
}
}
if (this.readyState === this.DONE) {
if (responseCallback) {
responseCallback(
this.status,
this.timeout,
this.response,
this.responseURL,
this.responseType,
this,
);
}
}
},
false,
);
}
originalXHRSend.apply(this, arguments);
};
isInterceptorEnabled = true;
},
// Unpatch XMLHttpRequest methods and remove the callbacks.
disableInterception() {
if (!isInterceptorEnabled) {
return;
}
isInterceptorEnabled = false;
// $FlowFixMe[cannot-write]
XMLHttpRequest.prototype.send = originalXHRSend;
// $FlowFixMe[cannot-write]
XMLHttpRequest.prototype.open = originalXHROpen;
// $FlowFixMe[cannot-write]
XMLHttpRequest.prototype.setRequestHeader = originalXHRSetRequestHeader;
responseCallback = null;
openCallback = null;
sendCallback = null;
headerReceivedCallback = null;
requestHeaderCallback = null;
},
};
export default XHRInterceptor;
@@ -0,0 +1,81 @@
/**
* 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 {TouchedViewDataAtPoint} from '../../../../../Libraries/Renderer/shims/ReactNativeTypes';
import type {HostInstance} from '../../../types/HostInstance';
const invariant = require('invariant');
export type ReactRenderer = {
rendererConfig: {
getInspectorDataForViewAtPoint: (
inspectedView: ?HostInstance,
locationX: number,
locationY: number,
callback: Function,
) => void,
...
},
};
type AttachedRendererEventPayload = {id: number, renderer: ReactRenderer};
const reactDevToolsHook = (window: any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
invariant(
Boolean(reactDevToolsHook),
'getInspectorDataForViewAtPoint should not be used if React DevTools hook is not injected',
);
const renderers: Array<ReactRenderer> = Array.from(
(window: any).__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.values(),
);
const appendRenderer = ({renderer}: AttachedRendererEventPayload) =>
renderers.push(renderer);
reactDevToolsHook.on('renderer', appendRenderer);
function validateRenderers(): void {
invariant(
renderers.length > 0,
'Expected to find at least one React Native renderer on DevTools hook.',
);
}
function getInspectorDataForViewAtPoint(
inspectedView: ?HostInstance,
locationX: number,
locationY: number,
callback: (viewData: TouchedViewDataAtPoint) => boolean,
) {
validateRenderers();
let shouldBreak = false;
// Check all renderers for inspector data.
for (const renderer of renderers) {
if (shouldBreak) {
break;
}
if (renderer?.rendererConfig?.getInspectorDataForViewAtPoint != null) {
renderer.rendererConfig.getInspectorDataForViewAtPoint(
inspectedView,
locationX,
locationY,
viewData => {
// Only return with non-empty view data since only one renderer will have this view.
if (viewData && viewData.hierarchy.length > 0) {
shouldBreak = callback(viewData);
}
},
);
}
}
}
export default getInspectorDataForViewAtPoint;
@@ -0,0 +1,115 @@
/**
* 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';
const I18nManager =
require('../../../../../Libraries/ReactNative/I18nManager').default;
/**
* Resolve a style property into its component parts.
*
* For example:
*
* > resolveProperties('margin', {margin: 5, marginBottom: 10})
* {top: 5, left: 5, right: 5, bottom: 10}
*
* If no parts exist, this returns null.
*/
function resolveBoxStyle(
prefix: string,
style: Object,
): ?$ReadOnly<{
bottom: number,
left: number,
right: number,
top: number,
}> {
let hasParts = false;
const result = {
bottom: 0,
left: 0,
right: 0,
top: 0,
};
// TODO: Fix issues with multiple properties affecting the same side.
const styleForAll = style[prefix];
if (styleForAll != null) {
for (const key of Object.keys(result)) {
result[key] = styleForAll;
}
hasParts = true;
}
const styleForHorizontal = style[prefix + 'Horizontal'];
if (styleForHorizontal != null) {
result.left = styleForHorizontal;
result.right = styleForHorizontal;
hasParts = true;
} else {
const styleForLeft = style[prefix + 'Left'];
if (styleForLeft != null) {
result.left = styleForLeft;
hasParts = true;
}
const styleForRight = style[prefix + 'Right'];
if (styleForRight != null) {
result.right = styleForRight;
hasParts = true;
}
const styleForEnd = style[prefix + 'End'];
if (styleForEnd != null) {
const constants = I18nManager.getConstants();
if (constants.isRTL && constants.doLeftAndRightSwapInRTL) {
result.left = styleForEnd;
} else {
result.right = styleForEnd;
}
hasParts = true;
}
const styleForStart = style[prefix + 'Start'];
if (styleForStart != null) {
const constants = I18nManager.getConstants();
if (constants.isRTL && constants.doLeftAndRightSwapInRTL) {
result.right = styleForStart;
} else {
result.left = styleForStart;
}
hasParts = true;
}
}
const styleForVertical = style[prefix + 'Vertical'];
if (styleForVertical != null) {
result.bottom = styleForVertical;
result.top = styleForVertical;
hasParts = true;
} else {
const styleForBottom = style[prefix + 'Bottom'];
if (styleForBottom != null) {
result.bottom = styleForBottom;
hasParts = true;
}
const styleForTop = style[prefix + 'Top'];
if (styleForTop != null) {
result.top = styleForTop;
hasParts = true;
}
}
return hasParts ? result : null;
}
export default resolveBoxStyle;
@@ -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 * as React from 'react';
const View = require('../../../../../Libraries/Components/View/View').default;
const StyleSheet =
require('../../../../../Libraries/StyleSheet/StyleSheet').default;
const Text = require('../../../../../Libraries/Text/Text').default;
const PerformanceLogger =
require('../../../../../Libraries/Utilities/GlobalPerformanceLogger').default;
class PerformanceOverlay extends React.Component<{...}> {
render(): React.Node {
const perfLogs = PerformanceLogger.getTimespans();
const items = [];
for (const key in perfLogs) {
if (perfLogs[key]?.totalTime) {
const unit = key === 'BundleSize' ? 'b' : 'ms';
items.push(
<View style={styles.row} key={key}>
<Text style={[styles.text, styles.label]}>{key}</Text>
<Text style={[styles.text, styles.totalTime]}>
{perfLogs[key].totalTime + unit}
</Text>
</View>,
);
}
}
return <View style={styles.container}>{items}</View>;
}
}
const styles = StyleSheet.create({
container: {
height: 100,
paddingTop: 10,
},
label: {
flex: 1,
},
row: {
flexDirection: 'row',
paddingHorizontal: 10,
},
text: {
color: 'white',
fontSize: 12,
},
totalTime: {
paddingRight: 100,
},
});
export default PerformanceOverlay;
@@ -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
* @format
*/
import type {TurboModule} from '../../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+show: () => void;
+reload: () => void;
+setProfilingEnabled: (enabled: boolean) => void;
+setHotLoadingEnabled: (enabled: boolean) => void;
}
export default (TurboModuleRegistry.getEnforcing<Spec>('DevMenu'): Spec);
@@ -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.
*
* @flow strict
* @format
*/
class FuseboxSessionObserver {
#hasNativeSupport: boolean;
constructor() {
this.#hasNativeSupport = global.hasOwnProperty(
'__DEBUGGER_SESSION_OBSERVER__',
);
}
hasActiveSession(): boolean {
if (!this.#hasNativeSupport) {
return false;
}
return global.__DEBUGGER_SESSION_OBSERVER__.hasActiveSession;
}
subscribe(callback: (status: boolean) => void): () => void {
if (!this.#hasNativeSupport) {
return () => {};
}
global.__DEBUGGER_SESSION_OBSERVER__.subscribers.add(callback);
return () => {
global.__DEBUGGER_SESSION_OBSERVER__.subscribers.delete(callback);
};
}
}
const observerInstance: FuseboxSessionObserver = new FuseboxSessionObserver();
export default observerInstance;
@@ -0,0 +1,19 @@
/**
* 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
*/
import NativeReactDevToolsSettingsManager from './specs/NativeReactDevToolsSettingsManager';
export function setGlobalHookSettings(settings: string) {
NativeReactDevToolsSettingsManager?.setGlobalHookSettings(settings);
}
export function getGlobalHookSettings(): ?string {
return NativeReactDevToolsSettingsManager?.getGlobalHookSettings();
}
@@ -0,0 +1,27 @@
/**
* 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 Settings from '../../../../Libraries/Settings/Settings';
const GLOBAL_HOOK_SETTINGS = 'ReactDevTools::HookSettings';
export function setGlobalHookSettings(settings: string) {
Settings.set({
[GLOBAL_HOOK_SETTINGS]: settings,
});
}
export function getGlobalHookSettings(): ?string {
const value = Settings.get(GLOBAL_HOOK_SETTINGS);
if (typeof value === 'string') {
return value;
}
return null;
}
@@ -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
* @format
*/
declare export function setGlobalHookSettings(settings: string): void;
declare export function getGlobalHookSettings(): ?string;
@@ -0,0 +1,113 @@
/**
* 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
*/
type JSONValue =
| string
| number
| boolean
| null
| {[key: string]: JSONValue}
| Array<JSONValue>;
type DomainName = 'react-devtools';
class EventScope<T> {
#listeners: Set<(T) => void> = new Set();
addEventListener(listener: T => void): void {
this.#listeners.add(listener);
}
removeEventListener(listener: T => void): void {
this.#listeners.delete(listener);
}
emit(value: T): void {
// Assuming that listeners won't throw.
for (const listener of this.#listeners) {
listener(value);
}
}
}
export class Domain {
name: DomainName;
onMessage: EventScope<JSONValue>;
constructor(name: DomainName) {
if (global[FuseboxReactDevToolsDispatcher.BINDING_NAME] == null) {
throw new Error(
`Could not create domain ${name}: receiving end doesn't exist`,
);
}
this.name = name;
this.onMessage = new EventScope<JSONValue>();
}
sendMessage(message: JSONValue) {
const messageWithDomain = {domain: this.name, message};
const serializedMessageWithDomain = JSON.stringify(messageWithDomain);
global[FuseboxReactDevToolsDispatcher.BINDING_NAME](
serializedMessageWithDomain,
);
}
}
/**
* Globally bound object providing a hook for React DevTools runtime API calls
* over CDP.
*
* @see {@link ./__docs__/FuseboxReactDevToolsDispatcher.excalidraw.svg}
*/
class FuseboxReactDevToolsDispatcher {
static #domainNameToDomainMap: Map<DomainName, Domain> = new Map();
// Referenced and initialized from Chrome DevTools frontend.
static BINDING_NAME: string = '__CHROME_DEVTOOLS_FRONTEND_BINDING__';
static onDomainInitialization: EventScope<Domain> = new EventScope<Domain>();
// Should be private, referenced from Chrome DevTools frontend only.
static initializeDomain(domainName: DomainName): Domain {
const domain = new Domain(domainName);
this.#domainNameToDomainMap.set(domainName, domain);
this.onDomainInitialization.emit(domain);
return domain;
}
// Should be private, referenced from Chrome DevTools frontend only.
static sendMessage(domainName: DomainName, message: string): void {
const domain = this.#domainNameToDomainMap.get(domainName);
if (domain == null) {
throw new Error(
`Could not send message to ${domainName}: domain doesn't exist`,
);
}
try {
const parsedMessage = JSON.parse(message);
domain.onMessage.emit(parsedMessage);
} catch (err) {
console.error(
`Error while trying to send a message to domain ${domainName}:`,
err,
);
}
}
}
Object.defineProperty(global, '__FUSEBOX_REACT_DEVTOOLS_DISPATCHER__', {
value: FuseboxReactDevToolsDispatcher,
configurable: false,
enumerable: false,
writable: false,
});
@@ -0,0 +1,33 @@
/**
* 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 {TurboModule} from '../../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../../Libraries/TurboModule/TurboModuleRegistry';
export type ReloadAndProfileConfig = {
shouldReloadAndProfile: boolean,
recordChangeDescriptions: boolean,
};
// Linter doesn't speak Flow's `Partial` type
export type PartialReloadAndProfileConfig = {
shouldReloadAndProfile?: boolean,
recordChangeDescriptions?: boolean,
};
export interface Spec extends TurboModule {
+setReloadAndProfileConfig: (config: PartialReloadAndProfileConfig) => void;
+getReloadAndProfileConfig: () => ReloadAndProfileConfig;
}
export default (TurboModuleRegistry.get<Spec>(
'ReactDevToolsRuntimeSettingsModule',
): ?Spec);
@@ -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
* @format
*/
import type {TurboModule} from '../../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+setGlobalHookSettings: (settings: string) => void;
+getGlobalHookSettings: () => ?string;
}
export default (TurboModuleRegistry.get<Spec>(
'ReactDevToolsSettingsManager',
): ?Spec);
@@ -0,0 +1,450 @@
/**
* 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 SignedSource<<f17b8e4e33228e19b346837e7a33a2dd>>
* @flow strict
* @noformat
*/
/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags --update
*/
import {
type Getter,
type OverridesFor,
createJavaScriptFlagGetter,
createNativeFlagGetter,
setOverrides,
} from './ReactNativeFeatureFlagsBase';
export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{
jsOnlyTestFlag: Getter<boolean>,
alwaysFlattenAnimatedStyles: Getter<boolean>,
animatedShouldDebounceQueueFlush: Getter<boolean>,
animatedShouldUseSingleOp: Getter<boolean>,
avoidStateUpdateInAnimatedPropsMemo: Getter<boolean>,
deferFlatListFocusChangeRenderUpdate: Getter<boolean>,
disableInteractionManager: Getter<boolean>,
enableAccessToHostTreeInFabric: Getter<boolean>,
fixVirtualizeListCollapseWindowSize: Getter<boolean>,
isLayoutAnimationEnabled: Getter<boolean>,
reduceDefaultPropsInView: Getter<boolean>,
scheduleAnimatedCleanupInMicrotask: Getter<boolean>,
shouldUseAnimatedObjectForTransform: Getter<boolean>,
shouldUseRemoveClippedSubviewsAsDefaultOnIOS: Getter<boolean>,
shouldUseSetNativePropsInFabric: Getter<boolean>,
utilizeTokensInIntersectionObserver: Getter<boolean>,
}>;
export type ReactNativeFeatureFlagsJsOnlyOverrides = OverridesFor<ReactNativeFeatureFlagsJsOnly>;
export type ReactNativeFeatureFlags = $ReadOnly<{
...ReactNativeFeatureFlagsJsOnly,
commonTestFlag: Getter<boolean>,
commonTestFlagWithoutNativeImplementation: Getter<boolean>,
animatedShouldSignalBatch: Getter<boolean>,
cxxNativeAnimatedEnabled: Getter<boolean>,
cxxNativeAnimatedRemoveJsSync: Getter<boolean>,
disableMainQueueSyncDispatchIOS: Getter<boolean>,
disableMountItemReorderingAndroid: Getter<boolean>,
disableTextLayoutManagerCacheAndroid: Getter<boolean>,
enableAccessibilityOrder: Getter<boolean>,
enableAccumulatedUpdatesInRawPropsAndroid: Getter<boolean>,
enableAndroidTextMeasurementOptimizations: Getter<boolean>,
enableBridgelessArchitecture: Getter<boolean>,
enableCppPropsIteratorSetter: Getter<boolean>,
enableCustomFocusSearchOnClippedElementsAndroid: Getter<boolean>,
enableDestroyShadowTreeRevisionAsync: Getter<boolean>,
enableDoubleMeasurementFixAndroid: Getter<boolean>,
enableEagerRootViewAttachment: Getter<boolean>,
enableFabricLogs: Getter<boolean>,
enableFabricRenderer: Getter<boolean>,
enableFixForParentTagDuringReparenting: Getter<boolean>,
enableFontScaleChangesUpdatingLayout: Getter<boolean>,
enableIOSTextBaselineOffsetPerLine: Getter<boolean>,
enableIOSViewClipToPaddingBox: Getter<boolean>,
enableInteropViewManagerClassLookUpOptimizationIOS: Getter<boolean>,
enableLayoutAnimationsOnAndroid: Getter<boolean>,
enableLayoutAnimationsOnIOS: Getter<boolean>,
enableMainQueueCoordinatorOnIOS: Getter<boolean>,
enableMainQueueModulesOnIOS: Getter<boolean>,
enableModuleArgumentNSNullConversionIOS: Getter<boolean>,
enableNativeCSSParsing: Getter<boolean>,
enableNetworkEventReporting: Getter<boolean>,
enableNewBackgroundAndBorderDrawables: Getter<boolean>,
enablePreparedTextLayout: Getter<boolean>,
enablePropsUpdateReconciliationAndroid: Getter<boolean>,
enableResourceTimingAPI: Getter<boolean>,
enableSynchronousStateUpdates: Getter<boolean>,
enableViewCulling: Getter<boolean>,
enableViewRecycling: Getter<boolean>,
enableViewRecyclingForText: Getter<boolean>,
enableViewRecyclingForView: Getter<boolean>,
enableVirtualViewDebugFeatures: Getter<boolean>,
enableVirtualViewRenderState: Getter<boolean>,
enableVirtualViewWindowFocusDetection: Getter<boolean>,
fixMappingOfEventPrioritiesBetweenFabricAndReact: Getter<boolean>,
fuseboxEnabledRelease: Getter<boolean>,
fuseboxNetworkInspectionEnabled: Getter<boolean>,
hideOffscreenVirtualViewsOnIOS: Getter<boolean>,
preparedTextCacheSize: Getter<number>,
preventShadowTreeCommitExhaustion: Getter<boolean>,
traceTurboModulePromiseRejectionsOnAndroid: Getter<boolean>,
updateRuntimeShadowNodeReferencesOnCommit: Getter<boolean>,
useAlwaysAvailableJSErrorHandling: Getter<boolean>,
useFabricInterop: Getter<boolean>,
useNativeEqualsInNativeReadableArrayAndroid: Getter<boolean>,
useNativeTransformHelperAndroid: Getter<boolean>,
useNativeViewConfigsInBridgelessMode: Getter<boolean>,
useOptimizedEventBatchingOnAndroid: Getter<boolean>,
useRawPropsJsiValue: Getter<boolean>,
useShadowNodeStateOnClone: Getter<boolean>,
useTurboModuleInterop: Getter<boolean>,
useTurboModules: Getter<boolean>,
virtualViewPrerenderRatio: Getter<number>,
}>;
/**
* JS-only flag for testing. Do NOT modify.
*/
export const jsOnlyTestFlag: Getter<boolean> = createJavaScriptFlagGetter('jsOnlyTestFlag', false);
/**
* Changes `Animated` to always flatten style, fixing a bug with shadowed `AnimatedNode` instances.
*/
export const alwaysFlattenAnimatedStyles: Getter<boolean> = createJavaScriptFlagGetter('alwaysFlattenAnimatedStyles', false);
/**
* Enables an experimental flush-queue debouncing in Animated.js.
*/
export const animatedShouldDebounceQueueFlush: Getter<boolean> = createJavaScriptFlagGetter('animatedShouldDebounceQueueFlush', false);
/**
* Enables an experimental mega-operation for Animated.js that replaces many calls to native with a single call into native, to reduce JSI/JNI traffic.
*/
export const animatedShouldUseSingleOp: Getter<boolean> = createJavaScriptFlagGetter('animatedShouldUseSingleOp', false);
/**
* Changes `useAnimatedPropsMemo` to avoid state updates to invalidate the cached `AnimatedProps`.
*/
export const avoidStateUpdateInAnimatedPropsMemo: Getter<boolean> = createJavaScriptFlagGetter('avoidStateUpdateInAnimatedPropsMemo', true);
/**
* Use the deferred cell render update mechanism for focus change in FlatList.
*/
export const deferFlatListFocusChangeRenderUpdate: Getter<boolean> = createJavaScriptFlagGetter('deferFlatListFocusChangeRenderUpdate', false);
/**
* Disables InteractionManager and replaces its scheduler with `setImmediate`.
*/
export const disableInteractionManager: Getter<boolean> = createJavaScriptFlagGetter('disableInteractionManager', true);
/**
* Enables access to the host tree in Fabric using DOM-compatible APIs.
*/
export const enableAccessToHostTreeInFabric: Getter<boolean> = createJavaScriptFlagGetter('enableAccessToHostTreeInFabric', false);
/**
* Fixing an edge case where the current window size is not properly calculated with fast scrolling. Window size collapsed to 1 element even if windowSize more than the current amount of elements
*/
export const fixVirtualizeListCollapseWindowSize: Getter<boolean> = createJavaScriptFlagGetter('fixVirtualizeListCollapseWindowSize', false);
/**
* Function used to enable / disabled Layout Animations in React Native.
*/
export const isLayoutAnimationEnabled: Getter<boolean> = createJavaScriptFlagGetter('isLayoutAnimationEnabled', true);
/**
* Optimize how default (accessibility) props are processed in View to avoid unnecessary keys.
*/
export const reduceDefaultPropsInView: Getter<boolean> = createJavaScriptFlagGetter('reduceDefaultPropsInView', true);
/**
* Changes the cleanup of `AnimatedProps` to occur in a microtask instead of synchronously during effect cleanup (for unmount) or subsequent mounts (for updates).
*/
export const scheduleAnimatedCleanupInMicrotask: Getter<boolean> = createJavaScriptFlagGetter('scheduleAnimatedCleanupInMicrotask', true);
/**
* Enables use of AnimatedObject for animating transform values.
*/
export const shouldUseAnimatedObjectForTransform: Getter<boolean> = createJavaScriptFlagGetter('shouldUseAnimatedObjectForTransform', false);
/**
* removeClippedSubviews prop will be used as the default in FlatList on iOS to match Android
*/
export const shouldUseRemoveClippedSubviewsAsDefaultOnIOS: Getter<boolean> = createJavaScriptFlagGetter('shouldUseRemoveClippedSubviewsAsDefaultOnIOS', false);
/**
* Enables use of setNativeProps in JS driven animations.
*/
export const shouldUseSetNativePropsInFabric: Getter<boolean> = createJavaScriptFlagGetter('shouldUseSetNativePropsInFabric', true);
/**
* Use tokens in IntersectionObserver vs ShadowNode.
*/
export const utilizeTokensInIntersectionObserver: Getter<boolean> = createJavaScriptFlagGetter('utilizeTokensInIntersectionObserver', true);
/**
* Common flag for testing. Do NOT modify.
*/
export const commonTestFlag: Getter<boolean> = createNativeFlagGetter('commonTestFlag', false);
/**
* Common flag for testing (without native implementation). Do NOT modify.
*/
export const commonTestFlagWithoutNativeImplementation: Getter<boolean> = createNativeFlagGetter('commonTestFlagWithoutNativeImplementation', false);
/**
* Enables start- and finishOperationBatch on any platform.
*/
export const animatedShouldSignalBatch: Getter<boolean> = createNativeFlagGetter('animatedShouldSignalBatch', false);
/**
* Use a C++ implementation of Native Animated instead of the platform implementation.
*/
export const cxxNativeAnimatedEnabled: Getter<boolean> = createNativeFlagGetter('cxxNativeAnimatedEnabled', false);
/**
* Removes JS sync at end of native animation
*/
export const cxxNativeAnimatedRemoveJsSync: Getter<boolean> = createNativeFlagGetter('cxxNativeAnimatedRemoveJsSync', false);
/**
* Disable sync dispatch on the main queue on iOS
*/
export const disableMainQueueSyncDispatchIOS: Getter<boolean> = createNativeFlagGetter('disableMainQueueSyncDispatchIOS', false);
/**
* Prevent FabricMountingManager from reordering mountItems, which may lead to invalid state on the UI thread
*/
export const disableMountItemReorderingAndroid: Getter<boolean> = createNativeFlagGetter('disableMountItemReorderingAndroid', false);
/**
* Turns off the global measurement cache used by TextLayoutManager on Android.
*/
export const disableTextLayoutManagerCacheAndroid: Getter<boolean> = createNativeFlagGetter('disableTextLayoutManagerCacheAndroid', false);
/**
* When enabled, the accessibilityOrder prop will propagate to native platforms and define the accessibility order.
*/
export const enableAccessibilityOrder: Getter<boolean> = createNativeFlagGetter('enableAccessibilityOrder', false);
/**
* When enabled, Android will accumulate updates in rawProps to reduce the number of mounting instructions for cascading re-renders.
*/
export const enableAccumulatedUpdatesInRawPropsAndroid: Getter<boolean> = createNativeFlagGetter('enableAccumulatedUpdatesInRawPropsAndroid', false);
/**
* Enables various optimizations throughout the path of measuring text on Android.
*/
export const enableAndroidTextMeasurementOptimizations: Getter<boolean> = createNativeFlagGetter('enableAndroidTextMeasurementOptimizations', false);
/**
* Feature flag to enable the new bridgeless architecture. Note: Enabling this will force enable the following flags: `useTurboModules` & `enableFabricRenderer`.
*/
export const enableBridgelessArchitecture: Getter<boolean> = createNativeFlagGetter('enableBridgelessArchitecture', false);
/**
* Enable prop iterator setter-style construction of Props in C++ (this flag is not used in Java).
*/
export const enableCppPropsIteratorSetter: Getter<boolean> = createNativeFlagGetter('enableCppPropsIteratorSetter', false);
/**
* This enables the fabric implementation of focus search so that we can focus clipped elements
*/
export const enableCustomFocusSearchOnClippedElementsAndroid: Getter<boolean> = createNativeFlagGetter('enableCustomFocusSearchOnClippedElementsAndroid', true);
/**
* Enables destructor calls for ShadowTreeRevision in the background to reduce UI thread work.
*/
export const enableDestroyShadowTreeRevisionAsync: Getter<boolean> = createNativeFlagGetter('enableDestroyShadowTreeRevisionAsync', false);
/**
* When enabled a subset of components will avoid double measurement on Android.
*/
export const enableDoubleMeasurementFixAndroid: Getter<boolean> = createNativeFlagGetter('enableDoubleMeasurementFixAndroid', false);
/**
* Feature flag to configure eager attachment of the root view/initialisation of the JS code.
*/
export const enableEagerRootViewAttachment: Getter<boolean> = createNativeFlagGetter('enableEagerRootViewAttachment', false);
/**
* This feature flag enables logs for Fabric.
*/
export const enableFabricLogs: Getter<boolean> = createNativeFlagGetter('enableFabricLogs', false);
/**
* Enables the use of the Fabric renderer in the whole app.
*/
export const enableFabricRenderer: Getter<boolean> = createNativeFlagGetter('enableFabricRenderer', false);
/**
* This feature flag enables a fix for reparenting fix in differentiator
*/
export const enableFixForParentTagDuringReparenting: Getter<boolean> = createNativeFlagGetter('enableFixForParentTagDuringReparenting', false);
/**
* Enables font scale changes updating layout for measurable nodes.
*/
export const enableFontScaleChangesUpdatingLayout: Getter<boolean> = createNativeFlagGetter('enableFontScaleChangesUpdatingLayout', false);
/**
* Applies base offset for each line of text separately on iOS.
*/
export const enableIOSTextBaselineOffsetPerLine: Getter<boolean> = createNativeFlagGetter('enableIOSTextBaselineOffsetPerLine', false);
/**
* iOS Views will clip to their padding box vs border box
*/
export const enableIOSViewClipToPaddingBox: Getter<boolean> = createNativeFlagGetter('enableIOSViewClipToPaddingBox', false);
/**
* This is to fix the issue with interop view manager where component descriptor lookup is causing ViewManager to preload.
*/
export const enableInteropViewManagerClassLookUpOptimizationIOS: Getter<boolean> = createNativeFlagGetter('enableInteropViewManagerClassLookUpOptimizationIOS', false);
/**
* When enabled, LayoutAnimations API will animate state changes on Android.
*/
export const enableLayoutAnimationsOnAndroid: Getter<boolean> = createNativeFlagGetter('enableLayoutAnimationsOnAndroid', false);
/**
* When enabled, LayoutAnimations API will animate state changes on iOS.
*/
export const enableLayoutAnimationsOnIOS: Getter<boolean> = createNativeFlagGetter('enableLayoutAnimationsOnIOS', true);
/**
* Make RCTUnsafeExecuteOnMainQueueSync less likely to deadlock, when used in conjuction with sync rendering/events.
*/
export const enableMainQueueCoordinatorOnIOS: Getter<boolean> = createNativeFlagGetter('enableMainQueueCoordinatorOnIOS', false);
/**
* Makes modules requiring main queue setup initialize on the main thread, during React Native init.
*/
export const enableMainQueueModulesOnIOS: Getter<boolean> = createNativeFlagGetter('enableMainQueueModulesOnIOS', false);
/**
* Enable NSNull conversion when handling module arguments on iOS
*/
export const enableModuleArgumentNSNullConversionIOS: Getter<boolean> = createNativeFlagGetter('enableModuleArgumentNSNullConversionIOS', false);
/**
* Parse CSS strings using the Fabric CSS parser instead of ViewConfig processing
*/
export const enableNativeCSSParsing: Getter<boolean> = createNativeFlagGetter('enableNativeCSSParsing', false);
/**
* Enable network event reporting hooks in each native platform through `NetworkReporter`. This flag should be combined with `enableResourceTimingAPI` and `fuseboxNetworkInspectionEnabled` to enable end-to-end reporting behaviour via the Web Performance API and CDP debugging respectively.
*/
export const enableNetworkEventReporting: Getter<boolean> = createNativeFlagGetter('enableNetworkEventReporting', false);
/**
* Use BackgroundDrawable and BorderDrawable instead of CSSBackgroundDrawable
*/
export const enableNewBackgroundAndBorderDrawables: Getter<boolean> = createNativeFlagGetter('enableNewBackgroundAndBorderDrawables', true);
/**
* Enables caching text layout artifacts for later reuse
*/
export const enablePreparedTextLayout: Getter<boolean> = createNativeFlagGetter('enablePreparedTextLayout', false);
/**
* When enabled, Android will receive prop updates based on the differences between the last rendered shadow node and the last committed shadow node.
*/
export const enablePropsUpdateReconciliationAndroid: Getter<boolean> = createNativeFlagGetter('enablePropsUpdateReconciliationAndroid', false);
/**
* Enables the reporting of network resource timings through `PerformanceObserver`.
*/
export const enableResourceTimingAPI: Getter<boolean> = createNativeFlagGetter('enableResourceTimingAPI', false);
/**
* Dispatches state updates synchronously in Fabric (e.g.: updates the scroll position in the shadow tree synchronously from the main thread).
*/
export const enableSynchronousStateUpdates: Getter<boolean> = createNativeFlagGetter('enableSynchronousStateUpdates', false);
/**
* Enables View Culling: as soon as a view goes off screen, it can be reused anywhere in the UI and pieced together with other items to create new UI elements.
*/
export const enableViewCulling: Getter<boolean> = createNativeFlagGetter('enableViewCulling', false);
/**
* Enables View Recycling. When enabled, individual ViewManagers must still opt-in.
*/
export const enableViewRecycling: Getter<boolean> = createNativeFlagGetter('enableViewRecycling', false);
/**
* Enables View Recycling for <Text> via ReactTextView/ReactTextViewManager.
*/
export const enableViewRecyclingForText: Getter<boolean> = createNativeFlagGetter('enableViewRecyclingForText', true);
/**
* Enables View Recycling for <View> via ReactViewGroup/ReactViewManager.
*/
export const enableViewRecyclingForView: Getter<boolean> = createNativeFlagGetter('enableViewRecyclingForView', true);
/**
* Enables VirtualView debug features such as logging and overlays.
*/
export const enableVirtualViewDebugFeatures: Getter<boolean> = createNativeFlagGetter('enableVirtualViewDebugFeatures', false);
/**
* Enables reading render state when dispatching VirtualView events.
*/
export const enableVirtualViewRenderState: Getter<boolean> = createNativeFlagGetter('enableVirtualViewRenderState', false);
/**
* Enables window focus detection for prioritizing VirtualView events.
*/
export const enableVirtualViewWindowFocusDetection: Getter<boolean> = createNativeFlagGetter('enableVirtualViewWindowFocusDetection', false);
/**
* Uses the default event priority instead of the discreet event priority by default when dispatching events from Fabric to React.
*/
export const fixMappingOfEventPrioritiesBetweenFabricAndReact: Getter<boolean> = createNativeFlagGetter('fixMappingOfEventPrioritiesBetweenFabricAndReact', false);
/**
* Flag determining if the React Native DevTools (Fusebox) CDP backend should be enabled in release builds. This flag is global and should not be changed across React Host lifetimes.
*/
export const fuseboxEnabledRelease: Getter<boolean> = createNativeFlagGetter('fuseboxEnabledRelease', false);
/**
* Enable network inspection support in the React Native DevTools CDP backend. Requires `enableBridgelessArchitecture`. This flag is global and should not be changed across React Host lifetimes.
*/
export const fuseboxNetworkInspectionEnabled: Getter<boolean> = createNativeFlagGetter('fuseboxNetworkInspectionEnabled', false);
/**
* Hides offscreen VirtualViews on iOS by setting hidden = YES to avoid extra cost of views
*/
export const hideOffscreenVirtualViewsOnIOS: Getter<boolean> = createNativeFlagGetter('hideOffscreenVirtualViewsOnIOS', false);
/**
* Number cached PreparedLayouts in TextLayoutManager cache
*/
export const preparedTextCacheSize: Getter<number> = createNativeFlagGetter('preparedTextCacheSize', 200);
/**
* Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again.
*/
export const preventShadowTreeCommitExhaustion: Getter<boolean> = createNativeFlagGetter('preventShadowTreeCommitExhaustion', false);
/**
* Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause.
*/
export const traceTurboModulePromiseRejectionsOnAndroid: Getter<boolean> = createNativeFlagGetter('traceTurboModulePromiseRejectionsOnAndroid', false);
/**
* When enabled, runtime shadow node references will be updated during the commit. This allows running RSNRU from any thread without corrupting the renderer state.
*/
export const updateRuntimeShadowNodeReferencesOnCommit: Getter<boolean> = createNativeFlagGetter('updateRuntimeShadowNodeReferencesOnCommit', false);
/**
* In Bridgeless mode, use the always available javascript error reporting pipeline.
*/
export const useAlwaysAvailableJSErrorHandling: Getter<boolean> = createNativeFlagGetter('useAlwaysAvailableJSErrorHandling', false);
/**
* Should this application enable the Fabric Interop Layer for Android? If yes, the application will behave so that it can accept non-Fabric components and render them on Fabric. This toggle is controlling extra logic such as custom event dispatching that are needed for the Fabric Interop Layer to work correctly.
*/
export const useFabricInterop: Getter<boolean> = createNativeFlagGetter('useFabricInterop', true);
/**
* Use a native implementation of equals in NativeReadableArray.
*/
export const useNativeEqualsInNativeReadableArrayAndroid: Getter<boolean> = createNativeFlagGetter('useNativeEqualsInNativeReadableArrayAndroid', false);
/**
* Use a native implementation of TransformHelper
*/
export const useNativeTransformHelperAndroid: Getter<boolean> = createNativeFlagGetter('useNativeTransformHelperAndroid', false);
/**
* When enabled, the native view configs are used in bridgeless mode.
*/
export const useNativeViewConfigsInBridgelessMode: Getter<boolean> = createNativeFlagGetter('useNativeViewConfigsInBridgelessMode', false);
/**
* Uses an optimized mechanism for event batching on Android that does not need to wait for a Choreographer frame callback.
*/
export const useOptimizedEventBatchingOnAndroid: Getter<boolean> = createNativeFlagGetter('useOptimizedEventBatchingOnAndroid', false);
/**
* Instead of using folly::dynamic as internal representation in RawProps and RawValue, use jsi::Value
*/
export const useRawPropsJsiValue: Getter<boolean> = createNativeFlagGetter('useRawPropsJsiValue', false);
/**
* Use the state stored on the source shadow node when cloning it instead of reading in the most recent state on the shadow node family.
*/
export const useShadowNodeStateOnClone: Getter<boolean> = createNativeFlagGetter('useShadowNodeStateOnClone', false);
/**
* In Bridgeless mode, should legacy NativeModules use the TurboModule system?
*/
export const useTurboModuleInterop: Getter<boolean> = createNativeFlagGetter('useTurboModuleInterop', false);
/**
* When enabled, NativeModules will be executed by using the TurboModule system
*/
export const useTurboModules: Getter<boolean> = createNativeFlagGetter('useTurboModules', false);
/**
* Initial prerender ratio for VirtualView.
*/
export const virtualViewPrerenderRatio: Getter<number> = createNativeFlagGetter('virtualViewPrerenderRatio', 5);
/**
* Overrides the feature flags with the provided methods.
* NOTE: Only JS-only flags can be overridden from JavaScript using this API.
*/
export const override = setOverrides;
@@ -0,0 +1,117 @@
/**
* 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
*/
import type {
ReactNativeFeatureFlagsJsOnly,
ReactNativeFeatureFlagsJsOnlyOverrides,
} from './ReactNativeFeatureFlags';
import NativeReactNativeFeatureFlags from './specs/NativeReactNativeFeatureFlags';
const accessedFeatureFlags: Set<string> = new Set();
let overrides: ?ReactNativeFeatureFlagsJsOnlyOverrides;
export type Getter<T> = () => T;
// This defines the types for the overrides object, whose methods also receive
// the default value as a parameter.
export type OverridesFor<T> = Partial<{
[key in keyof T]: (ReturnType<T[key]>) => ReturnType<T[key]>,
}>;
function createGetter<T: boolean | number | string>(
configName: string,
customValueGetter: Getter<?T>,
defaultValue: T,
): Getter<T> {
let cachedValue: ?T;
return () => {
if (cachedValue == null) {
cachedValue = customValueGetter() ?? defaultValue;
}
return cachedValue;
};
}
export function createJavaScriptFlagGetter<
K: $Keys<ReactNativeFeatureFlagsJsOnly>,
>(
configName: K,
defaultValue: ReturnType<ReactNativeFeatureFlagsJsOnly[K]>,
): Getter<ReturnType<ReactNativeFeatureFlagsJsOnly[K]>> {
return createGetter(
configName,
() => {
accessedFeatureFlags.add(configName);
return overrides?.[configName]?.(defaultValue);
},
defaultValue,
);
}
type NativeFeatureFlags = $NonMaybeType<typeof NativeReactNativeFeatureFlags>;
export function createNativeFlagGetter<K: $Keys<NativeFeatureFlags>>(
configName: K,
defaultValue: ReturnType<$NonMaybeType<NativeFeatureFlags[K]>>,
skipUnavailableNativeModuleError: boolean = false,
): Getter<ReturnType<$NonMaybeType<NativeFeatureFlags[K]>>> {
return createGetter(
configName,
() => {
maybeLogUnavailableNativeModuleError(configName);
return NativeReactNativeFeatureFlags?.[configName]?.();
},
defaultValue,
);
}
export function getOverrides(): ?ReactNativeFeatureFlagsJsOnlyOverrides {
return overrides;
}
export function setOverrides(
newOverrides: ReactNativeFeatureFlagsJsOnlyOverrides,
): void {
if (overrides != null) {
throw new Error('Feature flags cannot be overridden more than once');
}
if (accessedFeatureFlags.size > 0) {
const accessedFeatureFlagsStr = Array.from(accessedFeatureFlags).join(', ');
throw new Error(
`Feature flags were accessed before being overridden: ${accessedFeatureFlagsStr}`,
);
}
overrides = newOverrides;
}
const reportedConfigNames: Set<string> = new Set();
const hasTurboModules =
global.RN$Bridgeless === true || global.__turboModuleProxy != null;
function maybeLogUnavailableNativeModuleError(configName: string): void {
if (
!NativeReactNativeFeatureFlags &&
// Don't log in tests.
process.env.NODE_ENV !== 'test' &&
// Don't log more than once per config
!reportedConfigNames.has(configName) &&
// Don't log in the legacy architecture.
hasTurboModules
) {
reportedConfigNames.add(configName);
console.error(
`Could not access feature flag '${configName}' because native module method was not available`,
);
}
}
@@ -0,0 +1,95 @@
/**
* 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 SignedSource<<8b4f1275a16d5b83f5594da1eb89c6c1>>
* @flow strict
* @noformat
*/
/**
* IMPORTANT: Do NOT modify this file directly.
*
* To change the definition of the flags, edit
* packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js.
*
* To regenerate this code, run the following script from the repo root:
* yarn featureflags --update
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+commonTestFlag?: () => boolean;
+commonTestFlagWithoutNativeImplementation?: () => boolean;
+animatedShouldSignalBatch?: () => boolean;
+cxxNativeAnimatedEnabled?: () => boolean;
+cxxNativeAnimatedRemoveJsSync?: () => boolean;
+disableMainQueueSyncDispatchIOS?: () => boolean;
+disableMountItemReorderingAndroid?: () => boolean;
+disableTextLayoutManagerCacheAndroid?: () => boolean;
+enableAccessibilityOrder?: () => boolean;
+enableAccumulatedUpdatesInRawPropsAndroid?: () => boolean;
+enableAndroidTextMeasurementOptimizations?: () => boolean;
+enableBridgelessArchitecture?: () => boolean;
+enableCppPropsIteratorSetter?: () => boolean;
+enableCustomFocusSearchOnClippedElementsAndroid?: () => boolean;
+enableDestroyShadowTreeRevisionAsync?: () => boolean;
+enableDoubleMeasurementFixAndroid?: () => boolean;
+enableEagerRootViewAttachment?: () => boolean;
+enableFabricLogs?: () => boolean;
+enableFabricRenderer?: () => boolean;
+enableFixForParentTagDuringReparenting?: () => boolean;
+enableFontScaleChangesUpdatingLayout?: () => boolean;
+enableIOSTextBaselineOffsetPerLine?: () => boolean;
+enableIOSViewClipToPaddingBox?: () => boolean;
+enableInteropViewManagerClassLookUpOptimizationIOS?: () => boolean;
+enableLayoutAnimationsOnAndroid?: () => boolean;
+enableLayoutAnimationsOnIOS?: () => boolean;
+enableMainQueueCoordinatorOnIOS?: () => boolean;
+enableMainQueueModulesOnIOS?: () => boolean;
+enableModuleArgumentNSNullConversionIOS?: () => boolean;
+enableNativeCSSParsing?: () => boolean;
+enableNetworkEventReporting?: () => boolean;
+enableNewBackgroundAndBorderDrawables?: () => boolean;
+enablePreparedTextLayout?: () => boolean;
+enablePropsUpdateReconciliationAndroid?: () => boolean;
+enableResourceTimingAPI?: () => boolean;
+enableSynchronousStateUpdates?: () => boolean;
+enableViewCulling?: () => boolean;
+enableViewRecycling?: () => boolean;
+enableViewRecyclingForText?: () => boolean;
+enableViewRecyclingForView?: () => boolean;
+enableVirtualViewDebugFeatures?: () => boolean;
+enableVirtualViewRenderState?: () => boolean;
+enableVirtualViewWindowFocusDetection?: () => boolean;
+fixMappingOfEventPrioritiesBetweenFabricAndReact?: () => boolean;
+fuseboxEnabledRelease?: () => boolean;
+fuseboxNetworkInspectionEnabled?: () => boolean;
+hideOffscreenVirtualViewsOnIOS?: () => boolean;
+preparedTextCacheSize?: () => number;
+preventShadowTreeCommitExhaustion?: () => boolean;
+traceTurboModulePromiseRejectionsOnAndroid?: () => boolean;
+updateRuntimeShadowNodeReferencesOnCommit?: () => boolean;
+useAlwaysAvailableJSErrorHandling?: () => boolean;
+useFabricInterop?: () => boolean;
+useNativeEqualsInNativeReadableArrayAndroid?: () => boolean;
+useNativeTransformHelperAndroid?: () => boolean;
+useNativeViewConfigsInBridgelessMode?: () => boolean;
+useOptimizedEventBatchingOnAndroid?: () => boolean;
+useRawPropsJsiValue?: () => boolean;
+useShadowNodeStateOnClone?: () => boolean;
+useTurboModuleInterop?: () => boolean;
+useTurboModules?: () => boolean;
+virtualViewPrerenderRatio?: () => number;
}
const NativeReactNativeFeatureFlags: ?Spec = TurboModuleRegistry.get<Spec>(
'NativeReactNativeFeatureFlagsCxx',
);
export default NativeReactNativeFeatureFlags;
@@ -0,0 +1,83 @@
/**
* 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
*/
import type {ExtendedError} from '../../../../Libraries/Core/ExtendedError';
import ExceptionsManager, {
SyntheticError,
} from '../../../../Libraries/Core/ExceptionsManager';
import * as React from 'react';
type ErrorInfo = {
+componentStack?: ?string,
// $FlowFixMe[unclear-type] unknown props and state.
+errorBoundary?: ?React.Component<any, any>,
};
function getExtendedError(
errorValue: mixed,
errorInfo: ErrorInfo,
): ExtendedError {
let error;
// Typically, `errorValue` should be an error. However, other values such as
// strings (or even null) are sometimes thrown.
if (errorValue instanceof Error) {
/* $FlowFixMe[class-object-subtyping] added when improving typing for
* this parameters */
// $FlowFixMe[incompatible-cast]
error = (errorValue: ExtendedError);
} else if (typeof errorValue === 'string') {
/* $FlowFixMe[class-object-subtyping] added when improving typing for
* this parameters */
// $FlowFixMe[incompatible-cast]
error = (new SyntheticError(errorValue): ExtendedError);
} else {
/* $FlowFixMe[class-object-subtyping] added when improving typing for
* this parameters */
// $FlowFixMe[incompatible-cast]
error = (new SyntheticError('Unspecified error'): ExtendedError);
}
try {
// $FlowFixMe[incompatible-use] this is in try/catch.
error.componentStack = errorInfo.componentStack;
error.isComponentError = true;
} catch {
// Ignored.
}
return error;
}
export function onUncaughtError(errorValue: mixed, errorInfo: ErrorInfo): void {
const error = getExtendedError(errorValue, errorInfo);
// Uncaught errors are fatal.
ExceptionsManager.handleException(error, true);
}
export function onCaughtError(errorValue: mixed, errorInfo: ErrorInfo): void {
const error = getExtendedError(errorValue, errorInfo);
// Caught errors are not fatal.
ExceptionsManager.handleException(error, false);
}
export function onRecoverableError(
errorValue: mixed,
errorInfo: ErrorInfo,
): void {
const error = getExtendedError(errorValue, errorInfo);
// Recoverable errors should only be warnings.
// This will make it a soft error in LogBox.
// TODO: improve the logging for recoverable errors in prod.
console.warn(error);
}
+76
View File
@@ -0,0 +1,76 @@
/**
* 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 {polyfillGlobal} from '../../../Libraries/Utilities/PolyfillFunctions';
let initialized = false;
export default function setUpDOM() {
if (initialized) {
return;
}
initialized = true;
polyfillGlobal(
'DOMRect',
() => require('../webapis/geometry/DOMRect').default,
);
polyfillGlobal(
'DOMRectReadOnly',
() => require('../webapis/geometry/DOMRectReadOnly').default,
);
polyfillGlobal(
'DOMRectList',
() => require('../webapis/geometry/DOMRectList').default,
);
polyfillGlobal(
'HTMLCollection',
() => require('../webapis/dom/oldstylecollections/HTMLCollection').default,
);
polyfillGlobal(
'NodeList',
() => require('../webapis/dom/oldstylecollections/NodeList').default,
);
polyfillGlobal(
'Node',
() => require('../webapis/dom/nodes/ReadOnlyNode').default,
);
polyfillGlobal(
'Document',
() => require('../webapis/dom/nodes/ReactNativeDocument').default,
);
polyfillGlobal(
'CharacterData',
() => require('../webapis/dom/nodes/ReadOnlyCharacterData').default,
);
polyfillGlobal(
'Text',
() => require('../webapis/dom/nodes/ReadOnlyText').default,
);
polyfillGlobal(
'Element',
() => require('../webapis/dom/nodes/ReadOnlyElement').default,
);
polyfillGlobal(
'HTMLElement',
() => require('../webapis/dom/nodes/ReactNativeElement').default,
);
}
@@ -0,0 +1,44 @@
/**
* 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
*/
let initialized = false;
export default function setUpDefaltReactNativeEnvironment(
enableDeveloperTools: boolean = true,
) {
if (initialized) {
return;
}
initialized = true;
require('../../../Libraries/Core/setUpGlobals');
require('./setUpDOM').default();
require('../../../Libraries/Core/setUpPerformance');
require('../../../Libraries/Core/polyfillPromise');
require('../../../Libraries/Core/setUpTimers');
if (__DEV__ && enableDeveloperTools) {
require('../../../Libraries/Core/setUpReactDevTools');
}
require('../../../Libraries/Core/setUpErrorHandling');
require('../../../Libraries/Core/setUpRegeneratorRuntime');
require('../../../Libraries/Core/setUpXHR');
require('../../../Libraries/Core/setUpAlert');
require('../../../Libraries/Core/setUpNavigator');
require('../../../Libraries/Core/setUpBatchedBridge');
require('../../../Libraries/Core/setUpSegmentFetcher');
if (__DEV__ && enableDeveloperTools) {
require('../../../Libraries/Core/checkNativeVersion');
require('../../../Libraries/Core/setUpDeveloperTools');
require('../../../Libraries/LogBox/LogBox').default.install();
}
require('../../../Libraries/ReactNative/AppRegistry');
}
@@ -0,0 +1,27 @@
/**
* 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 {polyfillGlobal} from '../../../Libraries/Utilities/PolyfillFunctions';
let initialized = false;
export default function setUpIntersectionObserver() {
if (initialized) {
return;
}
initialized = true;
polyfillGlobal(
'IntersectionObserver',
() =>
require('../webapis/intersectionobserver/IntersectionObserver').default,
);
}
@@ -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-local
* @format
*/
import {polyfillGlobal} from '../../../Libraries/Utilities/PolyfillFunctions';
let initialized = false;
export default function setUpMutationObserver() {
if (initialized) {
return;
}
initialized = true;
polyfillGlobal(
'MutationObserver',
() => require('../webapis/mutationobserver/MutationObserver').default,
);
polyfillGlobal(
'MutationRecord',
() => require('../webapis/mutationobserver/MutationRecord').default,
);
}
@@ -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 strict-local
* @format
*/
import {polyfillGlobal} from '../../../Libraries/Utilities/PolyfillFunctions';
let initialized = false;
export default function setUpPerformanceObserver() {
if (initialized) {
return;
}
initialized = true;
polyfillGlobal(
'PerformanceObserver',
() =>
require('../webapis/performance/PerformanceObserver').PerformanceObserver,
);
polyfillGlobal(
'PerformanceObserverEntryList',
() =>
require('../webapis/performance/PerformanceObserver')
.PerformanceObserverEntryList,
);
polyfillGlobal(
'PerformanceEntry',
() => require('../webapis/performance/PerformanceEntry').PerformanceEntry,
);
polyfillGlobal(
'PerformanceMark',
() => require('../webapis/performance/UserTiming').PerformanceMark,
);
polyfillGlobal(
'PerformanceMeasure',
() => require('../webapis/performance/UserTiming').PerformanceMeasure,
);
polyfillGlobal(
'PerformanceEventTiming',
() => require('../webapis/performance/EventTiming').PerformanceEventTiming,
);
polyfillGlobal(
'PerformanceResourceTiming',
() =>
require('../webapis/performance/ResourceTiming')
.PerformanceResourceTiming,
);
polyfillGlobal(
'TaskAttributionTiming',
() => require('../webapis/performance/LongTasks').TaskAttributionTiming,
);
polyfillGlobal(
'PerformanceLongTaskTiming',
() => require('../webapis/performance/LongTasks').PerformanceLongTaskTiming,
);
}
@@ -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 strict-local
* @format
*/
import type {ViewProps} from '../../../../Libraries/Components/View/ViewPropTypes';
import type {ColorValue} from '../../../../Libraries/StyleSheet/StyleSheet';
import type {WithDefault} from '../../../../Libraries/Types/CodegenTypes';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
type RCTActivityIndicatorViewNativeProps = $ReadOnly<{
...ViewProps,
/**
* Whether the indicator should hide when not animating (true by default).
*
* See https://reactnative.dev/docs/activityindicator#hideswhenstopped-ios
*/
hidesWhenStopped?: WithDefault<boolean, true>,
/**
* Whether to show the indicator (true, the default) or hide it (false).
*
* See https://reactnative.dev/docs/activityindicator#animating
*/
animating?: WithDefault<boolean, true>,
/**
* The foreground color of the spinner (default is gray).
*
* See https://reactnative.dev/docs/activityindicator#color
*/
color?: ?ColorValue,
/**
* Size of the indicator (default is 'small').
* Passing a number to the size prop is only supported on Android.
*
* See https://reactnative.dev/docs/activityindicator#size
*/
size?: WithDefault<'small' | 'large', 'small'>,
}>;
export default (codegenNativeComponent<RCTActivityIndicatorViewNativeProps>(
'ActivityIndicatorView',
{
paperComponentName: 'RCTActivityIndicatorView',
},
): HostComponent<RCTActivityIndicatorViewNativeProps>);
@@ -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.
*
* @flow strict-local
* @format
*/
import type {ViewProps} from '../../../../Libraries/Components/View/ViewPropTypes';
import type {ColorValue} from '../../../../Libraries/StyleSheet/StyleSheet';
import type {
DirectEventHandler,
Float,
Int32,
WithDefault,
} from '../../../../Libraries/Types/CodegenTypes';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeCommands from '../../../../Libraries/Utilities/codegenNativeCommands';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
import * as React from 'react';
type DrawerStateEvent = $ReadOnly<{
drawerState: Int32,
}>;
type DrawerSlideEvent = $ReadOnly<{
offset: Float,
}>;
type AndroidDrawerLayoutNativeProps = $ReadOnly<{
...ViewProps,
/**
* Determines whether the keyboard gets dismissed in response to a drag.
* - 'none' (the default), drags do not dismiss the keyboard.
* - 'on-drag', the keyboard is dismissed when a drag begins.
*/
keyboardDismissMode?: WithDefault<'none' | 'on-drag', 'none'>,
/**
* Specifies the background color of the drawer. The default value is white.
* If you want to set the opacity of the drawer, use rgba. Example:
*
* ```
* return (
* <DrawerLayoutAndroid drawerBackgroundColor="rgba(0,0,0,0.5)">
* </DrawerLayoutAndroid>
* );
* ```
*/
drawerBackgroundColor: ColorValue,
/**
* Specifies the side of the screen from which the drawer will slide in.
*/
drawerPosition?: WithDefault<'left' | 'right', 'left'>,
/**
* Specifies the width of the drawer, more precisely the width of the view that be pulled in
* from the edge of the window.
*/
drawerWidth?: WithDefault<Float, null>,
/**
* Specifies the lock mode of the drawer. The drawer can be locked in 3 states:
* - unlocked (default), meaning that the drawer will respond (open/close) to touch gestures.
* - locked-closed, meaning that the drawer will stay closed and not respond to gestures.
* - locked-open, meaning that the drawer will stay opened and not respond to gestures.
* The drawer may still be opened and closed programmatically (`openDrawer`/`closeDrawer`).
*/
drawerLockMode?: WithDefault<
'unlocked' | 'locked-closed' | 'locked-open',
'unlocked',
>,
/**
* Function called whenever there is an interaction with the navigation view.
*/
onDrawerSlide?: ?DirectEventHandler<DrawerSlideEvent>,
/**
* Function called when the drawer state has changed. The drawer can be in 3 states:
* - Idle, meaning there is no interaction with the navigation view happening at the time
* - Dragging, meaning there is currently an interaction with the navigation view
* - Settling, meaning that there was an interaction with the navigation view, and the
* navigation view is now finishing its closing or opening animation
*/
onDrawerStateChanged?: ?DirectEventHandler<DrawerStateEvent>,
/**
* Function called whenever the navigation view has been opened.
*/
onDrawerOpen?: ?DirectEventHandler<null>,
/**
* Function called whenever the navigation view has been closed.
*/
onDrawerClose?: ?DirectEventHandler<null>,
/**
* Make the drawer take the entire screen and draw the background of the
* status bar to allow it to open over the status bar. It will only have an
* effect on API 21+.
*/
statusBarBackgroundColor?: ?ColorValue,
}>;
type NativeType = HostComponent<AndroidDrawerLayoutNativeProps>;
interface NativeCommands {
+openDrawer: (viewRef: React.ElementRef<NativeType>) => void;
+closeDrawer: (viewRef: React.ElementRef<NativeType>) => void;
}
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
supportedCommands: ['openDrawer', 'closeDrawer'],
});
export default (codegenNativeComponent<AndroidDrawerLayoutNativeProps>(
'AndroidDrawerLayout',
): NativeType);
@@ -0,0 +1,27 @@
/**
* 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 '../../../../Libraries/Components/View/ViewPropTypes';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
type AndroidHorizontalScrollContentViewNativeProps = $ReadOnly<{
...ViewProps,
removeClippedSubviews?: ?boolean,
}>;
type NativeType = HostComponent<AndroidHorizontalScrollContentViewNativeProps>;
export default (codegenNativeComponent<AndroidHorizontalScrollContentViewNativeProps>(
'AndroidHorizontalScrollContentView',
{interfaceOnly: true},
): NativeType);
@@ -0,0 +1,74 @@
/**
* 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 '../../../../Libraries/Components/View/ViewPropTypes';
import type {ColorValue} from '../../../../Libraries/StyleSheet/StyleSheet';
import type {
DirectEventHandler,
Float,
WithDefault,
} from '../../../../Libraries/Types/CodegenTypes';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeCommands from '../../../../Libraries/Utilities/codegenNativeCommands';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
import * as React from 'react';
type AndroidSwipeRefreshLayoutNativeProps = $ReadOnly<{
...ViewProps,
/**
* Whether the pull to refresh functionality is enabled.
*/
enabled?: WithDefault<boolean, true>,
/**
* The colors (at least one) that will be used to draw the refresh indicator.
*/
colors?: ?$ReadOnlyArray<ColorValue>,
/**
* The background color of the refresh indicator.
*/
progressBackgroundColor?: ?ColorValue,
/**
* Size of the refresh indicator.
*/
size?: WithDefault<'default' | 'large', 'default'>,
/**
* Progress view top offset
*/
progressViewOffset?: WithDefault<Float, 0>,
/**
* Called when the view starts refreshing.
*/
onRefresh?: ?DirectEventHandler<null>,
/**
* Whether the view should be indicating an active refresh.
*/
refreshing: boolean,
}>;
type NativeType = HostComponent<AndroidSwipeRefreshLayoutNativeProps>;
interface NativeCommands {
+setNativeRefreshing: (
viewRef: React.ElementRef<NativeType>,
value: boolean,
) => void;
}
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
supportedCommands: ['setNativeRefreshing'],
});
export default (codegenNativeComponent<AndroidSwipeRefreshLayoutNativeProps>(
'AndroidSwipeRefreshLayout',
): NativeType);
@@ -0,0 +1,65 @@
/**
* 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 '../../../../Libraries/Components/View/ViewPropTypes';
import type {ColorValue} from '../../../../Libraries/StyleSheet/StyleSheet';
import type {
BubblingEventHandler,
Int32,
WithDefault,
} from '../../../../Libraries/Types/CodegenTypes';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeCommands from '../../../../Libraries/Utilities/codegenNativeCommands';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
import * as React from 'react';
type AndroidSwitchChangeEvent = $ReadOnly<{
value: boolean,
target: Int32,
}>;
type AndroidSwitchNativeProps = $ReadOnly<{
...ViewProps,
// Props
disabled?: WithDefault<boolean, false>,
enabled?: WithDefault<boolean, true>,
thumbColor?: ?ColorValue,
trackColorForFalse?: ?ColorValue,
trackColorForTrue?: ?ColorValue,
value?: WithDefault<boolean, false>,
on?: WithDefault<boolean, false>,
thumbTintColor?: ?ColorValue,
trackTintColor?: ?ColorValue,
// Events
onChange?: BubblingEventHandler<AndroidSwitchChangeEvent>,
}>;
type NativeType = HostComponent<AndroidSwitchNativeProps>;
interface NativeCommands {
+setNativeValue: (
viewRef: React.ElementRef<NativeType>,
value: boolean,
) => void;
}
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
supportedCommands: ['setNativeValue'],
});
export default (codegenNativeComponent<AndroidSwitchNativeProps>(
'AndroidSwitch',
{
interfaceOnly: true,
},
): NativeType);
@@ -0,0 +1,62 @@
/**
* 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 '../../../../Libraries/Components/View/ViewPropTypes';
import type {ProcessedColorValue} from '../../../../Libraries/StyleSheet/processColor';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeCommands from '../../../../Libraries/Utilities/codegenNativeCommands';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
import * as React from 'react';
type DebuggingOverlayNativeProps = $ReadOnly<{
...ViewProps,
}>;
export type DebuggingOverlayNativeComponentType =
HostComponent<DebuggingOverlayNativeProps>;
export type TraceUpdate = {
id: number,
rectangle: ElementRectangle,
color: ?ProcessedColorValue,
};
export type ElementRectangle = {
x: number,
y: number,
width: number,
height: number,
};
interface NativeCommands {
+highlightTraceUpdates: (
viewRef: React.ElementRef<DebuggingOverlayNativeComponentType>,
updates: $ReadOnlyArray<TraceUpdate>,
) => void;
+highlightElements: (
viewRef: React.ElementRef<DebuggingOverlayNativeComponentType>,
elements: $ReadOnlyArray<ElementRectangle>,
) => void;
+clearElementsHighlights: (
viewRef: React.ElementRef<DebuggingOverlayNativeComponentType>,
) => void;
}
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
supportedCommands: [
'highlightTraceUpdates',
'highlightElements',
'clearElementsHighlights',
],
});
export default (codegenNativeComponent<DebuggingOverlayNativeProps>(
'DebuggingOverlay',
): HostComponent<DebuggingOverlayNativeProps>);
@@ -0,0 +1,39 @@
/**
* 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 '../../../../Libraries/Components/View/ViewPropTypes';
import type {ColorValue} from '../../../../Libraries/StyleSheet/StyleSheet';
import type {
Double,
WithDefault,
} from '../../../../Libraries/Types/CodegenTypes';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
type AndroidProgressBarNativeProps = $ReadOnly<{
...ViewProps,
//Props
styleAttr?: string,
typeAttr?: string,
indeterminate: boolean,
progress?: WithDefault<Double, 0>,
animating?: WithDefault<boolean, true>,
color?: ?ColorValue,
testID?: WithDefault<string, ''>,
}>;
export default (codegenNativeComponent<AndroidProgressBarNativeProps>(
'AndroidProgressBar',
{
interfaceOnly: true,
},
): HostComponent<AndroidProgressBarNativeProps>);
@@ -0,0 +1,74 @@
/**
* 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 '../../../../Libraries/Components/View/ViewPropTypes';
import type {ColorValue} from '../../../../Libraries/StyleSheet/StyleSheet';
import type {
DirectEventHandler,
Float,
WithDefault,
} from '../../../../Libraries/Types/CodegenTypes';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeCommands from '../../../../Libraries/Utilities/codegenNativeCommands';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
import * as React from 'react';
type PullToRefreshNativeProps = $ReadOnly<{
...ViewProps,
/**
* The color of the refresh indicator.
*/
tintColor?: ?ColorValue,
/**
* Title color.
*/
titleColor?: ?ColorValue,
/**
* The title displayed under the refresh indicator.
*/
title?: WithDefault<string, null>,
/**
* Progress view top offset
*/
progressViewOffset?: WithDefault<Float, 0>,
/**
* Called when the view starts refreshing.
*/
onRefresh?: ?DirectEventHandler<null>,
/**
* Whether the view should be indicating an active refresh.
*/
refreshing: boolean,
}>;
type ComponentType = HostComponent<PullToRefreshNativeProps>;
interface NativeCommands {
+setNativeRefreshing: (
viewRef: React.ElementRef<ComponentType>,
refreshing: boolean,
) => void;
}
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
supportedCommands: ['setNativeRefreshing'],
});
export default (codegenNativeComponent<PullToRefreshNativeProps>(
'PullToRefreshView',
{
paperComponentName: 'RCTRefreshControl',
excludedPlatforms: ['android'],
},
): HostComponent<PullToRefreshNativeProps>);
@@ -0,0 +1,29 @@
/**
* 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 '../../../../Libraries/Components/View/ViewPropTypes';
import type {ColorValue} from '../../../../Libraries/StyleSheet/StyleSheet';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
type InputAccessoryNativeProps = $ReadOnly<{
...ViewProps,
backgroundColor?: ?ColorValue,
}>;
export default (codegenNativeComponent<InputAccessoryNativeProps>(
'InputAccessory',
{
interfaceOnly: true,
paperComponentName: 'RCTInputAccessoryView',
excludedPlatforms: ['android'],
},
): HostComponent<InputAccessoryNativeProps>);
@@ -0,0 +1,156 @@
/**
* 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 '../../../../Libraries/Components/View/ViewPropTypes';
import type {
DirectEventHandler,
Int32,
WithDefault,
} from '../../../../Libraries/Types/CodegenTypes';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
type OrientationChangeEvent = $ReadOnly<{
orientation: 'portrait' | 'landscape',
}>;
type RCTModalHostViewNativeProps = $ReadOnly<{
...ViewProps,
/**
* The `animationType` prop controls how the modal animates.
*
* See https://reactnative.dev/docs/modal#animationtype
*/
animationType?: WithDefault<'none' | 'slide' | 'fade', 'none'>,
/**
* The `presentationStyle` prop controls how the modal appears.
*
* See https://reactnative.dev/docs/modal#presentationstyle
*/
presentationStyle?: WithDefault<
'fullScreen' | 'pageSheet' | 'formSheet' | 'overFullScreen',
'fullScreen',
>,
/**
* The `transparent` prop determines whether your modal will fill the
* entire view.
*
* See https://reactnative.dev/docs/modal#transparent
*/
transparent?: WithDefault<boolean, false>,
/**
* The `statusBarTranslucent` prop determines whether your modal should go under
* the system statusbar.
*
* See https://reactnative.dev/docs/modal#statusBarTranslucent
*/
statusBarTranslucent?: WithDefault<boolean, false>,
/**
* The `navigationBarTranslucent` prop determines whether your modal should go under
* the system navigationbar.
*
* See https://reactnative.dev/docs/modal#navigationBarTranslucent
*/
navigationBarTranslucent?: WithDefault<boolean, false>,
/**
* The `hardwareAccelerated` prop controls whether to force hardware
* acceleration for the underlying window.
*
* See https://reactnative.dev/docs/modal#hardwareaccelerated
*/
hardwareAccelerated?: WithDefault<boolean, false>,
/**
* The `onRequestClose` callback is called when the user taps the hardware
* back button on Android or the menu button on Apple TV.
*
* This is required on Apple TV and Android.
*
* See https://reactnative.dev/docs/modal#onrequestclose
*/
onRequestClose?: ?DirectEventHandler<null>,
/**
* The `onShow` prop allows passing a function that will be called once the
* modal has been shown.
*
* See https://reactnative.dev/docs/modal#onshow
*/
onShow?: ?DirectEventHandler<null>,
/**
* The `onDismiss` prop allows passing a function that will be called once
* the modal has been dismissed.
*
* See https://reactnative.dev/docs/modal#ondismiss
*/
onDismiss?: ?DirectEventHandler<null>,
/**
* The `visible` prop determines whether your modal is visible.
*
* See https://reactnative.dev/docs/modal#visible
*/
visible?: WithDefault<boolean, false>,
/**
* Deprecated. Use the `animationType` prop instead.
*/
animated?: WithDefault<boolean, false>,
/**
* Controls whether the modal can be dismissed by swiping down on iOS.
* This requires you to implement the `onRequestClose` prop to handle the dismissal.
*/
allowSwipeDismissal?: WithDefault<boolean, false>,
/**
* The `supportedOrientations` prop allows the modal to be rotated to any of the specified orientations.
*
* See https://reactnative.dev/docs/modal#supportedorientations
*/
supportedOrientations?: WithDefault<
$ReadOnlyArray<
| 'portrait'
| 'portrait-upside-down'
| 'landscape'
| 'landscape-left'
| 'landscape-right',
>,
'portrait',
>,
/**
* The `onOrientationChange` callback is called when the orientation changes while the modal is being displayed.
*
* See https://reactnative.dev/docs/modal#onorientationchange
*/
onOrientationChange?: ?DirectEventHandler<OrientationChangeEvent>,
/**
* The `identifier` is the unique number for identifying Modal components.
*/
identifier?: WithDefault<Int32, 0>,
}>;
export default (codegenNativeComponent<RCTModalHostViewNativeProps>(
'ModalHostView',
{
interfaceOnly: true,
paperComponentName: 'RCTModalHostView',
},
): HostComponent<RCTModalHostViewNativeProps>);
@@ -0,0 +1,28 @@
/**
* 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 '../../../../Libraries/Components/View/ViewPropTypes';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
type RCTSafeAreaViewNativeProps = $ReadOnly<{
...ViewProps,
// No props
}>;
export default (codegenNativeComponent<RCTSafeAreaViewNativeProps>(
'SafeAreaView',
{
paperComponentName: 'RCTSafeAreaView',
interfaceOnly: true,
},
): HostComponent<RCTSafeAreaViewNativeProps>);
@@ -0,0 +1,62 @@
/**
* 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 '../../../../Libraries/Components/View/ViewPropTypes';
import type {ColorValue} from '../../../../Libraries/StyleSheet/StyleSheet';
import type {
BubblingEventHandler,
Int32,
WithDefault,
} from '../../../../Libraries/Types/CodegenTypes';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeCommands from '../../../../Libraries/Utilities/codegenNativeCommands';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
import * as React from 'react';
type NativeSwitchChangeEvent = $ReadOnly<{
value: boolean,
target: Int32,
}>;
type SwitchNativeProps = $ReadOnly<{
...ViewProps,
// Props
disabled?: WithDefault<boolean, false>,
value?: WithDefault<boolean, false>,
tintColor?: ?ColorValue,
onTintColor?: ?ColorValue,
thumbTintColor?: ?ColorValue,
// Deprecated props
thumbColor?: ?ColorValue,
trackColorForFalse?: ?ColorValue,
trackColorForTrue?: ?ColorValue,
// Events
onChange?: ?BubblingEventHandler<NativeSwitchChangeEvent>,
}>;
type ComponentType = HostComponent<SwitchNativeProps>;
interface NativeCommands {
+setValue: (viewRef: React.ElementRef<ComponentType>, value: boolean) => void;
}
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
supportedCommands: ['setValue'],
});
export default (codegenNativeComponent<SwitchNativeProps>('Switch', {
paperComponentName: 'RCTSwitch',
excludedPlatforms: ['android'],
interfaceOnly: true,
}): ComponentType);
@@ -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 {ViewProps} from '../../../../Libraries/Components/View/ViewPropTypes';
import type {WithDefault} from '../../../../Libraries/Types/CodegenTypes';
import type {HostComponent} from '../../types/HostComponent';
import codegenNativeComponent from '../../../../Libraries/Utilities/codegenNativeComponent';
type UnimplementedNativeViewNativeProps = $ReadOnly<{
...ViewProps,
name?: WithDefault<string, ''>,
}>;
// NOTE: This component is not implemented in paper
// Do not require this file in paper builds
export default (codegenNativeComponent<UnimplementedNativeViewNativeProps>(
'UnimplementedNativeView',
): HostComponent<UnimplementedNativeViewNativeProps>);
@@ -0,0 +1,42 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+isReduceMotionEnabled: (
onSuccess: (isReduceMotionEnabled: boolean) => void,
) => void;
+isInvertColorsEnabled?: (
onSuccess: (isInvertColorsEnabled: boolean) => void,
) => void;
+isHighTextContrastEnabled?: (
onSuccess: (isHighTextContrastEnabled: boolean) => void,
) => void;
+isTouchExplorationEnabled: (
onSuccess: (isScreenReaderEnabled: boolean) => void,
) => void;
+isAccessibilityServiceEnabled?: ?(
onSuccess: (isAccessibilityServiceEnabled: boolean) => void,
) => void;
+setAccessibilityFocus: (reactTag: number) => void;
+announceForAccessibility: (announcement: string) => void;
+getRecommendedTimeoutMillis?: (
mSec: number,
onSuccess: (recommendedTimeoutMillis: number) => void,
) => void;
+isGrayscaleEnabled?: (
onSuccess: (isGrayscaleEnabled: boolean) => void,
) => void;
}
export default (TurboModuleRegistry.get<Spec>('AccessibilityInfo'): ?Spec);
@@ -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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+getCurrentBoldTextState: (
onSuccess: (isBoldTextEnabled: boolean) => void,
onError: (error: Object) => void,
) => void;
+getCurrentGrayscaleState: (
onSuccess: (isGrayscaleEnabled: boolean) => void,
onError: (error: Object) => void,
) => void;
+getCurrentInvertColorsState: (
onSuccess: (isInvertColorsEnabled: boolean) => void,
onError: (error: Object) => void,
) => void;
+getCurrentReduceMotionState: (
onSuccess: (isReduceMotionEnabled: boolean) => void,
onError: (error: Object) => void,
) => void;
+getCurrentDarkerSystemColorsState?: (
onSuccess: (isDarkerSystemColorsEnabled: boolean) => void,
onError: (error: Object) => void,
) => void;
+getCurrentPrefersCrossFadeTransitionsState?: (
onSuccess: (prefersCrossFadeTransitions: boolean) => void,
onError: (error: Object) => void,
) => void;
+getCurrentReduceTransparencyState: (
onSuccess: (isReduceTransparencyEnabled: boolean) => void,
onError: (error: Object) => void,
) => void;
+getCurrentVoiceOverState: (
onSuccess: (isScreenReaderEnabled: boolean) => void,
onError: (error: Object) => void,
) => void;
+setAccessibilityContentSizeMultipliers: (JSMultipliers: {
+extraSmall?: ?number,
+small?: ?number,
+medium?: ?number,
+large?: ?number,
+extraLarge?: ?number,
+extraExtraLarge?: ?number,
+extraExtraExtraLarge?: ?number,
+accessibilityMedium?: ?number,
+accessibilityLarge?: ?number,
+accessibilityExtraLarge?: ?number,
+accessibilityExtraExtraLarge?: ?number,
+accessibilityExtraExtraExtraLarge?: ?number,
}) => void;
+setAccessibilityFocus: (reactTag: number) => void;
+announceForAccessibility: (announcement: string) => void;
+announceForAccessibilityWithOptions?: (
announcement: string,
options: {queue?: boolean},
) => void;
}
export default (TurboModuleRegistry.get<Spec>('AccessibilityManager'): ?Spec);
@@ -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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+getConstants: () => {};
+showActionSheetWithOptions: (
options: {
+title?: ?string,
+message?: ?string,
+options: ?Array<string>,
+destructiveButtonIndices?: ?Array<number>,
+cancelButtonIndex?: ?number,
+anchor?: ?number,
+tintColor?: ?number,
+cancelButtonTintColor?: ?number,
+disabledButtonTintColor?: ?number,
+userInterfaceStyle?: ?string,
+disabledButtonIndices?: Array<number>,
},
callback: (buttonIndex: number) => void,
) => void;
+showShareActionSheetWithOptions: (
options: {
+message?: ?string,
+url?: ?string,
+subject?: ?string,
+anchor?: ?number,
+tintColor?: ?number,
+cancelButtonTintColor?: ?number,
+disabledButtonTintColor?: ?number,
+excludedActivityTypes?: ?Array<string>,
+userInterfaceStyle?: ?string,
},
failureCallback: (error: {
+domain: string,
+code: string,
+userInfo?: ?Object,
+message: string,
}) => void,
successCallback: (completed: boolean, activityType: ?string) => void,
) => void;
+dismissActionSheet?: () => void;
}
export default (TurboModuleRegistry.get<Spec>('ActionSheetManager'): ?Spec);
@@ -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
* @format
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export type Args = {
title?: string,
message?: string,
buttons?: Array<Object>, // TODO(T67565166): have a better type
type?: string,
defaultValue?: string,
cancelButtonKey?: string,
destructiveButtonKey?: string,
preferredButtonKey?: string,
keyboardType?: string,
userInterfaceStyle?: string,
};
export interface Spec extends TurboModule {
+alertWithArgs: (
args: Args,
callback: (id: number, value: string) => void,
) => void;
}
export default (TurboModuleRegistry.get<Spec>('AlertManager'): ?Spec);
@@ -0,0 +1,77 @@
/**
* 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 {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import shouldUseTurboAnimatedModule from '../../../../Libraries/Animated/shouldUseTurboAnimatedModule';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
type EndResult = {finished: boolean, value?: number, offset?: number, ...};
type EndCallback = (result: EndResult) => void;
type SaveValueCallback = (value: number) => void;
export type EventMapping = {
nativeEventPath: Array<string>,
animatedValueTag: ?number,
};
// The config has different keys depending on the type of the Node
// TODO(T54896888): Make these types strict
export type AnimatedNodeConfig = Object;
export type AnimatingNodeConfig = Object;
export interface Spec extends TurboModule {
+startOperationBatch: () => void;
+finishOperationBatch: () => void;
+createAnimatedNode: (tag: number, config: AnimatedNodeConfig) => void;
+updateAnimatedNodeConfig?: (tag: number, config: AnimatedNodeConfig) => void;
+getValue: (tag: number, saveValueCallback: SaveValueCallback) => void;
+startListeningToAnimatedNodeValue: (tag: number) => void;
+stopListeningToAnimatedNodeValue: (tag: number) => void;
+connectAnimatedNodes: (parentTag: number, childTag: number) => void;
+disconnectAnimatedNodes: (parentTag: number, childTag: number) => void;
+startAnimatingNode: (
animationId: number,
nodeTag: number,
config: AnimatingNodeConfig,
endCallback: EndCallback,
) => void;
+stopAnimation: (animationId: number) => void;
+setAnimatedNodeValue: (nodeTag: number, value: number) => void;
+setAnimatedNodeOffset: (nodeTag: number, offset: number) => void;
+flattenAnimatedNodeOffset: (nodeTag: number) => void;
+extractAnimatedNodeOffset: (nodeTag: number) => void;
+connectAnimatedNodeToView: (nodeTag: number, viewTag: number) => void;
+disconnectAnimatedNodeFromView: (nodeTag: number, viewTag: number) => void;
+restoreDefaultValues: (nodeTag: number) => void;
+dropAnimatedNode: (tag: number) => void;
+addAnimatedEventToView: (
viewTag: number,
eventName: string,
eventMapping: EventMapping,
) => void;
+removeAnimatedEventFromView: (
viewTag: number,
eventName: string,
animatedNodeTag: number,
) => void;
// Events
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
// All of the above in a batched mode
+queueAndExecuteBatchedOperations?: (operationsAndArgs: Array<any>) => void;
}
const NativeModule: ?Spec = !shouldUseTurboAnimatedModule()
? TurboModuleRegistry.get<Spec>('NativeAnimatedModule')
: null;
export default NativeModule;
@@ -0,0 +1,78 @@
/**
* 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 {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import shouldUseTurboAnimatedModule from '../../../../Libraries/Animated/shouldUseTurboAnimatedModule';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
type EndResult = {finished: boolean, value?: number, offset?: number, ...};
type EndCallback = (result: EndResult) => void;
type SaveValueCallback = (value: number) => void;
export type EventMapping = {
nativeEventPath: Array<string>,
animatedValueTag: ?number,
};
// The config has different keys depending on the type of the Node
// TODO(T54896888): Make these types strict
export type AnimatedNodeConfig = Object;
export type AnimatingNodeConfig = Object;
export interface Spec extends TurboModule {
+startOperationBatch: () => void;
+finishOperationBatch: () => void;
+createAnimatedNode: (tag: number, config: AnimatedNodeConfig) => void;
+updateAnimatedNodeConfig?: (tag: number, config: AnimatedNodeConfig) => void;
+getValue: (tag: number, saveValueCallback: SaveValueCallback) => void;
+startListeningToAnimatedNodeValue: (tag: number) => void;
+stopListeningToAnimatedNodeValue: (tag: number) => void;
+connectAnimatedNodes: (parentTag: number, childTag: number) => void;
+disconnectAnimatedNodes: (parentTag: number, childTag: number) => void;
+startAnimatingNode: (
animationId: number,
nodeTag: number,
config: AnimatingNodeConfig,
endCallback: EndCallback,
) => void;
+stopAnimation: (animationId: number) => void;
+setAnimatedNodeValue: (nodeTag: number, value: number) => void;
+setAnimatedNodeOffset: (nodeTag: number, offset: number) => void;
+flattenAnimatedNodeOffset: (nodeTag: number) => void;
+extractAnimatedNodeOffset: (nodeTag: number) => void;
+connectAnimatedNodeToView: (nodeTag: number, viewTag: number) => void;
+disconnectAnimatedNodeFromView: (nodeTag: number, viewTag: number) => void;
+restoreDefaultValues: (nodeTag: number) => void;
+dropAnimatedNode: (tag: number) => void;
+addAnimatedEventToView: (
viewTag: number,
eventName: string,
eventMapping: EventMapping,
) => void;
+removeAnimatedEventFromView: (
viewTag: number,
eventName: string,
animatedNodeTag: number,
) => void;
// Events
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
// All of the above in a batched mode
+queueAndExecuteBatchedOperations?: (operationsAndArgs: Array<any>) => void;
}
const NativeModule: ?Spec = shouldUseTurboAnimatedModule()
? TurboModuleRegistry.get<Spec>('NativeAnimatedTurboModule')
: null;
export default NativeModule;
@@ -0,0 +1,33 @@
/**
* 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 {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export type AppStateConstants = {
initialAppState: string,
};
export type AppState = {app_state: string};
export interface Spec extends TurboModule {
+getConstants: () => AppStateConstants;
+getCurrentAppState: (
success: (appState: AppState) => void,
error: (error: Object) => void,
) => void;
// Events
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
}
export default (TurboModuleRegistry.getEnforcing<Spec>('AppState'): Spec);
@@ -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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export type ColorSchemeName = 'light' | 'dark' | 'unspecified';
export type AppearancePreferences = {
colorScheme?: ?ColorSchemeName,
};
export interface Spec extends TurboModule {
+getColorScheme: () => ?ColorSchemeName;
+setColorScheme: (colorScheme: ColorSchemeName) => void;
// RCTEventEmitter
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
}
export default (TurboModuleRegistry.get<Spec>('Appearance'): ?Spec);
@@ -0,0 +1,61 @@
/**
* 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 {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export type Constants = {BLOB_URI_SCHEME: ?string, BLOB_URI_HOST: ?string};
export interface Spec extends TurboModule {
+getConstants: () => Constants;
+addNetworkingHandler: () => void;
+addWebSocketHandler: (id: number) => void;
+removeWebSocketHandler: (id: number) => void;
+sendOverSocket: (blob: Object, socketID: number) => void;
+createFromParts: (parts: Array<Object>, withId: string) => void;
+release: (blobId: string) => void;
}
const NativeModule = TurboModuleRegistry.get<Spec>('BlobModule');
let constants = null;
let NativeBlobModule = null;
if (NativeModule != null) {
NativeBlobModule = {
getConstants(): Constants {
if (constants == null) {
constants = NativeModule.getConstants();
}
return constants;
},
addNetworkingHandler(): void {
NativeModule.addNetworkingHandler();
},
addWebSocketHandler(id: number): void {
NativeModule.addWebSocketHandler(id);
},
removeWebSocketHandler(id: number): void {
NativeModule.removeWebSocketHandler(id);
},
sendOverSocket(blob: Object, socketID: number): void {
NativeModule.sendOverSocket(blob, socketID);
},
createFromParts(parts: Array<Object>, withId: string): void {
NativeModule.createFromParts(parts, withId);
},
release(blobId: string): void {
NativeModule.release(blobId);
},
};
}
export default (NativeBlobModule: ?Spec);
@@ -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.
*
* @flow strict
* @format
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+getConstants: () => {};
+getString: () => Promise<string>;
+setString: (content: string) => void;
}
export default (TurboModuleRegistry.getEnforcing<Spec>('Clipboard'): Spec);
@@ -0,0 +1,24 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+showMessage: (
message: string,
withColor: ?number,
withBackgroundColor: ?number,
) => void;
+hide: () => void;
}
export default (TurboModuleRegistry.get<Spec>('DevLoadingView'): ?Spec);
@@ -0,0 +1,33 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+reload: () => void;
+reloadWithReason?: (reason: string) => void;
+onFastRefresh?: () => void;
+setHotLoadingEnabled: (isHotLoadingEnabled: boolean) => void;
+setProfilingEnabled: (isProfilingEnabled: boolean) => void;
+toggleElementInspector: () => void;
+addMenuItem: (title: string) => void;
+openDebugger?: () => void;
// Events
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
// iOS only.
+setIsShakeToShowDevMenuEnabled: (enabled: boolean) => void;
}
export default (TurboModuleRegistry.getEnforcing<Spec>('DevSettings'): Spec);
@@ -0,0 +1,19 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+invokeDefaultBackPressHandler: () => void;
}
export default (TurboModuleRegistry.get<Spec>('DeviceEventManager'): ?Spec);
@@ -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.
*
* @flow strict
* @format
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export type DisplayMetricsAndroid = {
width: number,
height: number,
scale: number,
fontScale: number,
densityDpi: number,
};
export type DisplayMetrics = {
width: number,
height: number,
scale: number,
fontScale: number,
};
export type DimensionsPayload = {
window?: DisplayMetrics,
screen?: DisplayMetrics,
windowPhysicalPixels?: DisplayMetricsAndroid,
screenPhysicalPixels?: DisplayMetricsAndroid,
};
export type DeviceInfoConstants = {
+Dimensions: DimensionsPayload,
+isEdgeToEdge?: boolean,
+isIPhoneX_deprecated?: boolean,
};
export interface Spec extends TurboModule {
+getConstants: () => DeviceInfoConstants;
}
const NativeModule: Spec = TurboModuleRegistry.getEnforcing<Spec>('DeviceInfo');
let constants: ?DeviceInfoConstants = null;
const NativeDeviceInfo = {
getConstants(): DeviceInfoConstants {
if (constants == null) {
constants = NativeModule.getConstants();
}
return constants;
},
};
export default NativeDeviceInfo;
@@ -0,0 +1,48 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
/* 'buttonClicked' | 'dismissed' */
type DialogAction = string;
/*
buttonPositive = -1,
buttonNegative = -2,
buttonNeutral = -3
*/
type DialogButtonKey = number;
export type DialogOptions = {
title?: string,
message?: string,
buttonPositive?: string,
buttonNegative?: string,
buttonNeutral?: string,
items?: Array<string>,
cancelable?: boolean,
};
export interface Spec extends TurboModule {
+getConstants: () => {
+buttonClicked: DialogAction,
+dismissed: DialogAction,
+buttonPositive: DialogButtonKey,
+buttonNegative: DialogButtonKey,
+buttonNeutral: DialogButtonKey,
};
+showAlert: (
config: DialogOptions,
onError: (error: string) => void,
onAction: (action: DialogAction, buttonKey?: DialogButtonKey) => void,
) => void;
}
export default (TurboModuleRegistry.get<Spec>('DialogManagerAndroid'): ?Spec);
@@ -0,0 +1,91 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
const Platform = require('../../../../Libraries/Utilities/Platform').default;
export type StackFrame = {
column: ?number,
file: ?string,
lineNumber: ?number,
methodName: string,
collapse?: boolean,
};
export type ExceptionData = {
message: string,
originalMessage: ?string,
name: ?string,
componentStack: ?string,
stack: Array<StackFrame>,
id: number,
isFatal: boolean,
// flowlint-next-line unclear-type:off
extraData?: Object,
...
};
export interface Spec extends TurboModule {
// Deprecated: Use `reportException`
+reportFatalException: (
message: string,
stack: Array<StackFrame>,
exceptionId: number,
) => void;
// Deprecated: Use `reportException`
+reportSoftException: (
message: string,
stack: Array<StackFrame>,
exceptionId: number,
) => void;
+reportException?: (data: ExceptionData) => void;
// TODO(T53311281): This is a noop on iOS now. Implement it.
+dismissRedbox?: () => void;
}
const NativeModule =
TurboModuleRegistry.getEnforcing<Spec>('ExceptionsManager');
const ExceptionsManager = {
reportFatalException(
message: string,
stack: Array<StackFrame>,
exceptionId: number,
) {
NativeModule.reportFatalException(message, stack, exceptionId);
},
reportSoftException(
message: string,
stack: Array<StackFrame>,
exceptionId: number,
) {
NativeModule.reportSoftException(message, stack, exceptionId);
},
dismissRedbox(): void {
if (Platform.OS !== 'ios' && NativeModule.dismissRedbox) {
// TODO(T53311281): This is a noop on iOS now. Implement it.
NativeModule.dismissRedbox();
}
},
reportException(data: ExceptionData): void {
if (NativeModule.reportException) {
NativeModule.reportException(data);
return;
}
if (data.isFatal) {
ExceptionsManager.reportFatalException(data.message, data.stack, data.id);
} else {
ExceptionsManager.reportSoftException(data.message, data.stack, data.id);
}
},
};
export default ExceptionsManager;
@@ -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
* @format
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+readAsDataURL: (data: Object) => Promise<string>;
+readAsText: (data: Object, encoding: string) => Promise<string>;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
'FileReaderModule',
): Spec);
@@ -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
* @format
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+setGlobalOptions: (options: {+debug?: ?boolean}) => void;
+setContext: (context: string) => void;
+beginScroll: () => void;
+endScroll: () => void;
}
export default (TurboModuleRegistry.get<Spec>('FrameRateLogger'): ?Spec);
@@ -0,0 +1,20 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+notifyTaskFinished: (taskId: number) => void;
+notifyTaskRetry: (taskId: number) => Promise<boolean>;
}
export default (TurboModuleRegistry.get<Spec>('HeadlessJsTaskSupport'): ?Spec);
@@ -0,0 +1,28 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export type I18nManagerConstants = {
doLeftAndRightSwapInRTL: boolean,
isRTL: boolean,
localeIdentifier?: ?string,
};
export interface Spec extends TurboModule {
+getConstants: () => I18nManagerConstants;
allowRTL: (allowRTL: boolean) => void;
forceRTL: (forceRTL: boolean) => void;
swapLeftAndRightInRTL: (flipStyles: boolean) => void;
}
export default (TurboModuleRegistry.get<Spec>('I18nManager'): ?Spec);
@@ -0,0 +1,52 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
type Options = {
+offset: {
+x: number,
+y: number,
},
+size: {
+width: number,
+height: number,
},
+displaySize?: ?{
+width: number,
+height: number,
},
/**
* Enum with potential values:
* - cover
* - contain
* - stretch
* - center
* - repeat
*/
+resizeMode?: ?string,
+allowExternalStorage?: boolean,
};
export interface Spec extends TurboModule {
+getConstants: () => {};
+cropImage: (
uri: string,
cropData: Options,
successCallback: (uri: string) => void,
errorCallback: (error: string) => void,
) => void;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
'ImageEditingManager',
): Spec);
@@ -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
* @format
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export type ImageSize = {
width: number,
height: number,
...
};
export interface Spec extends TurboModule {
+abortRequest: (requestId: number) => void;
+getConstants: () => {};
+getSize: (uri: string) => Promise<ImageSize>;
+getSizeWithHeaders: (uri: string, headers: Object) => Promise<ImageSize>;
+prefetchImage: (uri: string, requestId: number) => Promise<boolean>;
+queryCache: (uris: Array<string>) => Promise<Object>;
}
export default (TurboModuleRegistry.getEnforcing<Spec>('ImageLoader'): Spec);
@@ -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
* @format
*/
import type {RootTag} from '../../../../Libraries/TurboModule/RCTExport';
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+getConstants: () => {};
// Return [width, height] of image uri
+getSize: (uri: string) => Promise<$ReadOnlyArray<number>>;
+getSizeWithHeaders: (
uri: string,
headers: Object,
) => Promise<{
width: number,
height: number,
...
}>;
+prefetchImage: (uri: string) => Promise<boolean>;
+prefetchImageWithMetadata?: (
uri: string,
queryRootName: string,
rootTag: RootTag,
) => Promise<boolean>;
+queryCache: (uris: Array<string>) => Promise<Object>;
}
export default (TurboModuleRegistry.getEnforcing<Spec>('ImageLoader'): Spec);
@@ -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
* @format
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+getConstants: () => {};
+getBase64ForTag: (
uri: string,
successCallback: (base64ImageData: string) => void,
errorCallback: (error: string) => void,
) => void;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
'ImageStoreManager',
): Spec);
@@ -0,0 +1,33 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+getConstants: () => {};
+getBase64ForTag: (
uri: string,
successCallback: (base64ImageData: string) => void,
errorCallback: (error: {message: string}) => void,
) => void;
+hasImageForTag: (uri: string, callback: (hasImage: boolean) => void) => void;
+removeImageForTag: (uri: string) => void;
+addImageFromBase64: (
base64ImageData: string,
successCallback: (uri: string) => void,
errorCallback: (error: {message: string}) => void,
) => void;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
'ImageStoreManager',
): Spec);
@@ -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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+getInitialURL: () => Promise<string>;
+canOpenURL: (url: string) => Promise<boolean>;
+openURL: (url: string) => Promise<void>;
+openSettings: () => Promise<void>;
+sendIntent: (
action: string,
extras: ?Array<{
key: string,
value: string | number | boolean, // TODO(T67672788): Union types are not type safe
...
}>,
) => Promise<void>;
}
export default (TurboModuleRegistry.get<Spec>('IntentAndroid'): ?Spec);
@@ -0,0 +1,19 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+captureComplete: (path: string, error: ?string) => void;
}
export default (TurboModuleRegistry.get<Spec>('JSCHeapCapture'): ?Spec);
@@ -0,0 +1,20 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
}
export default (TurboModuleRegistry.get<Spec>('KeyboardObserver'): ?Spec);
@@ -0,0 +1,27 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
// Common interface
+getInitialURL: () => Promise<?string>;
+canOpenURL: (url: string) => Promise<boolean>;
+openURL: (url: string) => Promise<void>;
+openSettings: () => Promise<void>;
// Events
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
}
export default (TurboModuleRegistry.get<Spec>('LinkingManager'): ?Spec);
@@ -0,0 +1,20 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+show: () => void;
+hide: () => void;
}
export default (TurboModuleRegistry.get<Spec>('LogBox'): ?Spec);
@@ -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.
*
* @flow strict
* @format
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
// RCTEventEmitter
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
}
export default (TurboModuleRegistry.get<Spec>('ModalManager'): ?Spec);
@@ -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
* @format
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
type Header = [string, string];
export interface Spec extends TurboModule {
+sendRequest: (
method: string,
url: string,
requestId: number,
headers: Array<Header>,
data: Object,
responseType: string,
useIncrementalUpdates: boolean,
timeout: number,
withCredentials: boolean,
) => void;
+abortRequest: (requestId: number) => void;
+clearCookies: (callback: (result: boolean) => void) => void;
// RCTEventEmitter
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
}
export default (TurboModuleRegistry.getEnforcing<Spec>('Networking'): Spec);
@@ -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
* @format
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+sendRequest: (
query: {
method: string,
url: string,
data: Object,
headers: Object,
responseType: string,
incrementalUpdates: boolean,
timeout: number,
withCredentials: boolean,
},
callback: (requestId: number) => void,
) => void;
+abortRequest: (requestId: number) => void;
+clearCookies: (callback: (result: boolean) => void) => void;
// RCTEventEmitter
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
}
export default (TurboModuleRegistry.getEnforcing<Spec>('Networking'): Spec);
@@ -0,0 +1,78 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
// TODO: Use proper enum types.
export type PermissionStatus = string;
export type PermissionType = string;
/*
export type PermissionStatus = 'granted' | 'denied' | 'never_ask_again';
export type PermissionType =
| 'android.permission.READ_CALENDAR'
| 'android.permission.WRITE_CALENDAR'
| 'android.permission.CAMERA'
| 'android.permission.READ_CONTACTS'
| 'android.permission.WRITE_CONTACTS'
| 'android.permission.GET_ACCOUNTS'
| 'android.permission.ACCESS_BACKGROUND_LOCATION'
| 'android.permission.ACCESS_FINE_LOCATION'
| 'android.permission.ACCESS_COARSE_LOCATION'
| 'android.permission.RECORD_AUDIO'
| 'android.permission.READ_PHONE_STATE'
| 'android.permission.CALL_PHONE'
| 'android.permission.READ_CALL_LOG'
| 'android.permission.WRITE_CALL_LOG'
| 'com.android.voicemail.permission.ADD_VOICEMAIL'
| 'com.android.voicemail.permission.READ_VOICEMAIL'
| 'com.android.voicemail.permission.WRITE_VOICEMAIL'
| 'android.permission.USE_SIP'
| 'android.permission.PROCESS_OUTGOING_CALLS'
| 'android.permission.BODY_SENSORS'
| 'android.permission.BODY_SENSORS_BACKGROUND'
| 'android.permission.SEND_SMS'
| 'android.permission.RECEIVE_SMS'
| 'android.permission.READ_SMS'
| 'android.permission.RECEIVE_WAP_PUSH'
| 'android.permission.RECEIVE_MMS'
| 'android.permission.READ_EXTERNAL_STORAGE'
| 'android.permission.READ_MEDIA_IMAGES'
| 'android.permission.READ_MEDIA_VIDEO'
| 'android.permission.READ_MEDIA_AUDIO'
| 'android.permission.READ_MEDIA_VISUAL_USER_SELECTED'
| 'android.permission.WRITE_EXTERNAL_STORAGE'
| 'android.permission.BLUETOOTH_CONNECT'
| 'android.permission.BLUETOOTH_SCAN'
| 'android.permission.BLUETOOTH_ADVERTISE'
| 'android.permission.ACCESS_MEDIA_LOCATION'
| 'android.permission.ACCEPT_HANDOVER'
| 'android.permission.ACTIVITY_RECOGNITION'
| 'android.permission.ANSWER_PHONE_CALLS'
| 'android.permission.READ_PHONE_NUMBERS'
| 'android.permission.UWB_RANGING'
| 'android.permission.POST_NOTIFICATIONS'
| 'android.permission.NEARBY_WIFI_DEVICES';
*/
export interface Spec extends TurboModule {
+checkPermission: (permission: PermissionType) => Promise<boolean>;
+requestPermission: (permission: PermissionType) => Promise<PermissionStatus>;
+shouldShowRequestPermissionRationale: (
permission: string,
) => Promise<boolean>;
+requestMultiplePermissions: (
permissions: Array<PermissionType>,
) => Promise<{[permission: PermissionType]: PermissionStatus, ...}>;
}
export default (TurboModuleRegistry.get<Spec>('PermissionsAndroid'): ?Spec);
@@ -0,0 +1,44 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export type ReactNativeVersionAndroid = {
major: number,
minor: number,
patch: number,
prerelease: ?string,
};
export type PlatformConstantsAndroid = {
isTesting: boolean,
isDisableAnimations?: boolean,
reactNativeVersion: ReactNativeVersionAndroid,
Version: number,
Release: string,
Serial: string,
Fingerprint: string,
Model: string,
ServerHost?: string,
uiMode: string,
Brand: string,
Manufacturer: string,
};
export interface Spec extends TurboModule {
+getConstants: () => PlatformConstantsAndroid;
+getAndroidID: () => string;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
'PlatformConstants',
): Spec);
@@ -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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export type PlatformConstantsIOS = {
isTesting: boolean,
isDisableAnimations?: boolean,
reactNativeVersion: {
major: number,
minor: number,
patch: number,
prerelease: ?string,
},
forceTouchAvailable: boolean,
osVersion: string,
systemName: string,
interfaceIdiom: string,
isMacCatalyst?: boolean,
};
export interface Spec extends TurboModule {
+getConstants: () => PlatformConstantsIOS;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
'PlatformConstants',
): Spec);
@@ -0,0 +1,103 @@
/**
* 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 {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
type Permissions = {
alert: boolean,
badge: boolean,
sound: boolean,
};
type Notification = {
+alertTitle?: ?string,
+alertBody?: ?string,
+userInfo?: ?Object,
/**
* Identifier for the notification category. See the [Apple documentation](https://developer.apple.com/documentation/usernotifications/declaring_your_actionable_notification_types)
* for more details.
*/
+category?: ?string,
/**
* Actual type: string | number
*
* Schedule notifications using EITHER `fireDate` or `fireIntervalSeconds`.
* If both are specified, `fireDate` takes precedence.
* If you use `presentLocalNotification`, both will be ignored
* and the notification will be shown immediately.
*/
+fireDate?: ?number,
/**
* Seconds from now to display the notification.
*
* Schedule notifications using EITHER `fireDate` or `fireIntervalSeconds`.
* If both are specified, `fireDate` takes precedence.
* If you use `presentLocalNotification`, both will be ignored
* and the notification will be shown immediately.
*/
+fireIntervalSeconds?: ?number,
/** Badge count to display on the app icon. */
+applicationIconBadgeNumber?: ?number,
/** Whether to silence the notification sound. */
+isSilent?: ?boolean,
/**
* Custom notification sound. Can only be set when creating notifications.
* This will be null for notifications retrieved via
* getScheduledLocalNotifications or getDeliveredNotifications.
*/
+soundName?: ?string,
};
export interface Spec extends TurboModule {
+getConstants: () => {};
+onFinishRemoteNotification: (
notificationId: string,
/**
* Type:
* 'UIBackgroundFetchResultNewData' |
* 'UIBackgroundFetchResultNoData' |
* 'UIBackgroundFetchResultFailed'
*/
fetchResult: string,
) => void;
+setApplicationIconBadgeNumber: (num: number) => void;
+getApplicationIconBadgeNumber: (callback: (num: number) => void) => void;
+requestPermissions: (permission: {
+alert: boolean,
+badge: boolean,
+sound: boolean,
}) => Promise<Permissions>;
+abandonPermissions: () => void;
+checkPermissions: (callback: (permissions: Permissions) => void) => void;
+presentLocalNotification: (notification: Notification) => void;
+scheduleLocalNotification: (notification: Notification) => void;
+cancelAllLocalNotifications: () => void;
+cancelLocalNotifications: (userInfo: Object) => void;
+getInitialNotification: () => Promise<?Notification>;
+getScheduledLocalNotifications: (
callback: (notification: Notification) => void,
) => void;
+removeAllDeliveredNotifications: () => void;
+removeDeliveredNotifications: (identifiers: Array<string>) => void;
+getDeliveredNotifications: (
callback: (notification: Array<Notification>) => void,
) => void;
+getAuthorizationStatus: (
callback: (authorizationStatus: number) => void,
) => void;
+addListener: (eventType: string) => void;
+removeListeners: (count: number) => void;
}
export default (TurboModuleRegistry.get<Spec>(
'PushNotificationManager',
): ?Spec);
@@ -0,0 +1,20 @@
/**
* 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 {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+setExtraData: (extraData: Object, forIdentifier: string) => void;
+dismiss: () => void;
}
export default (TurboModuleRegistry.get<Spec>('RedBox'): ?Spec);
@@ -0,0 +1,69 @@
/**
* 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 {
RootTag,
TurboModule,
} from '../../../../Libraries/TurboModule/RCTExport';
import type {
EventEmitter,
UnsafeObject,
} from '../../../../Libraries/Types/CodegenTypes';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export enum EnumInt {
A = 23,
B = 42,
}
export type ObjectStruct = {
a: number,
b: string,
c?: ?string,
};
export interface Spec extends TurboModule {
+onPress: EventEmitter<void>;
+onClick: EventEmitter<string>;
+onChange: EventEmitter<ObjectStruct>;
+onSubmit: EventEmitter<ObjectStruct[]>;
// Exported methods.
+getConstants: () => {
const1: boolean,
const2: number,
const3: string,
};
+voidFunc: () => void;
+getBool: (arg: boolean) => boolean;
+getEnum?: (arg: EnumInt) => EnumInt;
+getNumber: (arg: number) => number;
+getString: (arg: string) => string;
+getArray: (arg: Array<any>) => Array<any>;
+getObject: (arg: Object) => Object;
+getUnsafeObject: (arg: UnsafeObject) => UnsafeObject;
+getRootTag: (arg: RootTag) => RootTag;
+getValue: (x: number, y: string, z: Object) => Object;
+getValueWithCallback: (callback: (value: string) => void) => void;
+getValueWithPromise: (error: boolean) => Promise<string>;
+voidFuncThrows?: () => void;
+getObjectThrows?: (arg: Object) => Object;
+promiseThrows?: () => Promise<void>;
+voidFuncAssert?: () => void;
+getObjectAssert?: (arg: Object) => Object;
+promiseAssert?: () => Promise<void>;
// Android-only
+getImageUrl?: () => Promise<string | null>;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
'SampleTurboModule',
): Spec);
@@ -0,0 +1,28 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+fetchSegment: (
segmentId: number,
options: Object, // flowlint-line unclear-type: off
callback: (error: ?Object) => void, // flowlint-line unclear-type: off
) => void;
+getSegment?: (
segmentId: number,
options: Object, // flowlint-line unclear-type: off
callback: (error: ?Object, path: ?string) => void, // flowlint-line unclear-type: off
) => void;
}
export default (TurboModuleRegistry.getEnforcing<Spec>('SegmentFetcher'): Spec);
@@ -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.
*
* @flow
* @format
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+getConstants: () => {
settings: Object,
};
+setValues: (values: Object) => void;
+deleteValues: (values: Array<string>) => void;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
'SettingsManager',
): Spec);
@@ -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
* @format
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+getConstants: () => {};
+share: (
content: {title?: string, message?: string},
dialogTitle?: string,
) => Promise<{action: string}>;
}
export default (TurboModuleRegistry.get<Spec>('ShareModule'): ?Spec);
@@ -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
* @format
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
/**
* Native Module used for playing sounds in native platform.
*/
export interface Spec extends TurboModule {
+playTouchSound: () => void;
}
export default (TurboModuleRegistry.get<Spec>('SoundManager'): ?Spec);
@@ -0,0 +1,36 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export type SourceCodeConstants = {
scriptURL: string,
};
export interface Spec extends TurboModule {
+getConstants: () => SourceCodeConstants;
}
const NativeModule = TurboModuleRegistry.getEnforcing<Spec>('SourceCode');
let constants = null;
const NativeSourceCode = {
getConstants(): SourceCodeConstants {
if (constants == null) {
constants = NativeModule.getConstants();
}
return constants;
},
};
export default NativeSourceCode;
@@ -0,0 +1,68 @@
/**
* 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
*/
import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';
import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+getConstants: () => {
+HEIGHT: number,
+DEFAULT_BACKGROUND_COLOR: number,
};
+setColor: (color: number, animated: boolean) => void;
+setTranslucent: (translucent: boolean) => void;
/**
* - statusBarStyles can be:
* - 'default'
* - 'dark-content'
*/
+setStyle: (statusBarStyle?: ?string) => void;
+setHidden: (hidden: boolean) => void;
}
const NativeModule = TurboModuleRegistry.getEnforcing<Spec>('StatusBarManager');
let constants = null;
const NativeStatusBarManager = {
getConstants(): {
+HEIGHT: number,
+DEFAULT_BACKGROUND_COLOR?: number,
} {
if (constants == null) {
constants = NativeModule.getConstants();
}
return constants;
},
setColor(color: number, animated: boolean): void {
NativeModule.setColor(color, animated);
},
setTranslucent(translucent: boolean): void {
NativeModule.setTranslucent(translucent);
},
/**
* - statusBarStyles can be:
* - 'default'
* - 'dark-content'
*/
setStyle(statusBarStyle?: ?string): void {
NativeModule.setStyle(statusBarStyle);
},
setHidden(hidden: boolean): void {
NativeModule.setHidden(hidden);
},
};
export default NativeStatusBarManager;

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