From c19c38303ac5ec05bc9da097c1fd94a35ee50b26 Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Mon, 31 Jan 2022 14:50:51 -0800 Subject: [PATCH] Introduce PlatformBaseViewConfig and fix SVC for RCTView Summary: ## Impact Fix the Static ViewConfig for . This diff fixes the base ViewConfig for all HostComponents on both platforms. Consequently, it simplifies SVC reconciliation efforts, by nearly eliminating the first of these classes of SVC errors: 1. Unexpected properties in SVC 2. Missing properties in SVC 3. Not matching properites in SVC ## What is the base ViewConfig on each iOS/Android? **On iOS:** - All props come from ViewManagers - All HostComponent ViewManagers extend ViewManager https://pxl.cl/1SxdF Therefore, the base ViewConfig for all components should be 's static ViewConfig. **On Android:** The component model is a bit more complicated: https://pxl.cl/1Vmp5 Takeaways: - Props come from Shadow Nodes **and** ViewManagers - Nearly all HostComponent ViewManagers extend BaseViewManager. But, that's not 's ViewManager. - 's ViewManager is [ReactViewManager](https://fburl.com/code/0zalv8zk), which is a descendent of BaseViewManager, and declares its own ReactProps. So, on Android, it's not safe for the base ViewConfig to be 's ViewConfig: 1. No components actualy incorportate 's props 2. Some components don't even incorporate BaseViewManager's props. So, what should the base ViewConfig be on Android? - Nearly all components extend BaseViewManager. BaseViewManager must have a shadow node [that extends LayoutShadowNode](https://www.internalfb.com/code/fbsource/[47d68ebc06e64d97da9d069f1ab662b392f0df8a]/xplat/js/react-native-github/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java?lines=40). Therefore, we'll make the base ViewConfig on Android be generated by BaseViewManager + LayoutShadowNode. ## Changes In this diff, I removed ReactNativeViewViewConfig, and introduced a new view config called PlatformBaseViewConfig. This ViewConfig partial will capture all the props available on all HostComponents on **both** platforms. This may not necessarily be the props made available on . The only components that don't extend the base platform props are: RCTTextInlineImage. What we do with these components is TBD. Changelog: [Internal] Reviewed By: p-sun, yungsters Differential Revision: D33135055 fbshipit-source-id: 7299f60ae45ed499ce47c0d0a6309a047bff90bb --- .../View/ReactNativeViewViewConfig.js | 374 --------------- .../View/ReactNativeViewViewConfigAndroid.js | 83 ---- .../Components/View/ViewNativeComponent.js | 71 ++- Libraries/Core/setUpReactDevTools.js | 5 +- Libraries/Inspector/Inspector.js | 6 +- .../NativeComponent/PlatformBaseViewConfig.js | 452 ++++++++++++++++++ .../StaticViewConfigValidator.js | 6 +- Libraries/NativeComponent/ViewConfig.js | 8 +- Libraries/NativeComponent/ViewConfigIgnore.js | 27 ++ .../verifyComponentAttributeEquivalence.js | 4 +- 10 files changed, 559 insertions(+), 477 deletions(-) delete mode 100644 Libraries/Components/View/ReactNativeViewViewConfig.js delete mode 100644 Libraries/Components/View/ReactNativeViewViewConfigAndroid.js create mode 100644 Libraries/NativeComponent/PlatformBaseViewConfig.js create mode 100644 Libraries/NativeComponent/ViewConfigIgnore.js diff --git a/Libraries/Components/View/ReactNativeViewViewConfig.js b/Libraries/Components/View/ReactNativeViewViewConfig.js deleted file mode 100644 index 5902cc231a..0000000000 --- a/Libraries/Components/View/ReactNativeViewViewConfig.js +++ /dev/null @@ -1,374 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. 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 {ViewConfig} from '../../Renderer/shims/ReactNativeTypes'; -import ReactNativeViewViewConfigAndroid from './ReactNativeViewViewConfigAndroid'; -import ReactNativeViewViewConfigMacOS from './ReactNativeViewViewConfigMacOS'; // TODO(macOS GH#774) -import {Platform} from 'react-native'; - -const ReactNativeViewConfig: ViewConfig = { - uiViewClassName: 'RCTView', - baseModuleName: null, - Manager: 'ViewManager', - Commands: {}, - Constants: {}, - bubblingEventTypes: { - ...ReactNativeViewViewConfigAndroid.bubblingEventTypes, - topBlur: { - phasedRegistrationNames: { - bubbled: 'onBlur', - captured: 'onBlurCapture', - }, - }, - topChange: { - phasedRegistrationNames: { - bubbled: 'onChange', - captured: 'onChangeCapture', - }, - }, - topEndEditing: { - phasedRegistrationNames: { - bubbled: 'onEndEditing', - captured: 'onEndEditingCapture', - }, - }, - topFocus: { - phasedRegistrationNames: { - bubbled: 'onFocus', - captured: 'onFocusCapture', - }, - }, - topKeyPress: { - phasedRegistrationNames: { - bubbled: 'onKeyPress', - captured: 'onKeyPressCapture', - }, - }, - topPress: { - phasedRegistrationNames: { - bubbled: 'onPress', - captured: 'onPressCapture', - }, - }, - topSubmitEditing: { - phasedRegistrationNames: { - bubbled: 'onSubmitEditing', - captured: 'onSubmitEditingCapture', - }, - }, - topTouchCancel: { - phasedRegistrationNames: { - bubbled: 'onTouchCancel', - captured: 'onTouchCancelCapture', - }, - }, - topTouchEnd: { - phasedRegistrationNames: { - bubbled: 'onTouchEnd', - captured: 'onTouchEndCapture', - }, - }, - topTouchMove: { - phasedRegistrationNames: { - bubbled: 'onTouchMove', - captured: 'onTouchMoveCapture', - }, - }, - topTouchStart: { - phasedRegistrationNames: { - bubbled: 'onTouchStart', - captured: 'onTouchStartCapture', - }, - }, - }, - directEventTypes: { - ...ReactNativeViewViewConfigAndroid.directEventTypes, - ...ReactNativeViewViewConfigMacOS.directEventTypes, // TODO(macOS GH#774) - topAccessibilityAction: { - registrationName: 'onAccessibilityAction', - }, - topAccessibilityEscape: { - registrationName: 'onAccessibilityEscape', - }, - topAccessibilityTap: { - registrationName: 'onAccessibilityTap', - }, - topLayout: { - registrationName: 'onLayout', - }, - topMagicTap: { - registrationName: 'onMagicTap', - }, - topKeyUp: { - registrationName: 'onKeyUp', - }, - topKeyDown: { - registrationName: 'onKeyDown', - }, - topPointerEnter: { - registrationName: 'pointerenter', - }, - topPointerLeave: { - registrationName: 'pointerleave', - }, - topPointerMove: { - registrationName: 'pointermove', - }, - // Events for react-native-gesture-handler (T45765076) - // Remove once this library can handle JS View Configs - onGestureHandlerEvent: { - registrationName: 'onGestureHandlerEvent', - }, - onGestureHandlerStateChange: { - registrationName: 'onGestureHandlerStateChange', - }, - }, - validAttributes: { - ...ReactNativeViewViewConfigAndroid.validAttributes, - ...ReactNativeViewViewConfigMacOS.validAttributes, // TODO(macOS GH#774) - accessibilityActions: true, - accessibilityElementsHidden: true, - accessibilityHint: true, - accessibilityIgnoresInvertColors: true, - accessibilityLabel: true, - accessibilityLiveRegion: true, - accessibilityRole: true, - accessibilityState: true, - accessibilityValue: true, - accessibilityViewIsModal: true, - accessible: true, - alignContent: true, - alignItems: true, - alignSelf: true, - aspectRatio: true, - backfaceVisibility: true, - backgroundColor: {process: require('../../StyleSheet/processColor')}, - borderBottomColor: {process: require('../../StyleSheet/processColor')}, - borderBottomEndRadius: true, - borderBottomLeftRadius: true, - borderBottomRightRadius: true, - borderBottomStartRadius: true, - borderBottomWidth: true, - borderColor: {process: require('../../StyleSheet/processColor')}, - borderEndColor: {process: require('../../StyleSheet/processColor')}, - borderEndWidth: true, - borderLeftColor: {process: require('../../StyleSheet/processColor')}, - borderLeftWidth: true, - borderRadius: true, - borderRightColor: {process: require('../../StyleSheet/processColor')}, - borderRightWidth: true, - borderStartColor: {process: require('../../StyleSheet/processColor')}, - borderStartWidth: true, - borderStyle: true, - borderTopColor: {process: require('../../StyleSheet/processColor')}, - borderTopEndRadius: true, - borderTopLeftRadius: true, - borderTopRightRadius: true, - borderTopStartRadius: true, - borderTopWidth: true, - borderWidth: true, - bottom: true, - clickable: true, - collapsable: true, - direction: true, - display: true, - elevation: true, - end: true, - flex: true, - flexBasis: true, - flexDirection: true, - flexGrow: true, - flexShrink: true, - flexWrap: true, - height: true, - hitSlop: {diff: require('../../Utilities/differ/insetsDiffer')}, - importantForAccessibility: true, - justifyContent: true, - left: true, - margin: true, - marginBottom: true, - marginEnd: true, - marginHorizontal: true, - marginLeft: true, - marginRight: true, - marginStart: true, - marginTop: true, - marginVertical: true, - maxHeight: true, - maxWidth: true, - minHeight: true, - minWidth: true, - nativeID: true, - needsOffscreenAlphaCompositing: true, - onAccessibilityAction: true, - onAccessibilityEscape: true, - onAccessibilityTap: true, - pointerenter: true, - pointerleave: true, - pointermove: true, - onLayout: true, - onMagicTap: true, - opacity: true, - overflow: true, - padding: true, - paddingBottom: true, - paddingEnd: true, - paddingHorizontal: true, - paddingLeft: true, - paddingRight: true, - paddingStart: true, - paddingTop: true, - paddingVertical: true, - pointerEvents: true, - position: true, - removeClippedSubviews: true, - renderToHardwareTextureAndroid: true, - right: true, - rotation: true, - scaleX: true, - scaleY: true, - shadowColor: {process: require('../../StyleSheet/processColor')}, - shadowOffset: {diff: require('../../Utilities/differ/sizesDiffer')}, - shadowOpacity: true, - shadowRadius: true, - shouldRasterizeIOS: true, - start: true, - style: { - alignContent: true, - alignItems: true, - alignSelf: true, - aspectRatio: true, - backfaceVisibility: true, - backgroundColor: {process: require('../../StyleSheet/processColor')}, - borderBottomColor: {process: require('../../StyleSheet/processColor')}, - borderBottomEndRadius: true, - borderBottomLeftRadius: true, - borderBottomRightRadius: true, - borderBottomStartRadius: true, - borderBottomWidth: true, - borderColor: {process: require('../../StyleSheet/processColor')}, - borderEndColor: {process: require('../../StyleSheet/processColor')}, - borderEndWidth: true, - borderLeftColor: {process: require('../../StyleSheet/processColor')}, - borderLeftWidth: true, - borderRadius: true, - borderRightColor: {process: require('../../StyleSheet/processColor')}, - borderRightWidth: true, - borderStartColor: {process: require('../../StyleSheet/processColor')}, - borderStartWidth: true, - borderStyle: true, - borderTopColor: {process: require('../../StyleSheet/processColor')}, - borderTopEndRadius: true, - borderTopLeftRadius: true, - borderTopRightRadius: true, - borderTopStartRadius: true, - borderTopWidth: true, - borderWidth: true, - bottom: true, - color: {process: require('../../StyleSheet/processColor')}, - cursor: true, - decomposedMatrix: true, - direction: true, - display: true, - elevation: true, - end: true, - flex: true, - flexBasis: true, - flexDirection: true, - flexGrow: true, - flexShrink: true, - flexWrap: true, - fontFamily: true, - fontSize: true, - fontStyle: true, - fontVariant: true, - fontWeight: true, - apple_fontSmoothing: true, // TODO(OSS Candidate ISS#2710739) - height: true, - includeFontPadding: true, - justifyContent: true, - left: true, - letterSpacing: true, - lineHeight: true, - margin: true, - marginBottom: true, - marginEnd: true, - marginHorizontal: true, - marginLeft: true, - marginRight: true, - marginStart: true, - marginTop: true, - marginVertical: true, - maxHeight: true, - maxWidth: true, - minHeight: true, - minWidth: true, - opacity: true, - overflow: true, - overlayColor: {process: require('../../StyleSheet/processColor')}, - padding: true, - paddingBottom: true, - paddingEnd: true, - paddingHorizontal: true, - paddingLeft: true, - paddingRight: true, - paddingStart: true, - paddingTop: true, - paddingVertical: true, - position: true, - resizeMode: true, - right: true, - rotation: true, - scaleX: true, - scaleY: true, - shadowColor: {process: require('../../StyleSheet/processColor')}, - shadowOffset: {diff: require('../../Utilities/differ/sizesDiffer')}, - shadowOpacity: true, - shadowRadius: true, - start: true, - textAlign: true, - textAlignVertical: true, - textDecorationColor: {process: require('../../StyleSheet/processColor')}, - textDecorationLine: true, - textDecorationStyle: true, - textShadowColor: {process: require('../../StyleSheet/processColor')}, - textShadowOffset: true, - textShadowRadius: true, - textTransform: true, - tintColor: {process: require('../../StyleSheet/processColor')}, - top: true, - transform: - Platform.OS === 'ios' || Platform.OS === 'macos' // TODO(macOS GH#774) - ? {diff: require('../../Utilities/differ/matricesDiffer')} - : {process: require('../../StyleSheet/processTransform')}, - transformMatrix: true, - translateX: true, - translateY: true, - width: true, - writingDirection: true, - zIndex: true, - }, - testID: true, - top: true, - transform: - Platform.OS === 'ios' || Platform.OS === 'macos' // TODO(macOS GH#774) - ? {diff: require('../../Utilities/differ/matricesDiffer')} - : {process: require('../../StyleSheet/processTransform')}, - translateX: true, - translateY: true, - validKeysDown: true, - validKeysUp: true, - nextKeyViewTag: true, // TODO(macOS GH#768) - width: true, - zIndex: true, - }, -}; - -module.exports = ReactNativeViewConfig; diff --git a/Libraries/Components/View/ReactNativeViewViewConfigAndroid.js b/Libraries/Components/View/ReactNativeViewViewConfigAndroid.js deleted file mode 100644 index 504e529027..0000000000 --- a/Libraries/Components/View/ReactNativeViewViewConfigAndroid.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. 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'; - -const ReactNativeViewViewConfigAndroid = { - uiViewClassName: 'RCTView', - bubblingEventTypes: { - topSelect: { - phasedRegistrationNames: { - bubbled: 'onSelect', - captured: 'onSelectCapture', - }, - }, - topAssetDidLoad: { - phasedRegistrationNames: { - bubbled: 'onAssetDidLoad', - captured: 'onAssetDidLoadCapture', - }, - }, - }, - directEventTypes: { - topClick: { - registrationName: 'onClick', - }, - topContentSizeChange: { - registrationName: 'onContentSizeChange', - }, - topLoadingError: { - registrationName: 'onLoadingError', - }, - topLoadingFinish: { - registrationName: 'onLoadingFinish', - }, - topLoadingStart: { - registrationName: 'onLoadingStart', - }, - topMessage: { - registrationName: 'onMessage', - }, - topMomentumScrollBegin: { - registrationName: 'onMomentumScrollBegin', - }, - topMomentumScrollEnd: { - registrationName: 'onMomentumScrollEnd', - }, - topScroll: { - registrationName: 'onScroll', - }, - topScrollBeginDrag: { - registrationName: 'onScrollBeginDrag', - }, - topScrollEndDrag: { - registrationName: 'onScrollEndDrag', - }, - topSelectionChange: { - registrationName: 'onSelectionChange', - }, - onAssetDidLoad: { - registrationName: 'onAssetDidLoad', - }, - }, - validAttributes: { - hasTVPreferredFocus: true, - focusable: true, - nativeBackgroundAndroid: true, - nativeForegroundAndroid: true, - nextFocusDown: true, - nextFocusForward: true, - nextFocusLeft: true, - nextFocusRight: true, - nextFocusUp: true, - }, -}; - -module.exports = ReactNativeViewViewConfigAndroid; diff --git a/Libraries/Components/View/ViewNativeComponent.js b/Libraries/Components/View/ViewNativeComponent.js index 9d6db6e23c..378e4a5982 100644 --- a/Libraries/Components/View/ViewNativeComponent.js +++ b/Libraries/Components/View/ViewNativeComponent.js @@ -10,18 +10,75 @@ import * as NativeComponentRegistry from '../../NativeComponent/NativeComponentRegistry'; import {type HostComponent} from '../../Renderer/shims/ReactNativeTypes'; -import Platform from '../../Utilities/Platform'; import codegenNativeCommands from '../../Utilities/codegenNativeCommands'; -import ReactNativeViewViewConfigAndroid from './ReactNativeViewViewConfigAndroid'; import {type ViewProps as Props} from './ViewPropTypes'; +import Platform from '../../Utilities/Platform'; + import * as React from 'react'; +const ViewPartialViewConfig = + Platform.OS === 'android' + ? { + uiViewClassName: 'RCTView', + validAttributes: { + // 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, + + borderStyle: true, + hitSlop: true, + pointerEvents: true, + nativeBackgroundAndroid: true, + nativeForegroundAndroid: true, + needsOffscreenAlphaCompositing: true, + + borderWidth: true, + borderLeftWidth: true, + borderRightWidth: true, + borderTopWidth: true, + borderBottomWidth: true, + borderStartWidth: true, + borderEndWidth: true, + + borderColor: {process: require('../../StyleSheet/processColor')}, + borderLeftColor: {process: require('../../StyleSheet/processColor')}, + borderRightColor: {process: require('../../StyleSheet/processColor')}, + borderTopColor: {process: require('../../StyleSheet/processColor')}, + borderBottomColor: { + process: require('../../StyleSheet/processColor'), + }, + borderStartColor: {process: require('../../StyleSheet/processColor')}, + borderEndColor: {process: require('../../StyleSheet/processColor')}, + + focusable: true, + overflow: true, + backfaceVisibility: true, + }, + } + : { + uiViewClassName: 'RCTView', + }; + const ViewNativeComponent: HostComponent = - NativeComponentRegistry.get('RCTView', () => - Platform.OS === 'android' - ? ReactNativeViewViewConfigAndroid - : {uiViewClassName: 'RCTView'}, - ); + NativeComponentRegistry.get('RCTView', () => ViewPartialViewConfig); interface NativeCommands { +hotspotUpdate: ( diff --git a/Libraries/Core/setUpReactDevTools.js b/Libraries/Core/setUpReactDevTools.js index 7665ad4002..46955da620 100644 --- a/Libraries/Core/setUpReactDevTools.js +++ b/Libraries/Core/setUpReactDevTools.js @@ -58,12 +58,13 @@ if (__DEV__) { isWebSocketOpen = true; }); - const viewConfig = require('../Components/View/ReactNativeViewViewConfig'); + const ReactNativeStyleAttributes = require('../Components/View/ReactNativeStyleAttributes'); + reactDevTools.connectToDevTools({ isAppActive, resolveRNStyle: require('../StyleSheet/flattenStyle'), nativeStyleEditorValidAttributes: Object.keys( - viewConfig.validAttributes.style, + ReactNativeStyleAttributes, ), websocket: ws, }); diff --git a/Libraries/Inspector/Inspector.js b/Libraries/Inspector/Inspector.js index cc239e4846..1134e25429 100644 --- a/Libraries/Inspector/Inspector.js +++ b/Libraries/Inspector/Inspector.js @@ -19,6 +19,7 @@ const React = require('react'); const ReactNative = require('../Renderer/shims/ReactNative'); const StyleSheet = require('../StyleSheet/StyleSheet'); const View = require('../Components/View/View'); +const ReactNativeStyleAttributes = require('../Components/View/ReactNativeStyleAttributes'); const invariant = require('invariant'); @@ -47,10 +48,7 @@ const renderers = findRenderers(); // Required for React DevTools to view/edit React Native styles in Flipper. // Flipper doesn't inject these values when initializing DevTools. hook.resolveRNStyle = require('../StyleSheet/flattenStyle'); -const viewConfig = require('../Components/View/ReactNativeViewViewConfig'); -hook.nativeStyleEditorValidAttributes = Object.keys( - viewConfig.validAttributes.style, -); +hook.nativeStyleEditorValidAttributes = Object.keys(ReactNativeStyleAttributes); function findRenderers(): $ReadOnlyArray { const allRenderers = Array.from(hook.renderers.values()); diff --git a/Libraries/NativeComponent/PlatformBaseViewConfig.js b/Libraries/NativeComponent/PlatformBaseViewConfig.js new file mode 100644 index 0000000000..3d0d35152e --- /dev/null +++ b/Libraries/NativeComponent/PlatformBaseViewConfig.js @@ -0,0 +1,452 @@ +/** + * Copyright (c) Meta Platforms, Inc. 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 + * @flow strict-local + */ + +import {Platform} from 'react-native'; +import type {PartialViewConfig} from '../Renderer/shims/ReactNativeTypes'; +import ReactNativeStyleAttributes from '../Components/View/ReactNativeStyleAttributes'; +import {DynamicallyInjectedByGestureHandler} from './ViewConfigIgnore'; + +type PartialViewConfigWithoutName = $Rest< + PartialViewConfig, + {uiViewClassName: string}, +>; + +const PlatformBaseViewConfig: PartialViewConfigWithoutName = + Platform.OS === 'android' + ? /** + * 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. + * - BaseViewManagers' ShadowNodes descend from LayoutShadowNode. + * + * So, these ViewConfigs are generated from LayoutShadowNode and BaseViewManager. + */ + { + directEventTypes: { + topAccessibilityAction: { + registrationName: 'onAccessibilityAction', + }, + topPointerEnter: { + registrationName: 'pointerenter', + }, + topPointerLeave: { + registrationName: 'pointerleave', + }, + topPointerMove: { + registrationName: 'pointermove', + }, + 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', + }, + topClick: { + registrationName: 'onClick', + }, + topLoadingStart: { + registrationName: 'onLoadingStart', + }, + topLoadingError: { + registrationName: 'onLoadingError', + }, + topMomentumScrollBegin: { + registrationName: 'onMomentumScrollBegin', + }, + topScrollEndDrag: { + registrationName: 'onScrollEndDrag', + }, + topScroll: { + registrationName: 'onScroll', + }, + topLayout: { + registrationName: 'onLayout', + }, + }, + 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', + }, + }, + }, + validAttributes: { + // @ReactProps from BaseViewManager + backgroundColor: {process: require('../StyleSheet/processColor')}, + transform: true, + opacity: true, + elevation: true, + shadowColor: {process: require('../StyleSheet/processColor')}, + zIndex: true, + renderToHardwareTextureAndroid: true, + testID: true, + nativeID: true, + accessibilityLabelledBy: true, + accessibilityLabel: true, + accessibilityHint: true, + accessibilityRole: true, + accessibilityState: true, + accessibilityActions: true, + accessibilityValue: true, + importantForAccessibility: true, + rotation: true, + scaleX: true, + scaleY: true, + translateX: true, + translateY: true, + accessibilityLiveRegion: true, + + // @ReactProps from LayoutShadowNode + width: true, + minWidth: true, + collapsable: true, + maxWidth: true, + height: true, + minHeight: true, + maxHeight: true, + flex: true, + flexGrow: true, + flexShrink: true, + flexBasis: true, + aspectRatio: true, + flexDirection: true, + flexWrap: true, + alignSelf: true, + alignItems: true, + alignContent: true, + justifyContent: true, + overflow: true, + display: true, + + margin: true, + marginVertical: true, + marginHorizontal: true, + marginStart: true, + marginEnd: true, + marginTop: true, + marginBottom: true, + marginLeft: true, + marginRight: true, + + padding: true, + paddingVertical: true, + paddingHorizontal: true, + paddingStart: true, + paddingEnd: true, + paddingTop: true, + paddingBottom: true, + paddingLeft: true, + paddingRight: true, + + borderWidth: true, + borderStartWidth: true, + borderEndWidth: true, + borderTopWidth: true, + borderBottomWidth: true, + borderLeftWidth: true, + borderRightWidth: true, + + start: true, + end: true, + left: true, + right: true, + top: true, + bottom: true, + + position: true, + onLayout: true, + + pointerenter: true, + pointerleave: true, + pointermove: true, + + style: ReactNativeStyleAttributes, + }, + } + : /** + * On iOS, ViewManagers define all of a component's props. + * All ViewManagers extend RCTViewManager, and RCTViewManager declares + * these props. + */ + { + 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', + }, + }, + }, + directEventTypes: { + topAccessibilityAction: { + registrationName: 'onAccessibilityAction', + }, + topAccessibilityTap: { + registrationName: 'onAccessibilityTap', + }, + topMagicTap: { + registrationName: 'onMagicTap', + }, + topAccessibilityEscape: { + registrationName: 'onAccessibilityEscape', + }, + topLayout: { + registrationName: 'onLayout', + }, + onGestureHandlerEvent: DynamicallyInjectedByGestureHandler({ + registrationName: 'onGestureHandlerEvent', + }), + onGestureHandlerStateChange: DynamicallyInjectedByGestureHandler({ + registrationName: 'onGestureHandlerStateChange', + }), + }, + validAttributes: { + // View Props + accessible: true, + accessibilityActions: true, + accessibilityLabel: true, + accessibilityHint: true, + accessibilityValue: true, + accessibilityViewIsModal: true, + accessibilityElementsHidden: true, + accessibilityIgnoresInvertColors: true, + testID: true, + backgroundColor: {process: require('../StyleSheet/processColor')}, + backfaceVisibility: true, + opacity: true, + shadowColor: {process: require('../StyleSheet/processColor')}, + shadowOffset: {diff: require('../Utilities/differ/sizesDiffer')}, + shadowOpacity: true, + shadowRadius: true, + needsOffscreenAlphaCompositing: true, + overflow: true, + shouldRasterizeIOS: true, + transform: {diff: require('../Utilities/differ/matricesDiffer')}, + accessibilityRole: true, + accessibilityState: true, + nativeID: true, + pointerEvents: true, + removeClippedSubviews: true, + borderRadius: true, + borderColor: {process: require('../StyleSheet/processColor')}, + borderWidth: true, + borderStyle: true, + hitSlop: {diff: require('../Utilities/differ/insetsDiffer')}, + collapsable: true, + + borderTopWidth: true, + borderTopColor: {process: require('../StyleSheet/processColor')}, + borderRightWidth: true, + borderRightColor: {process: require('../StyleSheet/processColor')}, + borderBottomWidth: true, + borderBottomColor: {process: require('../StyleSheet/processColor')}, + borderLeftWidth: true, + borderLeftColor: {process: require('../StyleSheet/processColor')}, + borderStartWidth: true, + borderStartColor: {process: require('../StyleSheet/processColor')}, + borderEndWidth: true, + borderEndColor: {process: require('../StyleSheet/processColor')}, + + borderTopLeftRadius: true, + borderTopRightRadius: true, + borderTopStartRadius: true, + borderTopEndRadius: true, + borderBottomLeftRadius: true, + borderBottomRightRadius: true, + borderBottomStartRadius: true, + borderBottomEndRadius: true, + display: true, + zIndex: true, + + // ShadowView properties + top: true, + right: true, + start: true, + end: true, + bottom: true, + left: 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, + + marginTop: true, + marginRight: true, + marginBottom: true, + marginLeft: true, + marginStart: true, + marginEnd: true, + marginVertical: true, + marginHorizontal: true, + margin: true, + + paddingTop: true, + paddingRight: true, + paddingBottom: true, + paddingLeft: true, + paddingStart: true, + paddingEnd: true, + paddingVertical: true, + paddingHorizontal: true, + padding: true, + + flex: true, + flexGrow: true, + flexShrink: true, + flexBasis: true, + flexDirection: true, + flexWrap: true, + justifyContent: true, + alignItems: true, + alignSelf: true, + alignContent: true, + position: true, + aspectRatio: true, + + // Also declared as ViewProps + // overflow: true, + // display: true, + + direction: true, + + style: ReactNativeStyleAttributes, + }, + }; + +export default PlatformBaseViewConfig; diff --git a/Libraries/NativeComponent/StaticViewConfigValidator.js b/Libraries/NativeComponent/StaticViewConfigValidator.js index f69efcfb34..ce48473d47 100644 --- a/Libraries/NativeComponent/StaticViewConfigValidator.js +++ b/Libraries/NativeComponent/StaticViewConfigValidator.js @@ -13,6 +13,7 @@ import {type ViewConfig} from '../Renderer/shims/ReactNativeTypes'; import getNativeComponentAttributes from '../ReactNative/getNativeComponentAttributes'; // $FlowFixMe[nonstrict-import] import {createViewConfig} from './ViewConfig'; +import {isIgnored} from './ViewConfigIgnore'; type Difference = | { @@ -183,7 +184,10 @@ function accumulateDifferences( } for (const staticKey in staticObject) { - if (!nativeObject.hasOwnProperty(staticKey)) { + if ( + !nativeObject.hasOwnProperty(staticKey) && + !isIgnored(staticObject[staticKey]) + ) { differences.push({ path: [...path, staticKey], type: 'unexpected', diff --git a/Libraries/NativeComponent/ViewConfig.js b/Libraries/NativeComponent/ViewConfig.js index ffba9d9cde..53a003e60b 100644 --- a/Libraries/NativeComponent/ViewConfig.js +++ b/Libraries/NativeComponent/ViewConfig.js @@ -8,11 +8,11 @@ * @format */ -import ReactNativeViewViewConfig from '../Components/View/ReactNativeViewViewConfig'; import type { PartialViewConfig, ViewConfig, } from '../Renderer/shims/ReactNativeTypes'; +import PlatformBaseViewConfig from './PlatformBaseViewConfig'; /** * Creates a complete `ViewConfig` from a `PartialViewConfig`. @@ -24,16 +24,16 @@ export function createViewConfig( uiViewClassName: partialViewConfig.uiViewClassName, Commands: {}, bubblingEventTypes: composeIndexers( - ReactNativeViewViewConfig.bubblingEventTypes, + PlatformBaseViewConfig.bubblingEventTypes, partialViewConfig.bubblingEventTypes, ), directEventTypes: composeIndexers( - ReactNativeViewViewConfig.directEventTypes, + PlatformBaseViewConfig.directEventTypes, partialViewConfig.directEventTypes, ), validAttributes: composeIndexers( // $FlowFixMe[incompatible-call] `style` property confuses Flow. - ReactNativeViewViewConfig.validAttributes, + PlatformBaseViewConfig.validAttributes, // $FlowFixMe[incompatible-call] `style` property confuses Flow. partialViewConfig.validAttributes, ), diff --git a/Libraries/NativeComponent/ViewConfigIgnore.js b/Libraries/NativeComponent/ViewConfigIgnore.js new file mode 100644 index 0000000000..78ccbb4044 --- /dev/null +++ b/Libraries/NativeComponent/ViewConfigIgnore.js @@ -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 + */ + +const ignoredViewConfigProps = new WeakSet<{...}>(); + +/** + * Decorates ViewConfig values that are dynamically injected by the library, + * react-native-gesture-handler. (T45765076) + */ +export function DynamicallyInjectedByGestureHandler(object: T): T { + ignoredViewConfigProps.add(object); + return object; +} + +export function isIgnored(value: mixed): boolean { + if (typeof value === 'object' && value != null) { + return ignoredViewConfigProps.has(value); + } + return false; +} diff --git a/Libraries/Utilities/verifyComponentAttributeEquivalence.js b/Libraries/Utilities/verifyComponentAttributeEquivalence.js index 437fa90926..684a888e6e 100644 --- a/Libraries/Utilities/verifyComponentAttributeEquivalence.js +++ b/Libraries/Utilities/verifyComponentAttributeEquivalence.js @@ -8,7 +8,7 @@ * @flow */ -import ReactNativeViewViewConfig from '../Components/View/ReactNativeViewViewConfig'; +import PlatformBaseViewConfig from '../NativeComponent/PlatformBaseViewConfig'; import {type ViewConfig} from '../Renderer/shims/ReactNativeTypes'; const IGNORED_KEYS = ['transform', 'hitSlop']; @@ -109,7 +109,7 @@ export function getConfigWithoutViewProps( } return Object.keys(viewConfig[propName]) - .filter(prop => !ReactNativeViewViewConfig[propName][prop]) + .filter(prop => !PlatformBaseViewConfig[propName][prop]) .reduce((obj, prop) => { obj[prop] = viewConfig[propName][prop]; return obj;