first commit

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

View File

@@ -0,0 +1,445 @@
/**
* 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 {PartialViewConfigWithoutName} from './PlatformBaseViewConfig';
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
import ReactNativeStyleAttributes from '../Components/View/ReactNativeStyleAttributes';
import {DynamicallyInjectedByGestureHandler} from './ViewConfigIgnore';
const bubblingEventTypes = {
// Bubbling events from UIManagerModuleConstants.java
topChange: {
phasedRegistrationNames: {
captured: 'onChangeCapture',
bubbled: 'onChange',
},
},
topSelect: {
phasedRegistrationNames: {
captured: 'onSelectCapture',
bubbled: 'onSelect',
},
},
topTouchEnd: {
phasedRegistrationNames: {
captured: 'onTouchEndCapture',
bubbled: 'onTouchEnd',
},
},
topTouchCancel: {
phasedRegistrationNames: {
captured: 'onTouchCancelCapture',
bubbled: 'onTouchCancel',
},
},
topTouchStart: {
phasedRegistrationNames: {
captured: 'onTouchStartCapture',
bubbled: 'onTouchStart',
},
},
topTouchMove: {
phasedRegistrationNames: {
captured: 'onTouchMoveCapture',
bubbled: 'onTouchMove',
},
},
// Experimental/Work in Progress Pointer Events (not yet ready for use)
topPointerCancel: {
phasedRegistrationNames: {
captured: 'onPointerCancelCapture',
bubbled: 'onPointerCancel',
},
},
topPointerDown: {
phasedRegistrationNames: {
captured: 'onPointerDownCapture',
bubbled: 'onPointerDown',
},
},
topPointerEnter: {
phasedRegistrationNames: {
captured: 'onPointerEnterCapture',
bubbled: 'onPointerEnter',
skipBubbling: true,
},
},
topPointerLeave: {
phasedRegistrationNames: {
captured: 'onPointerLeaveCapture',
bubbled: 'onPointerLeave',
skipBubbling: true,
},
},
topPointerMove: {
phasedRegistrationNames: {
captured: 'onPointerMoveCapture',
bubbled: 'onPointerMove',
},
},
topPointerUp: {
phasedRegistrationNames: {
captured: 'onPointerUpCapture',
bubbled: 'onPointerUp',
},
},
topPointerOut: {
phasedRegistrationNames: {
captured: 'onPointerOutCapture',
bubbled: 'onPointerOut',
},
},
topPointerOver: {
phasedRegistrationNames: {
captured: 'onPointerOverCapture',
bubbled: 'onPointerOver',
},
},
topClick: {
phasedRegistrationNames: {
captured: 'onClickCapture',
bubbled: 'onClick',
},
},
topBlur: {
phasedRegistrationNames: {
captured: 'onBlurCapture',
bubbled: 'onBlur',
},
},
topFocus: {
phasedRegistrationNames: {
captured: 'onFocusCapture',
bubbled: 'onFocus',
},
},
};
const directEventTypes = {
topAccessibilityAction: {
registrationName: 'onAccessibilityAction',
},
onGestureHandlerEvent: DynamicallyInjectedByGestureHandler({
registrationName: 'onGestureHandlerEvent',
}),
onGestureHandlerStateChange: DynamicallyInjectedByGestureHandler({
registrationName: 'onGestureHandlerStateChange',
}),
// Direct events from UIManagerModuleConstants.java
topContentSizeChange: {
registrationName: 'onContentSizeChange',
},
topScrollBeginDrag: {
registrationName: 'onScrollBeginDrag',
},
topMessage: {
registrationName: 'onMessage',
},
topSelectionChange: {
registrationName: 'onSelectionChange',
},
topLoadingFinish: {
registrationName: 'onLoadingFinish',
},
topMomentumScrollEnd: {
registrationName: 'onMomentumScrollEnd',
},
topLoadingStart: {
registrationName: 'onLoadingStart',
},
topLoadingError: {
registrationName: 'onLoadingError',
},
topMomentumScrollBegin: {
registrationName: 'onMomentumScrollBegin',
},
topScrollEndDrag: {
registrationName: 'onScrollEndDrag',
},
topScroll: {
registrationName: 'onScroll',
},
topLayout: {
registrationName: 'onLayout',
},
};
const validAttributesForNonEventProps = {
// @ReactProps from BaseViewManager
backgroundColor: {process: require('../StyleSheet/processColor').default},
transform: true,
transformOrigin: true,
experimental_backgroundImage: ReactNativeFeatureFlags.enableNativeCSSParsing()
? (true as const)
: {process: require('../StyleSheet/processBackgroundImage').default},
experimental_backgroundSize: {
process: require('../StyleSheet/processBackgroundSize').default,
},
experimental_backgroundPosition: {
process: require('../StyleSheet/processBackgroundPosition').default,
},
experimental_backgroundRepeat: {
process: require('../StyleSheet/processBackgroundRepeat').default,
},
boxShadow: ReactNativeFeatureFlags.enableNativeCSSParsing()
? (true as const)
: {process: require('../StyleSheet/processBoxShadow').default},
filter: ReactNativeFeatureFlags.enableNativeCSSParsing()
? (true as const)
: {process: require('../StyleSheet/processFilter').default},
mixBlendMode: true,
isolation: true,
opacity: true,
elevation: true,
shadowColor: {process: require('../StyleSheet/processColor').default},
zIndex: true,
renderToHardwareTextureAndroid: true,
testID: true,
nativeID: true,
accessibilityLabelledBy: true,
accessibilityLabel: true,
accessibilityHint: true,
accessibilityRole: true,
accessibilityCollection: true,
accessibilityCollectionItem: true,
accessibilityState: true,
accessibilityActions: true,
accessibilityValue: true,
experimental_accessibilityOrder: true,
importantForAccessibility: true,
screenReaderFocusable: true,
role: true,
rotation: true,
scaleX: true,
scaleY: true,
translateX: true,
translateY: true,
accessibilityLiveRegion: true,
// @ReactProps from LayoutShadowNode
width: true,
minWidth: true,
collapsable: true,
collapsableChildren: true,
maxWidth: true,
height: true,
minHeight: true,
maxHeight: true,
flex: true,
flexGrow: true,
rowGap: true,
columnGap: true,
gap: true,
flexShrink: true,
flexBasis: true,
aspectRatio: true,
flexDirection: true,
flexWrap: true,
alignSelf: true,
alignItems: true,
alignContent: true,
justifyContent: true,
overflow: true,
display: true,
boxSizing: true,
margin: true,
marginBlock: true,
marginBlockEnd: true,
marginBlockStart: true,
marginBottom: true,
marginEnd: true,
marginHorizontal: true,
marginInline: true,
marginInlineEnd: true,
marginInlineStart: true,
marginLeft: true,
marginRight: true,
marginStart: true,
marginTop: true,
marginVertical: true,
padding: true,
paddingBlock: true,
paddingBlockEnd: true,
paddingBlockStart: true,
paddingBottom: true,
paddingEnd: true,
paddingHorizontal: true,
paddingInline: true,
paddingInlineEnd: true,
paddingInlineStart: true,
paddingLeft: true,
paddingRight: true,
paddingStart: true,
paddingTop: true,
paddingVertical: true,
borderWidth: true,
borderStartWidth: true,
borderEndWidth: true,
borderTopWidth: true,
borderBottomWidth: true,
borderLeftWidth: true,
borderRightWidth: true,
outlineColor: {process: require('../StyleSheet/processColor').default},
outlineOffset: true,
outlineStyle: true,
outlineWidth: true,
start: true,
end: true,
left: true,
right: true,
top: true,
bottom: true,
inset: true,
insetBlock: true,
insetBlockEnd: true,
insetBlockStart: true,
insetInline: true,
insetInlineEnd: true,
insetInlineStart: true,
position: true,
style: ReactNativeStyleAttributes,
// ReactClippingViewManager @ReactProps
removeClippedSubviews: true,
// ReactViewManager @ReactProps
accessible: true,
hasTVPreferredFocus: true,
nextFocusDown: true,
nextFocusForward: true,
nextFocusLeft: true,
nextFocusRight: true,
nextFocusUp: true,
borderRadius: true,
borderTopLeftRadius: true,
borderTopRightRadius: true,
borderBottomRightRadius: true,
borderBottomLeftRadius: true,
borderTopStartRadius: true,
borderTopEndRadius: true,
borderBottomStartRadius: true,
borderBottomEndRadius: true,
borderEndEndRadius: true,
borderEndStartRadius: true,
borderStartEndRadius: true,
borderStartStartRadius: true,
borderStyle: true,
hitSlop: true,
pointerEvents: true,
nativeBackgroundAndroid: true,
nativeForegroundAndroid: true,
needsOffscreenAlphaCompositing: true,
borderColor: {
process: require('../StyleSheet/processColor').default,
},
borderLeftColor: {
process: require('../StyleSheet/processColor').default,
},
borderRightColor: {
process: require('../StyleSheet/processColor').default,
},
borderTopColor: {
process: require('../StyleSheet/processColor').default,
},
borderBottomColor: {
process: require('../StyleSheet/processColor').default,
},
borderStartColor: {
process: require('../StyleSheet/processColor').default,
},
borderEndColor: {
process: require('../StyleSheet/processColor').default,
},
borderBlockColor: {
process: require('../StyleSheet/processColor').default,
},
borderBlockEndColor: {
process: require('../StyleSheet/processColor').default,
},
borderBlockStartColor: {
process: require('../StyleSheet/processColor').default,
},
focusable: true,
backfaceVisibility: true,
} as const;
// Props for bubbling and direct events
const validAttributesForEventProps = {
onLayout: true,
// PanResponder handlers
onMoveShouldSetResponder: true,
onMoveShouldSetResponderCapture: true,
onStartShouldSetResponder: true,
onStartShouldSetResponderCapture: true,
onResponderGrant: true,
onResponderReject: true,
onResponderStart: true,
onResponderEnd: true,
onResponderRelease: true,
onResponderMove: true,
onResponderTerminate: true,
onResponderTerminationRequest: true,
onShouldBlockNativeResponder: true,
// Touch events
onTouchStart: true,
onTouchMove: true,
onTouchEnd: true,
onTouchCancel: true,
// Pointer events
onClick: true,
onClickCapture: true,
onPointerEnter: true,
onPointerEnterCapture: true,
onPointerLeave: true,
onPointerLeaveCapture: true,
onPointerMove: true,
onPointerMoveCapture: true,
onPointerOut: true,
onPointerOutCapture: true,
onPointerOver: true,
onPointerOverCapture: true,
} as const;
/**
* On Android, Props are derived from a ViewManager and its ShadowNode.
*
* Where did we find these base platform props from?
* - Nearly all component ViewManagers descend from BaseViewManager,
* - and BaseViewManagers' ShadowNodes descend from LayoutShadowNode.
* - Also, all components inherit ViewConfigs from UIManagerModuleConstants.java.
*
* So, these ViewConfigs are generated from LayoutShadowNode and BaseViewManager.
*/
const PlatformBaseViewConfigAndroid: PartialViewConfigWithoutName = {
directEventTypes,
bubblingEventTypes,
validAttributes: {
...validAttributesForNonEventProps,
...validAttributesForEventProps,
},
};
export default PlatformBaseViewConfigAndroid;

View File

@@ -0,0 +1,424 @@
/**
* 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 {PartialViewConfigWithoutName} from './PlatformBaseViewConfig';
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
import ReactNativeStyleAttributes from '../Components/View/ReactNativeStyleAttributes';
import {
ConditionallyIgnoredEventHandlers,
DynamicallyInjectedByGestureHandler,
} from './ViewConfigIgnore';
const bubblingEventTypes = {
// Generic Events
topPress: {
phasedRegistrationNames: {
bubbled: 'onPress',
captured: 'onPressCapture',
},
},
topChange: {
phasedRegistrationNames: {
bubbled: 'onChange',
captured: 'onChangeCapture',
},
},
topFocus: {
phasedRegistrationNames: {
bubbled: 'onFocus',
captured: 'onFocusCapture',
},
},
topBlur: {
phasedRegistrationNames: {
bubbled: 'onBlur',
captured: 'onBlurCapture',
},
},
topSubmitEditing: {
phasedRegistrationNames: {
bubbled: 'onSubmitEditing',
captured: 'onSubmitEditingCapture',
},
},
topEndEditing: {
phasedRegistrationNames: {
bubbled: 'onEndEditing',
captured: 'onEndEditingCapture',
},
},
topKeyPress: {
phasedRegistrationNames: {
bubbled: 'onKeyPress',
captured: 'onKeyPressCapture',
},
},
// Touch Events
topTouchStart: {
phasedRegistrationNames: {
bubbled: 'onTouchStart',
captured: 'onTouchStartCapture',
},
},
topTouchMove: {
phasedRegistrationNames: {
bubbled: 'onTouchMove',
captured: 'onTouchMoveCapture',
},
},
topTouchCancel: {
phasedRegistrationNames: {
bubbled: 'onTouchCancel',
captured: 'onTouchCancelCapture',
},
},
topTouchEnd: {
phasedRegistrationNames: {
bubbled: 'onTouchEnd',
captured: 'onTouchEndCapture',
},
},
// Experimental/Work in Progress Pointer Events (not yet ready for use)
topClick: {
phasedRegistrationNames: {
captured: 'onClickCapture',
bubbled: 'onClick',
},
},
topPointerCancel: {
phasedRegistrationNames: {
captured: 'onPointerCancelCapture',
bubbled: 'onPointerCancel',
},
},
topPointerDown: {
phasedRegistrationNames: {
captured: 'onPointerDownCapture',
bubbled: 'onPointerDown',
},
},
topPointerMove: {
phasedRegistrationNames: {
captured: 'onPointerMoveCapture',
bubbled: 'onPointerMove',
},
},
topPointerUp: {
phasedRegistrationNames: {
captured: 'onPointerUpCapture',
bubbled: 'onPointerUp',
},
},
topPointerEnter: {
phasedRegistrationNames: {
captured: 'onPointerEnterCapture',
bubbled: 'onPointerEnter',
skipBubbling: true,
},
},
topPointerLeave: {
phasedRegistrationNames: {
captured: 'onPointerLeaveCapture',
bubbled: 'onPointerLeave',
skipBubbling: true,
},
},
topPointerOver: {
phasedRegistrationNames: {
captured: 'onPointerOverCapture',
bubbled: 'onPointerOver',
},
},
topPointerOut: {
phasedRegistrationNames: {
captured: 'onPointerOutCapture',
bubbled: 'onPointerOut',
},
},
topGotPointerCapture: {
phasedRegistrationNames: {
captured: 'onGotPointerCaptureCapture',
bubbled: 'onGotPointerCapture',
},
},
topLostPointerCapture: {
phasedRegistrationNames: {
captured: 'onLostPointerCaptureCapture',
bubbled: 'onLostPointerCapture',
},
},
};
const directEventTypes = {
topAccessibilityAction: {
registrationName: 'onAccessibilityAction',
},
topAccessibilityTap: {
registrationName: 'onAccessibilityTap',
},
topMagicTap: {
registrationName: 'onMagicTap',
},
topAccessibilityEscape: {
registrationName: 'onAccessibilityEscape',
},
topLayout: {
registrationName: 'onLayout',
},
onGestureHandlerEvent: DynamicallyInjectedByGestureHandler({
registrationName: 'onGestureHandlerEvent',
}),
onGestureHandlerStateChange: DynamicallyInjectedByGestureHandler({
registrationName: 'onGestureHandlerStateChange',
}),
};
const validAttributesForNonEventProps = {
// View Props
accessible: true,
accessibilityActions: true,
accessibilityLabel: true,
accessibilityHint: true,
accessibilityLanguage: true,
accessibilityValue: true,
accessibilityViewIsModal: true,
accessibilityElementsHidden: true,
accessibilityIgnoresInvertColors: true,
accessibilityShowsLargeContentViewer: true,
accessibilityLargeContentTitle: true,
experimental_accessibilityOrder: true,
accessibilityRespondsToUserInteraction: true,
testID: true,
backgroundColor: {process: require('../StyleSheet/processColor').default},
backfaceVisibility: true,
cursor: true,
opacity: true,
shadowColor: {process: require('../StyleSheet/processColor').default},
shadowOffset: {diff: require('../Utilities/differ/sizesDiffer').default},
shadowOpacity: true,
shadowRadius: true,
needsOffscreenAlphaCompositing: true,
overflow: true,
shouldRasterizeIOS: true,
transform: {diff: require('../Utilities/differ/matricesDiffer').default},
transformOrigin: true,
accessibilityRole: true,
accessibilityState: true,
nativeID: true,
pointerEvents: true,
removeClippedSubviews: true,
role: true,
borderRadius: true,
borderColor: {process: require('../StyleSheet/processColor').default},
borderBlockColor: {process: require('../StyleSheet/processColor').default},
borderCurve: true,
borderWidth: true,
borderBlockWidth: true,
borderStyle: true,
hitSlop: {diff: require('../Utilities/differ/insetsDiffer').default},
collapsable: true,
collapsableChildren: true,
filter: ReactNativeFeatureFlags.enableNativeCSSParsing()
? (true as const)
: {process: require('../StyleSheet/processFilter').default},
boxShadow: ReactNativeFeatureFlags.enableNativeCSSParsing()
? (true as const)
: {process: require('../StyleSheet/processBoxShadow').default},
mixBlendMode: true,
isolation: true,
borderTopWidth: true,
borderTopColor: {process: require('../StyleSheet/processColor').default},
borderRightWidth: true,
borderRightColor: {process: require('../StyleSheet/processColor').default},
borderBottomWidth: true,
borderBottomColor: {process: require('../StyleSheet/processColor').default},
borderLeftWidth: true,
borderLeftColor: {process: require('../StyleSheet/processColor').default},
borderStartWidth: true,
borderBlockStartWidth: true,
borderStartColor: {process: require('../StyleSheet/processColor').default},
borderBlockStartColor: {
process: require('../StyleSheet/processColor').default,
},
borderEndWidth: true,
borderBlockEndWidth: true,
borderEndColor: {process: require('../StyleSheet/processColor').default},
borderBlockEndColor: {process: require('../StyleSheet/processColor').default},
borderTopLeftRadius: true,
borderTopRightRadius: true,
borderTopStartRadius: true,
borderTopEndRadius: true,
borderBottomLeftRadius: true,
borderBottomRightRadius: true,
borderBottomStartRadius: true,
borderBottomEndRadius: true,
borderEndEndRadius: true,
borderEndStartRadius: true,
borderStartEndRadius: true,
borderStartStartRadius: true,
display: true,
zIndex: true,
// ShadowView properties
top: true,
right: true,
start: true,
end: true,
bottom: true,
left: true,
inset: true,
insetBlock: true,
insetBlockEnd: true,
insetBlockStart: true,
insetInline: true,
insetInlineEnd: true,
insetInlineStart: true,
width: true,
height: true,
minWidth: true,
maxWidth: true,
minHeight: true,
maxHeight: true,
// Also declared as ViewProps
// borderTopWidth: true,
// borderRightWidth: true,
// borderBottomWidth: true,
// borderLeftWidth: true,
// borderStartWidth: true,
// borderEndWidth: true,
// borderWidth: true,
margin: true,
marginBlock: true,
marginBlockEnd: true,
marginBlockStart: true,
marginBottom: true,
marginEnd: true,
marginHorizontal: true,
marginInline: true,
marginInlineEnd: true,
marginInlineStart: true,
marginLeft: true,
marginRight: true,
marginStart: true,
marginTop: true,
marginVertical: true,
padding: true,
paddingBlock: true,
paddingBlockEnd: true,
paddingBlockStart: true,
paddingBottom: true,
paddingEnd: true,
paddingHorizontal: true,
paddingInline: true,
paddingInlineEnd: true,
paddingInlineStart: true,
paddingLeft: true,
paddingRight: true,
paddingStart: true,
paddingTop: true,
paddingVertical: true,
flex: true,
flexGrow: true,
rowGap: true,
columnGap: true,
gap: true,
flexShrink: true,
flexBasis: true,
flexDirection: true,
flexWrap: true,
justifyContent: true,
alignItems: true,
alignSelf: true,
alignContent: true,
position: true,
aspectRatio: true,
boxSizing: true,
// Also declared as ViewProps
// overflow: true,
// display: true,
direction: true,
style: ReactNativeStyleAttributes,
} as const;
// Props for bubbling and direct events
const validAttributesForEventProps = ConditionallyIgnoredEventHandlers({
onLayout: true,
onMagicTap: true,
// Accessibility
onAccessibilityAction: true,
onAccessibilityEscape: true,
onAccessibilityTap: true,
// PanResponder handlers
onMoveShouldSetResponder: true,
onMoveShouldSetResponderCapture: true,
onStartShouldSetResponder: true,
onStartShouldSetResponderCapture: true,
onResponderGrant: true,
onResponderReject: true,
onResponderStart: true,
onResponderEnd: true,
onResponderRelease: true,
onResponderMove: true,
onResponderTerminate: true,
onResponderTerminationRequest: true,
onShouldBlockNativeResponder: true,
// Touch events
onTouchStart: true,
onTouchMove: true,
onTouchEnd: true,
onTouchCancel: true,
// Pointer events
onClick: true,
onClickCapture: true,
onPointerUp: true,
onPointerDown: true,
onPointerCancel: true,
onPointerEnter: true,
onPointerMove: true,
onPointerLeave: true,
onPointerOver: true,
onPointerOut: true,
onGotPointerCapture: true,
onLostPointerCapture: true,
});
/**
* On iOS, view managers define all of a component's props.
* All view managers extend RCTViewManager, and RCTViewManager declares these props.
*/
const PlatformBaseViewConfigIos: PartialViewConfigWithoutName = {
bubblingEventTypes,
directEventTypes,
validAttributes: {
...validAttributesForNonEventProps,
...validAttributesForEventProps,
},
};
export default PlatformBaseViewConfigIos;

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
// NOTE: This file supports backwards compatibility of subpath (deep) imports
// from 'react-native' with platform-specific extensions. It can be deleted
// once we remove the "./*" mapping from package.json "exports".
import BaseViewConfig from './BaseViewConfig';
export default BaseViewConfig;

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {PartialViewConfigWithoutName} from './PlatformBaseViewConfig';
declare const PlatformBaseViewConfig: PartialViewConfigWithoutName;
export default PlatformBaseViewConfig;

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import type {HostComponent} from '../../types/public/ReactNativeTypes';
import * as React from 'react';
/**
* Configures a function that is called to determine whether a given component
* should be registered using reflection of the native component at runtime.
*
* The provider should return null if the native component is unavailable in
* the current environment.
*/
export function setRuntimeConfigProvider(
runtimeConfigProvider: (name: string) => {
native: boolean;
verify: boolean;
} | null,
): void;
/**
* Gets a `NativeComponent` that can be rendered by React Native.
*
* The supplied `viewConfigProvider` may or may not be invoked and utilized,
* depending on how `setRuntimeConfigProvider` is configured.
*/
export function get<Config extends object>(
name: string,
viewConfigProvider: () => PartialViewConfig,
): HostComponent<Config>;
/**
* Same as `NativeComponentRegistry.get(...)`, except this will check either
* the `setRuntimeConfigProvider` configuration or use native reflection (slow)
* to determine whether this native component is available.
*
* If the native component is not available, a stub component is returned. Note
* that the return value of this is not `HostComponent` because the returned
* component instance is not guaranteed to have native methods.
*/
export function getWithFallback_DEPRECATED<Config extends object>(
name: string,
viewConfigProvider: () => PartialViewConfig,
): React.ComponentType<Config>;
/**
* Unstable API. Do not use!
*
* This method returns if there is a StaticViewConfig registered for the
* component name received as a parameter.
*/
export function unstable_hasStaticViewConfig(name: string): boolean;
type AttributeType<T, V> =
| true
| {
readonly diff?: ((arg1: T, arg2: T) => boolean) | undefined;
readonly process?: ((arg1: V) => T) | undefined;
};
type AnyAttributeType = AttributeType<any, any>;
type AttributeConfiguration = {
readonly [propName: string]: AnyAttributeType | void;
readonly style?:
| {
readonly [propName: string]: AnyAttributeType;
}
| undefined;
};
type PartialViewConfig = Readonly<{
bubblingEventTypes?:
| {
readonly [eventName: string]: {
readonly phasedRegistrationNames: {
readonly bubbled: string;
readonly captured: string;
readonly skipBubbling?: boolean | undefined;
};
};
}
| undefined;
directEventTypes?:
| {
readonly [eventName: string]: {
readonly registrationName: string;
};
}
| undefined;
supportsRawText?: boolean | undefined;
uiViewClassName: string;
validAttributes?: AttributeConfiguration | undefined;
}>;

View File

@@ -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 strict-local
* @format
*/
import type {HostComponent} from '../../src/private/types/HostComponent';
import type {
PartialViewConfig,
ViewConfig,
} from '../Renderer/shims/ReactNativeTypes';
import getNativeComponentAttributes from '../ReactNative/getNativeComponentAttributes';
import UIManager from '../ReactNative/UIManager';
import * as ReactNativeViewConfigRegistry from '../Renderer/shims/ReactNativeViewConfigRegistry';
import * as StaticViewConfigValidator from './StaticViewConfigValidator';
import {createViewConfig} from './ViewConfig';
import invariant from 'invariant';
import * as React from 'react';
let getRuntimeConfig;
/**
* Configures a function that is called to determine whether a given component
* should be registered using reflection of the native component at runtime.
*
* The provider should return null if the native component is unavailable in
* the current environment.
*/
export function setRuntimeConfigProvider(
runtimeConfigProvider: (name: string) => ?{
native: boolean,
verify: boolean,
},
): void {
if (getRuntimeConfig === undefined) {
getRuntimeConfig = runtimeConfigProvider;
}
}
/**
* Gets a `NativeComponent` that can be rendered by React Native.
*
* The supplied `viewConfigProvider` may or may not be invoked and utilized,
* depending on how `setRuntimeConfigProvider` is configured.
*/
export function get<Config: {...}>(
name: string,
viewConfigProvider: () => PartialViewConfig,
): HostComponent<Config> {
ReactNativeViewConfigRegistry.register(name, () => {
const {native, verify} = getRuntimeConfig?.(name) ?? {
native: !global.RN$Bridgeless,
verify: false,
};
let viewConfig: ViewConfig;
if (native) {
viewConfig =
getNativeComponentAttributes(name) ??
createViewConfig(viewConfigProvider());
} else {
viewConfig =
createViewConfig(viewConfigProvider()) ??
getNativeComponentAttributes(name);
}
invariant(
viewConfig != null,
'NativeComponentRegistry.get: both static and native view config are missing for native component "%s".',
name,
);
if (verify) {
const nativeViewConfig = native
? viewConfig
: getNativeComponentAttributes(name);
if (nativeViewConfig == null) {
// Defer to static view config if native view config is missing.
return viewConfig;
}
const staticViewConfig: ViewConfig = native
? createViewConfig(viewConfigProvider())
: viewConfig;
const validationOutput = StaticViewConfigValidator.validate(
name,
nativeViewConfig,
staticViewConfig,
);
if (validationOutput.type === 'invalid') {
console.error(
StaticViewConfigValidator.stringifyValidationResult(
name,
validationOutput,
),
);
}
}
return viewConfig;
});
// $FlowFixMe[incompatible-type] `NativeComponent` is actually string!
return name;
}
/**
* Same as `NativeComponentRegistry.get(...)`, except this will check either
* the `setRuntimeConfigProvider` configuration or use native reflection (slow)
* to determine whether this native component is available.
*
* If the native component is not available, a stub component is returned. Note
* that the return value of this is not `HostComponent` because the returned
* component instance is not guaranteed to have native methods.
*/
export function getWithFallback_DEPRECATED<Config: {...}>(
name: string,
viewConfigProvider: () => PartialViewConfig,
): React.ComponentType<Config> {
if (getRuntimeConfig == null) {
// `getRuntimeConfig == null` when static view configs are disabled
// If `setRuntimeConfigProvider` is not configured, use native reflection.
if (hasNativeViewConfig(name)) {
/* $FlowFixMe[incompatible-type] Extra ref prop */
return get<Config>(name, viewConfigProvider);
}
} else {
// If there is no runtime config, then the native component is unavailable.
if (getRuntimeConfig(name) != null) {
/* $FlowFixMe[incompatible-type] Extra ref prop */
return get<Config>(name, viewConfigProvider);
}
}
const FallbackNativeComponent = function (props: Config): React.Node {
return null;
};
FallbackNativeComponent.displayName = `Fallback(${name})`;
return FallbackNativeComponent;
}
function hasNativeViewConfig(name: string): boolean {
invariant(getRuntimeConfig == null, 'Unexpected invocation!');
return UIManager.getViewManagerConfig(name) != null;
}
/**
* Unstable API. Do not use!
*
* This method returns if there is a StaticViewConfig registered for the
* component name received as a parameter.
*/
export function unstable_hasStaticViewConfig(name: string): boolean {
const {native} = getRuntimeConfig?.(name) ?? {
native: true,
};
return !native;
}

View File

@@ -0,0 +1,32 @@
/**
* 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
*/
let componentNameToExists: Map<string, boolean> = new Map();
/**
* Unstable API. Do not use!
*
* This method returns if the component with name received as a parameter
* is registered in the native platform.
*/
export function unstable_hasComponent(name: string): boolean {
let hasNativeComponent = componentNameToExists.get(name);
if (hasNativeComponent == null) {
if (global.__nativeComponentRegistry__hasComponent) {
hasNativeComponent = global.__nativeComponentRegistry__hasComponent(name);
componentNameToExists.set(name, hasNativeComponent);
} else {
throw new Error(
`unstable_hasComponent('${name}'): Global function is not registered`,
);
}
}
return hasNativeComponent;
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {PartialViewConfig} from '../Renderer/shims/ReactNativeTypes';
import BaseViewConfig from './BaseViewConfig';
export type PartialViewConfigWithoutName = Omit<
PartialViewConfig,
'uiViewClassName',
>;
const PlatformBaseViewConfig: PartialViewConfigWithoutName = BaseViewConfig;
// In Wilde/FB4A, use RNHostComponentListRoute in Bridge mode to verify
// whether the JS props defined here match the native props defined
// in RCTViewManagers in iOS, and ViewManagers in Android.
export default PlatformBaseViewConfig;

View File

@@ -0,0 +1,148 @@
/**
* 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 * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
import {type ViewConfig} from '../Renderer/shims/ReactNativeTypes';
export type Difference =
| {
type: 'missing',
path: Array<string>,
nativeValue: mixed,
}
| {
type: 'unequal',
path: Array<string>,
nativeValue: mixed,
staticValue: mixed,
};
export type ValidationResult = ValidResult | InvalidResult;
type ValidResult = {
type: 'valid',
};
type InvalidResult = {
type: 'invalid',
differences: Array<Difference>,
};
/**
* During the migration from native view configs to static view configs, this is
* used to validate that the two are equivalent.
*/
export function validate(
name: string,
nativeViewConfig: ViewConfig,
staticViewConfig: ViewConfig,
): ValidationResult {
const differences: Array<Difference> = [];
accumulateDifferences(
differences,
[],
{
bubblingEventTypes: nativeViewConfig.bubblingEventTypes,
directEventTypes: nativeViewConfig.directEventTypes,
uiViewClassName: nativeViewConfig.uiViewClassName,
validAttributes: nativeViewConfig.validAttributes,
},
{
bubblingEventTypes: staticViewConfig.bubblingEventTypes,
directEventTypes: staticViewConfig.directEventTypes,
uiViewClassName: staticViewConfig.uiViewClassName,
validAttributes: staticViewConfig.validAttributes,
},
);
if (differences.length === 0) {
return {type: 'valid'};
}
return {
type: 'invalid',
differences,
};
}
export function stringifyValidationResult(
name: string,
validationResult: InvalidResult,
): string {
const {differences} = validationResult;
return [
`StaticViewConfigValidator: Invalid static view config for '${name}'.`,
'',
...differences.map(difference => {
const {type, path} = difference;
switch (type) {
case 'missing':
return `- '${path.join('.')}' is missing.`;
case 'unequal':
return `- '${path.join('.')}' is the wrong value.`;
}
}),
'',
].join('\n');
}
function accumulateDifferences(
differences: Array<Difference>,
path: Array<string>,
nativeObject: {...},
staticObject: {...},
): void {
for (const nativeKey in nativeObject) {
// $FlowFixMe[invalid-computed-prop]
const nativeValue = nativeObject[nativeKey];
if (!staticObject.hasOwnProperty(nativeKey)) {
differences.push({
path: [...path, nativeKey],
type: 'missing',
nativeValue,
});
continue;
}
// $FlowFixMe[invalid-computed-prop]
const staticValue = staticObject[nativeKey];
const nativeValueIfObject = ifObject(nativeValue);
if (nativeValueIfObject != null) {
const staticValueIfObject = ifObject(staticValue);
if (staticValueIfObject != null) {
path.push(nativeKey);
accumulateDifferences(
differences,
path,
nativeValueIfObject,
staticValueIfObject,
);
path.pop();
continue;
}
}
if (
nativeValue !== staticValue &&
!ReactNativeFeatureFlags.enableNativeCSSParsing()
) {
differences.push({
path: [...path, nativeKey],
type: 'unequal',
nativeValue,
staticValue,
});
}
}
}
function ifObject(value: mixed): ?{...} {
return typeof value === 'object' && !Array.isArray(value) ? value : null;
}

View File

@@ -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-local
* @format
*/
import type {
PartialViewConfig,
ViewConfig,
} from '../Renderer/shims/ReactNativeTypes';
import PlatformBaseViewConfig from './PlatformBaseViewConfig';
/**
* Creates a complete `ViewConfig` from a `PartialViewConfig`.
*/
export function createViewConfig(
partialViewConfig: PartialViewConfig,
): ViewConfig {
return {
uiViewClassName: partialViewConfig.uiViewClassName,
Commands: {},
bubblingEventTypes: composeIndexers(
PlatformBaseViewConfig.bubblingEventTypes,
partialViewConfig.bubblingEventTypes,
),
directEventTypes: composeIndexers(
PlatformBaseViewConfig.directEventTypes,
partialViewConfig.directEventTypes,
),
// $FlowFixMe[incompatible-type]
validAttributes: composeIndexers(
// $FlowFixMe[incompatible-call] `style` property confuses Flow.
PlatformBaseViewConfig.validAttributes,
// $FlowFixMe[incompatible-call] `style` property confuses Flow.
partialViewConfig.validAttributes,
),
};
}
function composeIndexers<T>(
maybeA: ?{+[string]: T},
maybeB: ?{+[string]: T},
): {+[string]: T} {
return maybeA == null || maybeB == null
? (maybeA ?? maybeB ?? {})
: {...maybeA, ...maybeB};
}

View File

@@ -0,0 +1,51 @@
/**
* 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 Platform from '../Utilities/Platform';
const ignoredViewConfigProps = new WeakSet<{...}>();
/**
* Decorates ViewConfig values that are dynamically injected by the library,
* react-native-gesture-handler. (T45765076)
*/
export function DynamicallyInjectedByGestureHandler<T: {...}>(object: T): T {
ignoredViewConfigProps.add(object);
return object;
}
/**
* On iOS, ViewManager event declarations generate {eventName}: true entries
* in ViewConfig valueAttributes. These entries aren't generated for Android.
* This annotation allows Static ViewConfigs to insert these entries into
* iOS but not Android.
*
* In the future, we want to remove this platform-inconsistency. We want
* to set RN$ViewConfigEventValidAttributesDisabled = true server-side,
* so that iOS does not generate validAttributes from event props in iOS RCTViewManager,
* since Android does not generate validAttributes from events props in Android ViewManager.
*
* TODO(T110872225): Remove this logic, after achieving platform-consistency
*/
export function ConditionallyIgnoredEventHandlers<
const T: {+[name: string]: true},
>(value: T): T | void {
if (Platform.OS === 'ios') {
return value;
}
return undefined;
}
export function isIgnored(value: mixed): boolean {
if (typeof value === 'object' && value != null) {
return ignoredViewConfigProps.has(value);
}
return false;
}