diff --git a/apps/basic-example/ios/Podfile.lock b/apps/basic-example/ios/Podfile.lock index 20ad8ccb84..bbaa392846 100644 --- a/apps/basic-example/ios/Podfile.lock +++ b/apps/basic-example/ios/Podfile.lock @@ -2919,7 +2919,7 @@ SPEC CHECKSUMS: FBLazyVector: a293a88992c4c33f0aee184acab0b64a08ff9458 fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 - hermes-engine: db854ab6e74b584dc130d3ed0be3425726bac226 + hermes-engine: eec912f8a125ae0d3ad67b2e7b81a227319aa13b RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: 2b70c6e3abe00396cefd8913efbf6a2db01a2b36 RCTRequired: f3540eee8094231581d40c5c6d41b0f170237a81 @@ -2993,7 +2993,7 @@ SPEC CHECKSUMS: RNReanimated: 987d0b9af435441cc2ebc2a32ad06cafe8777d4e RNWorklets: 12b2d7cdcb48acbdd0b324d1fa810f849089bd7b SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 6ca93c8c13f56baeec55eb608577619b17a4d64e + Yoga: 7a9f26c70daf0b08d82ec2f862e9a8872442129e PODFILE CHECKSUM: ecce038d8e4749ee17b7dea28be0590cdc8b4836 diff --git a/apps/basic-example/src/App.tsx b/apps/basic-example/src/App.tsx index 483fdc1b7c..2a5fc77b2e 100644 --- a/apps/basic-example/src/App.tsx +++ b/apps/basic-example/src/App.tsx @@ -7,6 +7,7 @@ import Navigator from './Navigator'; import Text from './Text'; import NativeDetector from './NativeDetector'; import RuntimeDecoration from './RuntimeDecoration'; +import ContentsButton from './ContentsButton'; const EXAMPLES = [ { @@ -21,6 +22,10 @@ const EXAMPLES = [ name: 'Native Detector', component: NativeDetector, }, + { + name: 'Contents Button', + component: ContentsButton, + }, ]; const Stack = Navigator.create(); diff --git a/apps/basic-example/src/ContentsButton.tsx b/apps/basic-example/src/ContentsButton.tsx new file mode 100644 index 0000000000..23906db5ad --- /dev/null +++ b/apps/basic-example/src/ContentsButton.tsx @@ -0,0 +1,522 @@ +import React from 'react'; +import { View, StyleSheet, Text, SafeAreaView } from 'react-native'; +import { + GestureHandlerRootView, + ScrollView, + RectButton, +} from 'react-native-gesture-handler'; + +export default function ComplexUI() { + return ( + + + + + + + + + + + + + + + + + + ); +} + +const colors = ['#782AEB', '#38ACDD', '#57B495', '#FF6259', '#FFD61E']; + +function Avatars() { + return ( + + {colors.map((color) => ( + + {color.slice(1, 3)} + + ))} + + ); +} + +function Gallery() { + return ( + + Basic Gallery + + + + + + + + + + ); +} + +function SizeConstraints() { + return ( + + Size Constraints + + + Min/Max + + + 1:1 + + + Flex + + + + ); +} + +function FlexboxTests() { + return ( + + Flexbox Layouts + + + Start + + + Center + + + End + + + + + Wrap 1 + + + Wrap 2 + + + Wrap 3 + + + Wrap 4 + + + + ); +} + +function PositioningTests() { + return ( + + Positioning + + + Z-Index + + + Absolute + + + Relative + + + + ); +} + +function SpacingTests() { + return ( + + Spacing & Overflow + + + Padding + + + Margin + + + Overflow Hidden Test + + + + ); +} + +function VisualEffects() { + return ( + + Visual Effects + + + Shadow + + + Opacity + + + + ); +} + +function ComplexCombinations() { + return ( + + Complex Combinations + + + Complex 1 + + + Complex 2 + + + Complex 3 + + + Complex 4 + + + + ); +} + +function Transforms() { + return ( + + Transform + + + Transform + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#f5f5f5', + }, + scrollContent: { + paddingBottom: 50, + }, + paddedContainer: { + padding: 16, + }, + section: { + marginBottom: 30, + }, + sectionTitle: { + fontSize: 18, + fontWeight: 'bold', + marginBottom: 12, + color: '#333', + }, + gap: { + gap: 10, + }, + row: { + flexDirection: 'row', + }, + + // Avatar styles + avatars: { + width: 90, + height: 90, + borderWidth: 2, + borderColor: '#001A72', + borderTopLeftRadius: 30, + borderTopRightRadius: 5, + borderBottomLeftRadius: 5, + borderBottomRightRadius: 30, + marginHorizontal: 4, + alignItems: 'center', + justifyContent: 'center', + }, + avatarLabel: { + color: '#F8F9FF', + fontSize: 24, + fontWeight: 'bold', + }, + + // Gallery styles + fullWidthButton: { + width: '100%', + height: 160, + backgroundColor: '#FF6259', + borderTopRightRadius: 30, + borderTopLeftRadius: 30, + borderWidth: 1, + borderColor: '#000', + }, + leftButton: { + flex: 1, + height: 160, + backgroundColor: '#FFD61E', + borderBottomLeftRadius: 30, + borderWidth: 5, + borderColor: '#000', + }, + rightButton: { + flex: 1, + backgroundColor: '#782AEB', + height: 160, + borderBottomRightRadius: 30, + borderWidth: 8, + borderColor: '#000', + }, + + // Size constraint styles + minMaxButton: { + minWidth: 80, + maxWidth: 120, + minHeight: 40, + maxHeight: 80, + backgroundColor: '#38ACDD', + borderRadius: 10, + justifyContent: 'center', + alignItems: 'center', + }, + aspectRatioButton: { + width: 80, + aspectRatio: 1, + backgroundColor: '#57B495', + borderRadius: 40, + justifyContent: 'center', + alignItems: 'center', + }, + flexGrowButton: { + flexGrow: 1, + height: 60, + backgroundColor: '#FF6259', + borderRadius: 15, + justifyContent: 'center', + alignItems: 'center', + }, + + // Flexbox styles + flexContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + height: 80, + backgroundColor: '#e0e0e0', + borderRadius: 10, + padding: 10, + marginBottom: 10, + }, + flexStart: { + alignSelf: 'flex-start', + backgroundColor: '#782AEB', + padding: 10, + borderRadius: 5, + }, + flexCenter: { + alignSelf: 'center', + backgroundColor: '#38ACDD', + padding: 10, + borderRadius: 5, + }, + flexEnd: { + alignSelf: 'flex-end', + backgroundColor: '#57B495', + padding: 10, + borderRadius: 5, + }, + flexWrapContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: 5, + }, + wrapItem: { + width: '48%', + height: 50, + backgroundColor: '#FFD61E', + borderRadius: 8, + justifyContent: 'center', + alignItems: 'center', + }, + + // Positioning styles + positionContainer: { + height: 80, + backgroundColor: '#e0e0e0', + borderRadius: 10, + position: 'relative', + }, + absoluteButton: { + position: 'absolute', + top: 10, + right: 10, + backgroundColor: '#FF6259', + padding: 8, + borderRadius: 5, + }, + relativeButton: { + position: 'relative', + top: 20, + left: 20, + backgroundColor: '#782AEB', + padding: 8, + borderRadius: 5, + }, + zIndexButton: { + position: 'absolute', + top: 10, + left: 10, + zIndex: 10, + backgroundColor: '#57B495', + padding: 8, + borderRadius: 5, + }, + + // Spacing styles + paddingButton: { + flex: 1, + backgroundColor: '#38ACDD', + paddingVertical: 20, + paddingHorizontal: 15, + borderRadius: 10, + justifyContent: 'center', + alignItems: 'center', + }, + marginButton: { + flex: 1, + backgroundColor: '#FFD61E', + margin: 10, + padding: 10, + borderRadius: 10, + justifyContent: 'center', + alignItems: 'center', + }, + overflowButton: { + flex: 1, + height: 60, + backgroundColor: '#782AEB', + borderRadius: 10, + overflow: 'hidden', + justifyContent: 'center', + alignItems: 'center', + }, + + // Visual effect styles + shadowButton: { + flex: 1, + height: 60, + backgroundColor: '#FF6259', + borderRadius: 10, + justifyContent: 'center', + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 8, + }, + opacityButton: { + flex: 1, + height: 60, + backgroundColor: '#782AEB', + borderRadius: 10, + justifyContent: 'center', + alignItems: 'center', + opacity: 0.7, + }, + + // Complex combination styles + complexGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: 10, + }, + complexButton1: { + width: '48%', + height: 100, + backgroundColor: '#FF6259', + borderRadius: 20, + borderWidth: 2, + borderColor: '#782AEB', + justifyContent: 'center', + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { width: 2, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 4, + elevation: 4, + marginBottom: 5, + }, + complexButton2: { + width: '48%', + minHeight: 80, + maxHeight: 120, + backgroundColor: '#38ACDD', + borderTopLeftRadius: 30, + borderBottomRightRadius: 30, + paddingVertical: 15, + paddingHorizontal: 10, + justifyContent: 'center', + alignItems: 'center', + overflow: 'hidden', + }, + complexButton3: { + width: '48%', + aspectRatio: 1.5, + backgroundColor: '#57B495', + borderRadius: 15, + borderWidth: 4, + borderColor: '#FFD61E', + justifyContent: 'center', + alignItems: 'center', + opacity: 0.9, + marginTop: 10, + }, + complexButton4: { + width: '48%', + height: 80, + backgroundColor: '#FFD61E', + borderRadius: 10, + borderWidth: 1, + borderColor: '#FF6259', + justifyContent: 'center', + alignItems: 'center', + shadowColor: '#782AEB', + shadowOffset: { width: 0, height: 6 }, + shadowOpacity: 0.4, + shadowRadius: 10, + elevation: 10, + marginTop: 10, + }, + + // Transform styles + transformButton: { + width: '48%', + height: 100, + backgroundColor: '#38ACDD', + borderRadius: 15, + justifyContent: 'center', + alignItems: 'center', + transform: [{ translateX: 100 }, { rotate: '15deg' }, { scale: 1.1 }], + }, + + // Text styles + buttonText: { + color: 'white', + fontWeight: 'bold', + textAlign: 'center', + }, + longText: { + color: 'white', + fontWeight: 'bold', + textAlign: 'center', + fontSize: 16, + }, +}); diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/RNGestureHandlerPackage.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/RNGestureHandlerPackage.kt index 95f071671f..d5c7001e20 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/RNGestureHandlerPackage.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/RNGestureHandlerPackage.kt @@ -11,6 +11,7 @@ import com.facebook.react.module.model.ReactModuleInfo import com.facebook.react.module.model.ReactModuleInfoProvider import com.facebook.react.uimanager.ViewManager import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager +import com.swmansion.gesturehandler.react.RNGestureHandlerButtonWrapperViewManager import com.swmansion.gesturehandler.react.RNGestureHandlerDetectorViewManager import com.swmansion.gesturehandler.react.RNGestureHandlerModule import com.swmansion.gesturehandler.react.RNGestureHandlerRootViewManager @@ -34,6 +35,9 @@ class RNGestureHandlerPackage : RNGestureHandlerDetectorViewManager.REACT_CLASS to ModuleSpec.viewManagerSpec { RNGestureHandlerDetectorViewManager() }, + RNGestureHandlerButtonWrapperViewManager.REACT_CLASS to ModuleSpec.viewManagerSpec { + RNGestureHandlerButtonWrapperViewManager() + }, ) } @@ -41,6 +45,7 @@ class RNGestureHandlerPackage : RNGestureHandlerRootViewManager(), RNGestureHandlerButtonViewManager(), RNGestureHandlerDetectorViewManager(), + RNGestureHandlerButtonWrapperViewManager(), ) override fun getViewManagerNames(reactContext: ReactApplicationContext) = viewManagers.keys.toList() diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonWrapperViewManager.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonWrapperViewManager.kt new file mode 100644 index 0000000000..7137243bd7 --- /dev/null +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonWrapperViewManager.kt @@ -0,0 +1,30 @@ +package com.swmansion.gesturehandler.react + +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.ViewManagerDelegate +import com.facebook.react.viewmanagers.RNGestureHandlerButtonWrapperManagerDelegate +import com.facebook.react.viewmanagers.RNGestureHandlerButtonWrapperManagerInterface +import com.facebook.react.views.view.ReactViewGroup + +@ReactModule(name = RNGestureHandlerButtonWrapperViewManager.REACT_CLASS) +class RNGestureHandlerButtonWrapperViewManager : + ViewGroupManager(), + RNGestureHandlerButtonWrapperManagerInterface { + private val mDelegate: ViewManagerDelegate = + RNGestureHandlerButtonWrapperManagerDelegate< + ReactViewGroup, + RNGestureHandlerButtonWrapperViewManager, + >(this) + + override fun getDelegate(): ViewManagerDelegate = mDelegate + + override fun getName() = REACT_CLASS + + override fun createViewInstance(reactContext: ThemedReactContext) = ReactViewGroup(reactContext) + + companion object { + const val REACT_CLASS = "RNGestureHandlerButtonWrapper" + } +} diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt index c57dd8ad37..9647e6186b 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt @@ -2,6 +2,8 @@ package com.swmansion.gesturehandler.react import android.content.Context import android.view.View +import android.view.ViewGroup +import androidx.core.view.isNotEmpty import com.facebook.react.bridge.ReadableArray import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.UIManagerHelper @@ -160,6 +162,14 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) { // Note: RefreshControl is wrapped with a VirtualDetector, and native gestures for it are attached in `attachVirtualChildren`. val id = if (child is ReactSwipeRefreshLayout) { child.getChildAt(0).id + // TODO: figure out how to do it correctly + } else if (child is ViewGroup && child.isNotEmpty()) { + val grandChild = child.getChildAt(0) + if (grandChild is RNGestureHandlerButtonViewManager.ButtonViewGroup) { + grandChild.id + } else { + child.id + } } else { child.id } diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm index f1ce6f1c9d..1580f304d4 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm @@ -10,6 +10,7 @@ #import +#import #import #import @@ -646,6 +647,10 @@ - (BOOL)isUIScrollViewPanGestureRecognizer:(NSGestureRecognizer *)gestureRecogni - (RNGHUIScrollView *)retrieveScrollView:(RNGHUIView *)view { + if ([view isKindOfClass:[RCTEnhancedScrollView class]]) { + return (RCTEnhancedScrollView *)view; + } + if ([view isKindOfClass:[RCTScrollViewComponentView class]]) { RNGHUIScrollView *scrollView = ((RCTScrollViewComponentView *)view).scrollView; return scrollView; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.h new file mode 100644 index 0000000000..c94ecf946e --- /dev/null +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.h @@ -0,0 +1,19 @@ +#if !TARGET_OS_OSX +#import +#else +#import +#endif + +#import + +#import + +using namespace facebook::react; + +NS_ASSUME_NONNULL_BEGIN + +@interface RNGestureHandlerButtonWrapper : RCTViewComponentView + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.mm new file mode 100644 index 0000000000..526c7743d6 --- /dev/null +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.mm @@ -0,0 +1,46 @@ +#import "RNGestureHandlerButtonWrapper.h" +#import "RNGestureHandlerButtonWrapperComponentDescriptor.h" +#import "RNGestureHandlerModule.h" + +#import +#import + +#import +#import +#import + +@interface RNGestureHandlerButtonWrapper () +@end + +@implementation RNGestureHandlerButtonWrapper + +#if TARGET_OS_OSX ++ (BOOL)shouldBeRecycled +{ + return NO; +} +#endif + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + } + + return self; +} + +#pragma mark - RCTComponentViewProtocol + ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} + +@end + +Class RNGestureHandlerButtonWrapperCls(void) +{ + return RNGestureHandlerButtonWrapper.class; +} diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.mm index de67ea1694..f9fa3e1829 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerDetector.mm @@ -9,6 +9,7 @@ #import #import +#import #include @interface RNGestureHandlerDetector () @@ -295,10 +296,19 @@ - (void)tryAttachNativeHandlersToChildView RNGHUIView *view = self.subviews[0]; + // TODO: figure out how to do it correctly if ([view isKindOfClass:[RCTViewComponentView class]]) { RCTViewComponentView *componentView = (RCTViewComponentView *)view; if (componentView.contentView != nil) { view = componentView.contentView; + } else if (componentView.subviews.count > 0) { + view = componentView.subviews[0]; + if ([view isKindOfClass:[RCTViewComponentView class]]) { + RCTViewComponentView *componentView = (RCTViewComponentView *)view; + if (componentView.contentView != nil) { + view = componentView.contentView; + } + } } } diff --git a/packages/react-native-gesture-handler/package.json b/packages/react-native-gesture-handler/package.json index c8fd01ae1e..48d67b52db 100644 --- a/packages/react-native-gesture-handler/package.json +++ b/packages/react-native-gesture-handler/package.json @@ -127,7 +127,8 @@ "ios": { "componentProvider": { "RNGestureHandlerButton": "RNGestureHandlerButtonComponentView", - "RNGestureHandlerDetector": "RNGestureHandlerDetector" + "RNGestureHandlerDetector": "RNGestureHandlerDetector", + "RNGestureHandlerButtonWrapper": "RNGestureHandlerButtonWrapper" } } }, diff --git a/packages/react-native-gesture-handler/react-native.config.js b/packages/react-native-gesture-handler/react-native.config.js index 4ec41154cd..46e3de4682 100644 --- a/packages/react-native-gesture-handler/react-native.config.js +++ b/packages/react-native-gesture-handler/react-native.config.js @@ -2,7 +2,10 @@ module.exports = { dependency: { platforms: { android: { - componentDescriptors: ['RNGestureHandlerDetectorComponentDescriptor'], + componentDescriptors: [ + 'RNGestureHandlerDetectorComponentDescriptor', + 'RNGestureHandlerButtonWrapperComponentDescriptor', + ], cmakeListsPath: './CMakeLists.txt', }, }, diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/ComponentDescriptors.h b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/ComponentDescriptors.h index d9f3374c48..5621db20e7 100644 --- a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/ComponentDescriptors.h +++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/ComponentDescriptors.h @@ -15,12 +15,14 @@ #include #include +#include #include namespace facebook::react { using RNGestureHandlerButtonComponentDescriptor = ConcreteComponentDescriptor; + using RNGestureHandlerRootViewComponentDescriptor = ConcreteComponentDescriptor; diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperComponentDescriptor.h b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperComponentDescriptor.h new file mode 100644 index 0000000000..c3323ae823 --- /dev/null +++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperComponentDescriptor.h @@ -0,0 +1,32 @@ + +/** + * This code was generated by + * [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be + * lost once the code is regenerated. + * + * @generated by codegen project: GenerateComponentDescriptorH.js + */ + +#pragma once + +#include + +#include "RNGestureHandlerButtonWrapperShadowNode.h" + +namespace facebook::react { + +class RNGestureHandlerButtonWrapperComponentDescriptor final + : public ConcreteComponentDescriptor< + RNGestureHandlerButtonWrapperShadowNode> { + using ConcreteComponentDescriptor::ConcreteComponentDescriptor; + void adopt(ShadowNode &shadowNode) const override { + react_native_assert( + dynamic_cast(&shadowNode)); + + ConcreteComponentDescriptor::adopt(shadowNode); + } +}; + +} // namespace facebook::react diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.cpp b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.cpp new file mode 100644 index 0000000000..9b327dad51 --- /dev/null +++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.cpp @@ -0,0 +1,78 @@ + +#include + +#include "RNGestureHandlerButtonWrapperShadowNode.h" + +namespace facebook::react { + +extern const char RNGestureHandlerButtonWrapperComponentName[] = + "RNGestureHandlerButtonWrapper"; + +void RNGestureHandlerButtonWrapperShadowNode::initialize() { + // When the button wrapper is cloned and has a child node, the child node + // should be cloned as well to ensure it is mutable. + if (!getChildren().empty()) { + prepareChildren(); + } +} + +void RNGestureHandlerButtonWrapperShadowNode::prepareChildren() { + const auto &children = getChildren(); + react_native_assert( + children.size() == 1 && + "RNGestureHandlerButtonWrapper received more than one child"); + + const auto directChild = children[0]; + react_native_assert( + directChild->getChildren().size() == 1 && + "RNGestureHandlerButtonWrapper received more than one grandchild"); + + const auto clonedChild = directChild->clone({}); + + const auto childWithProtectedAccess = + std::static_pointer_cast( + clonedChild); + childWithProtectedAccess->traits_.unset(ShadowNodeTraits::ForceFlattenView); + + replaceChild(*directChild, clonedChild); + + const auto grandChild = clonedChild->getChildren()[0]; + const auto clonedGrandChild = grandChild->clone({}); + clonedChild->replaceChild(*grandChild, clonedGrandChild); +} + +void RNGestureHandlerButtonWrapperShadowNode::appendChild( + const std::shared_ptr &child) { + YogaLayoutableShadowNode::appendChild(child); + prepareChildren(); +} + +void RNGestureHandlerButtonWrapperShadowNode::layout( + LayoutContext layoutContext) { + YogaLayoutableShadowNode::layout(layoutContext); + react_native_assert(getChildren().size() == 1); + react_native_assert(getChildren()[0]->getChildren().size() == 1); + + auto child = std::static_pointer_cast( + getChildren()[0]); + auto grandChild = std::static_pointer_cast( + child->getChildren()[0]); + + child->ensureUnsealed(); + grandChild->ensureUnsealed(); + + auto mutableChild = std::const_pointer_cast(child); + auto mutableGrandChild = + std::const_pointer_cast(grandChild); + + // TODO: figure out the correct way to setup metrics between button wrapper + // and the child + auto metrics = grandChild->getLayoutMetrics(); + mutableChild->setLayoutMetrics(metrics); + + auto metricsNoOrigin = grandChild->getLayoutMetrics(); + metricsNoOrigin.frame.origin = Point{}; + mutableGrandChild->setLayoutMetrics(metricsNoOrigin); +} + +} // namespace facebook::react diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.h b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.h new file mode 100644 index 0000000000..79c87ea6bd --- /dev/null +++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "RNGestureHandlerButtonWrapperState.h" + +namespace facebook::react { + +JSI_EXPORT extern const char RNGestureHandlerButtonWrapperComponentName[]; + +/* + * `ShadowNode` for component. + */ +class RNGestureHandlerButtonWrapperShadowNode final + : public ConcreteViewShadowNode< + RNGestureHandlerButtonWrapperComponentName, + RNGestureHandlerButtonWrapperProps, + RNGestureHandlerButtonWrapperEventEmitter, + RNGestureHandlerButtonWrapperState> { + public: + RNGestureHandlerButtonWrapperShadowNode( + const ShadowNodeFragment &fragment, + const ShadowNodeFamily::Shared &family, + ShadowNodeTraits traits) + : ConcreteViewShadowNode(fragment, family, traits) { + initialize(); + } + + RNGestureHandlerButtonWrapperShadowNode( + const ShadowNode &sourceShadowNode, + const ShadowNodeFragment &fragment) + : ConcreteViewShadowNode(sourceShadowNode, fragment) { + initialize(); + } + + void layout(LayoutContext layoutContext) override; + void appendChild(const std::shared_ptr &child) override; + + private: + void initialize(); + void prepareChildren(); +}; + +} // namespace facebook::react diff --git a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperState.h b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperState.h new file mode 100644 index 0000000000..7d9fa242e0 --- /dev/null +++ b/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperState.h @@ -0,0 +1,32 @@ +/** + * This code was generated by + * [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be + * lost once the code is regenerated. + * + * @generated by codegen project: GenerateStateH.js + */ +#pragma once + +#ifdef ANDROID +#include +#endif + +namespace facebook::react { + +class RNGestureHandlerButtonWrapperState { + public: + RNGestureHandlerButtonWrapperState() = default; + +#ifdef ANDROID + RNGestureHandlerButtonWrapperState( + RNGestureHandlerButtonWrapperState const &previousState, + folly::dynamic data){}; + folly::dynamic getDynamic() const { + return {}; + }; +#endif +}; + +} // namespace facebook::react diff --git a/packages/react-native-gesture-handler/src/components/GestureHandlerButton.tsx b/packages/react-native-gesture-handler/src/components/GestureHandlerButton.tsx index 8f7ddcd1fb..6e6e268903 100644 --- a/packages/react-native-gesture-handler/src/components/GestureHandlerButton.tsx +++ b/packages/react-native-gesture-handler/src/components/GestureHandlerButton.tsx @@ -1,5 +1,147 @@ -import { HostComponent } from 'react-native'; -import type { LegacyRawButtonProps } from './GestureButtonsProps'; +import { HostComponent, StyleSheet, View } from 'react-native'; +import type { RawButtonProps } from '../v3/components/GestureButtonsProps'; import RNGestureHandlerButtonNativeComponent from '../specs/RNGestureHandlerButtonNativeComponent'; +import RNGestureHandlerButtonWrapperNativeComponent from '../specs/RNGestureHandlerButtonWrapperNativeComponent'; +import { useMemo } from 'react'; -export default RNGestureHandlerButtonNativeComponent as HostComponent; +const ButtonComponent = + RNGestureHandlerButtonNativeComponent as HostComponent; + +export default function GestureHandlerButton({ + style, + ...rest +}: RawButtonProps) { + const flattenedStyle = useMemo(() => StyleSheet.flatten(style), [style]); + + const { + // Layout properties + display, + width, + height, + minWidth, + maxWidth, + minHeight, + maxHeight, + flex, + flexGrow, + flexShrink, + flexBasis, + flexDirection, + flexWrap, + justifyContent, + alignItems, + alignContent, + alignSelf, + aspectRatio, + gap, + rowGap, + columnGap, + margin, + marginTop, + marginBottom, + marginLeft, + marginRight, + marginVertical, + marginHorizontal, + marginStart, + marginEnd, + padding, + paddingTop, + paddingBottom, + paddingLeft, + paddingRight, + paddingVertical, + paddingHorizontal, + paddingStart, + paddingEnd, + position, + top, + right, + bottom, + left, + start, + end, + overflow, + + // Visual properties + ...restStyle + } = flattenedStyle; + + // Layout styles for ButtonComponent + const layoutStyle = useMemo( + () => ({ + display, + width, + height, + minWidth, + maxWidth, + minHeight, + maxHeight, + flex, + flexGrow, + flexShrink, + flexBasis, + flexDirection, + flexWrap, + justifyContent, + alignItems, + alignContent, + alignSelf, + aspectRatio, + gap, + rowGap, + columnGap, + margin, + marginTop, + marginBottom, + marginLeft, + marginRight, + marginVertical, + marginHorizontal, + marginStart, + marginEnd, + padding, + paddingTop, + paddingBottom, + paddingLeft, + paddingRight, + paddingVertical, + paddingHorizontal, + paddingStart, + paddingEnd, + position, + top, + right, + bottom, + left, + start, + end, + overflow, + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [flattenedStyle] + ); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + contents: { + display: 'contents', + }, + overflowHidden: { + overflow: 'hidden', + }, +}); diff --git a/packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonWrapperNativeComponent.ts b/packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonWrapperNativeComponent.ts new file mode 100644 index 0000000000..d84e610aaa --- /dev/null +++ b/packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonWrapperNativeComponent.ts @@ -0,0 +1,11 @@ +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; +import type { ViewProps } from 'react-native'; + +interface NativeProps extends ViewProps {} + +export default codegenNativeComponent( + 'RNGestureHandlerButtonWrapper', + { + interfaceOnly: true, + } +);