diff --git a/apps/common-app/src/new_api/components/button_underlay/index.tsx b/apps/common-app/src/new_api/components/button_underlay/index.tsx
new file mode 100644
index 0000000000..1c4c54409c
--- /dev/null
+++ b/apps/common-app/src/new_api/components/button_underlay/index.tsx
@@ -0,0 +1,552 @@
+import React from 'react';
+import { View, StyleSheet, Text, SafeAreaView } from 'react-native';
+import {
+ GestureHandlerRootView,
+ ScrollView,
+ RawButton,
+} from 'react-native-gesture-handler';
+
+const UNDERLAY_PROPS = {
+ underlayColor: 'red',
+ activeUnderlayOpacity: 0.5,
+ animationDuration: 200,
+ rippleColor: 'transparent',
+} as const;
+
+export default function UnderlayEdgeCases() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function Section({
+ title,
+ children,
+}: {
+ title: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+ {title}
+ {children}
+
+ );
+}
+
+function Row({ children }: { children: React.ReactNode }) {
+ return {children};
+}
+
+function Label({ children }: { children: string }) {
+ return {children};
+}
+
+const buttonBase = {
+ justifyContent: 'center' as const,
+ alignItems: 'center' as const,
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#f5f5f5',
+ },
+ scrollContent: {
+ paddingBottom: 60,
+ },
+ padded: {
+ padding: 16,
+ },
+ section: {
+ marginBottom: 24,
+ },
+ sectionTitle: {
+ fontSize: 14,
+ fontWeight: 'bold',
+ marginBottom: 8,
+ color: '#333',
+ },
+ row: {
+ flexDirection: 'row',
+ gap: 10,
+ marginBottom: 10,
+ },
+ label: {
+ color: '#333',
+ fontSize: 11,
+ fontWeight: '600',
+ },
+ uniformSmall: {
+ flex: 1,
+ height: 70,
+ backgroundColor: '#FFD61E',
+ borderRadius: 8,
+ borderWidth: 2,
+ borderColor: '#000',
+ },
+ uniformLarge: {
+ flex: 1,
+ height: 70,
+ backgroundColor: '#38ACDD',
+ borderRadius: 24,
+ borderWidth: 2,
+ borderColor: '#000',
+ },
+ uniformPill: {
+ flex: 1,
+ height: 70,
+ backgroundColor: '#57B495',
+ borderRadius: 999,
+ borderWidth: 2,
+ borderColor: '#000',
+ },
+ perCornerMixed: {
+ flex: 1,
+ height: 80,
+ backgroundColor: '#FF6259',
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 4,
+ borderBottomLeftRadius: 4,
+ borderBottomRightRadius: 20,
+ borderWidth: 3,
+ borderColor: '#000',
+ },
+ perCornerOneZero: {
+ flex: 1,
+ height: 80,
+ backgroundColor: '#782AEB',
+ borderRadius: 16,
+ borderTopRightRadius: 0,
+ borderWidth: 3,
+ borderColor: '#000',
+ },
+ perCornerDiagonal: {
+ flex: 1,
+ height: 80,
+ backgroundColor: '#38ACDD',
+ borderTopLeftRadius: 30,
+ borderTopRightRadius: 0,
+ borderBottomLeftRadius: 0,
+ borderBottomRightRadius: 30,
+ borderWidth: 3,
+ borderColor: '#000',
+ },
+ logicalStart: {
+ flex: 1,
+ height: 80,
+ backgroundColor: '#57B495',
+ borderTopStartRadius: 24,
+ borderBottomStartRadius: 24,
+ borderWidth: 3,
+ borderColor: '#000',
+ },
+ logicalEnd: {
+ flex: 1,
+ height: 80,
+ backgroundColor: '#FFD61E',
+ borderTopEndRadius: 24,
+ borderBottomEndRadius: 24,
+ borderWidth: 3,
+ borderColor: '#000',
+ },
+ logicalMixed: {
+ flex: 1,
+ height: 80,
+ backgroundColor: '#FF6259',
+ borderTopStartRadius: 20,
+ borderBottomEndRadius: 20,
+ borderTopEndRadius: 0,
+ borderBottomStartRadius: 0,
+ borderWidth: 3,
+ borderColor: '#000',
+ },
+ oversizedUniform: {
+ flex: 1,
+ height: 80,
+ backgroundColor: '#782AEB',
+ borderRadius: 200,
+ borderWidth: 3,
+ borderColor: '#000',
+ },
+ oversizedUneven: {
+ flex: 1,
+ height: 80,
+ backgroundColor: '#38ACDD',
+ borderTopLeftRadius: 200,
+ borderTopRightRadius: 10,
+ borderBottomLeftRadius: 10,
+ borderBottomRightRadius: 200,
+ borderWidth: 3,
+ borderColor: '#000',
+ },
+ unevenBorderThickBottom: {
+ flex: 1,
+ height: 90,
+ backgroundColor: '#FFD61E',
+ borderRadius: 16,
+ borderWidth: 3,
+ borderBottomWidth: 16,
+ borderColor: '#000',
+ },
+ unevenBorderThickLeft: {
+ flex: 1,
+ height: 90,
+ backgroundColor: '#57B495',
+ borderRadius: 16,
+ borderWidth: 3,
+ borderLeftWidth: 16,
+ borderColor: '#000',
+ },
+ unevenBorderAllDifferent: {
+ flex: 1,
+ height: 90,
+ backgroundColor: '#FF6259',
+ borderRadius: 12,
+ borderTopWidth: 2,
+ borderRightWidth: 6,
+ borderBottomWidth: 14,
+ borderLeftWidth: 10,
+ borderColor: '#000',
+ },
+ unevenBorderWithRadius: {
+ flex: 1,
+ height: 90,
+ backgroundColor: '#782AEB',
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 0,
+ borderBottomLeftRadius: 0,
+ borderBottomRightRadius: 32,
+ borderWidth: 5,
+ borderBottomWidth: 16,
+ borderColor: '#000',
+ },
+ unevenOversized: {
+ flex: 1,
+ height: 90,
+ backgroundColor: '#38ACDD',
+ borderRadius: 200,
+ borderWidth: 3,
+ borderBottomWidth: 20,
+ borderColor: '#000',
+ },
+ unevenOversizedMixed: {
+ flex: 1,
+ height: 90,
+ backgroundColor: '#FFD61E',
+ borderRadius: 16,
+ borderTopRightRadius: 0,
+ borderBottomRightRadius: 32,
+ borderWidth: 5,
+ borderBottomWidth: 16,
+ borderColor: '#000',
+ },
+ noBorderRadius: {
+ flex: 1,
+ height: 70,
+ backgroundColor: '#57B495',
+ borderWidth: 3,
+ borderColor: '#000',
+ },
+ zeroBorderRadius: {
+ flex: 1,
+ height: 70,
+ backgroundColor: '#FF6259',
+ borderRadius: 0,
+ borderWidth: 3,
+ borderColor: '#000',
+ },
+ someZeroCorners: {
+ flex: 1,
+ height: 70,
+ backgroundColor: '#782AEB',
+ borderRadius: 16,
+ borderTopLeftRadius: 0,
+ borderBottomRightRadius: 0,
+ borderWidth: 3,
+ borderColor: '#000',
+ },
+ radiusLessThanBorder: {
+ flex: 1,
+ height: 80,
+ backgroundColor: '#38ACDD',
+ borderRadius: 5,
+ borderWidth: 10,
+ borderColor: '#000',
+ },
+ radiusEqualsBorder: {
+ flex: 1,
+ height: 80,
+ backgroundColor: '#FFD61E',
+ borderRadius: 10,
+ borderWidth: 10,
+ borderColor: '#000',
+ },
+ radiusSlightlyMore: {
+ flex: 1,
+ height: 80,
+ backgroundColor: '#57B495',
+ borderRadius: 12,
+ borderWidth: 10,
+ borderColor: '#000',
+ },
+ ellipticalInner: {
+ flex: 1,
+ height: 90,
+ backgroundColor: '#FF6259',
+ borderRadius: 20,
+ borderWidth: 5,
+ borderBottomWidth: 16,
+ borderColor: '#000',
+ },
+ ellipticalInnerLarge: {
+ flex: 1,
+ height: 90,
+ backgroundColor: '#782AEB',
+ borderRadius: 24,
+ borderWidth: 2,
+ borderBottomWidth: 20,
+ borderColor: '#000',
+ },
+ noBorderWithRadius: {
+ flex: 1,
+ height: 70,
+ backgroundColor: '#38ACDD',
+ borderRadius: 16,
+ },
+ noBorderPill: {
+ flex: 1,
+ height: 70,
+ backgroundColor: '#57B495',
+ borderRadius: 999,
+ },
+ paddingSmallRadius: {
+ flex: 1,
+ height: 80,
+ backgroundColor: '#FFD61E',
+ borderRadius: 8,
+ borderWidth: 3,
+ borderColor: '#000',
+ padding: 16,
+ },
+ paddingLargeRadius: {
+ flex: 1,
+ height: 80,
+ backgroundColor: '#FF6259',
+ borderRadius: 30,
+ borderWidth: 3,
+ borderBottomWidth: 12,
+ borderColor: '#000',
+ padding: 16,
+ },
+});
diff --git a/apps/common-app/src/new_api/index.tsx b/apps/common-app/src/new_api/index.tsx
index 3c706cf91b..5a9f73e41b 100644
--- a/apps/common-app/src/new_api/index.tsx
+++ b/apps/common-app/src/new_api/index.tsx
@@ -27,6 +27,7 @@ import RotationExample from './simple/rotation';
import TapExample from './simple/tap';
import ButtonsExample from './components/buttons';
+import ButtonUnderlayExample from './components/button_underlay';
import ReanimatedDrawerLayout from './components/drawer';
import FlatListExample from './components/flatlist';
import ScrollViewExample from './components/scrollview';
@@ -105,6 +106,7 @@ export const NEW_EXAMPLES: ExamplesSection[] = [
{ name: 'FlatList example', component: FlatListExample },
{ name: 'ScrollView example', component: ScrollViewExample },
{ name: 'Buttons example', component: ButtonsExample },
+ { name: 'Button underlay example', component: ButtonUnderlayExample },
{ name: 'Switch & TextInput', component: SwitchTextInputExample },
{ name: 'Reanimated Swipeable', component: Swipeable },
{ name: 'Reanimated Drawer Layout', component: ReanimatedDrawerLayout },
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 d5c7001e20..95f071671f 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,7 +11,6 @@ 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
@@ -35,9 +34,6 @@ class RNGestureHandlerPackage :
RNGestureHandlerDetectorViewManager.REACT_CLASS to ModuleSpec.viewManagerSpec {
RNGestureHandlerDetectorViewManager()
},
- RNGestureHandlerButtonWrapperViewManager.REACT_CLASS to ModuleSpec.viewManagerSpec {
- RNGestureHandlerButtonWrapperViewManager()
- },
)
}
@@ -45,7 +41,6 @@ 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/RNGestureHandlerButtonViewManager.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt
index 7a013ad041..31428bed99 100644
--- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt
+++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt
@@ -7,12 +7,9 @@ import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.Context
import android.content.res.ColorStateList
+import android.graphics.Canvas
import android.graphics.Color
-import android.graphics.DashPathEffect
-import android.graphics.Paint
-import android.graphics.PathEffect
import android.graphics.drawable.Drawable
-import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.PaintDrawable
import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.ShapeDrawable
@@ -28,6 +25,9 @@ import androidx.core.view.children
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import com.facebook.react.R
import com.facebook.react.module.annotations.ReactModule
+import com.facebook.react.uimanager.BackgroundStyleApplicator
+import com.facebook.react.uimanager.LengthPercentage
+import com.facebook.react.uimanager.LengthPercentageType
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.uimanager.PointerEvents
import com.facebook.react.uimanager.ReactPointerEventsView
@@ -36,6 +36,9 @@ import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.ViewManagerDelegate
import com.facebook.react.uimanager.ViewProps
import com.facebook.react.uimanager.annotations.ReactProp
+import com.facebook.react.uimanager.style.BorderRadiusProp
+import com.facebook.react.uimanager.style.BorderStyle
+import com.facebook.react.uimanager.style.LogicalEdge
import com.facebook.react.viewmanagers.RNGestureHandlerButtonManagerDelegate
import com.facebook.react.viewmanagers.RNGestureHandlerButtonManagerInterface
import com.swmansion.gesturehandler.core.NativeViewGestureHandler
@@ -63,7 +66,7 @@ class RNGestureHandlerButtonViewManager :
@ReactProp(name = "backgroundColor")
override fun setBackgroundColor(view: ButtonViewGroup, backgroundColor: Int) {
- view.setBackgroundColor(backgroundColor)
+ BackgroundStyleApplicator.setBackgroundColor(view, backgroundColor)
}
@ReactProp(name = "borderless")
@@ -76,44 +79,171 @@ class RNGestureHandlerButtonViewManager :
view.isEnabled = enabled
}
+ @ReactProp(name = "borderWidth")
+ override fun setBorderWidth(view: ButtonViewGroup, borderWidth: Float) {
+ BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.ALL, borderWidth)
+ }
+
+ @ReactProp(name = "borderLeftWidth")
+ override fun setBorderLeftWidth(view: ButtonViewGroup, value: Float) {
+ BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.LEFT, value)
+ }
+
+ @ReactProp(name = "borderRightWidth")
+ override fun setBorderRightWidth(view: ButtonViewGroup, value: Float) {
+ BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.RIGHT, value)
+ }
+
+ @ReactProp(name = "borderTopWidth")
+ override fun setBorderTopWidth(view: ButtonViewGroup, value: Float) {
+ BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.TOP, value)
+ }
+
+ @ReactProp(name = "borderBottomWidth")
+ override fun setBorderBottomWidth(view: ButtonViewGroup, value: Float) {
+ BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.BOTTOM, value)
+ }
+
+ @ReactProp(name = "borderStartWidth")
+ override fun setBorderStartWidth(view: ButtonViewGroup, value: Float) {
+ BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.START, value)
+ }
+
+ @ReactProp(name = "borderEndWidth")
+ override fun setBorderEndWidth(view: ButtonViewGroup, value: Float) {
+ BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.END, value)
+ }
+
+ @ReactProp(name = "borderColor")
+ override fun setBorderColor(view: ButtonViewGroup, borderColor: Int?) {
+ BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.ALL, borderColor)
+ }
+
+ @ReactProp(name = "borderLeftColor")
+ override fun setBorderLeftColor(view: ButtonViewGroup, value: Int?) {
+ BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.LEFT, value)
+ }
+
+ @ReactProp(name = "borderRightColor")
+ override fun setBorderRightColor(view: ButtonViewGroup, value: Int?) {
+ BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.RIGHT, value)
+ }
+
+ @ReactProp(name = "borderTopColor")
+ override fun setBorderTopColor(view: ButtonViewGroup, value: Int?) {
+ BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.TOP, value)
+ }
+
+ @ReactProp(name = "borderBottomColor")
+ override fun setBorderBottomColor(view: ButtonViewGroup, value: Int?) {
+ BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.BOTTOM, value)
+ }
+
+ @ReactProp(name = "borderStartColor")
+ override fun setBorderStartColor(view: ButtonViewGroup, value: Int?) {
+ BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.START, value)
+ }
+
+ @ReactProp(name = "borderEndColor")
+ override fun setBorderEndColor(view: ButtonViewGroup, value: Int?) {
+ BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.END, value)
+ }
+
+ @ReactProp(name = "borderBlockColor")
+ override fun setBorderBlockColor(view: ButtonViewGroup, value: Int?) {
+ BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.BLOCK, value)
+ }
+
+ @ReactProp(name = "borderBlockEndColor")
+ override fun setBorderBlockEndColor(view: ButtonViewGroup, value: Int?) {
+ BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.BLOCK_END, value)
+ }
+
+ @ReactProp(name = "borderBlockStartColor")
+ override fun setBorderBlockStartColor(view: ButtonViewGroup, value: Int?) {
+ BackgroundStyleApplicator.setBorderColor(view, LogicalEdge.BLOCK_START, value)
+ }
+
+ @ReactProp(name = "borderStyle")
+ override fun setBorderStyle(view: ButtonViewGroup, borderStyle: String?) {
+ val parsed = if (borderStyle == null) null else BorderStyle.fromString(borderStyle)
+ BackgroundStyleApplicator.setBorderStyle(view, parsed)
+ }
+
+ @ReactProp(name = ViewProps.OVERFLOW)
+ override fun setOverflow(view: ButtonViewGroup, overflow: String?) {
+ view.setOverflow(overflow)
+ }
+
+ private fun setBorderRadiusInternal(view: ButtonViewGroup, prop: BorderRadiusProp, value: Float) {
+ val isUnset = value.isNaN() || value < 0f
+ val lp = if (isUnset) null else LengthPercentage(value, LengthPercentageType.POINT)
+ BackgroundStyleApplicator.setBorderRadius(view, prop, lp)
+ }
+
@ReactProp(name = ViewProps.BORDER_RADIUS)
override fun setBorderRadius(view: ButtonViewGroup, borderRadius: Float) {
- view.borderRadius = borderRadius
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_RADIUS, borderRadius)
}
@ReactProp(name = "borderTopLeftRadius")
- override fun setBorderTopLeftRadius(view: ButtonViewGroup, borderTopLeftRadius: Float) {
- view.borderTopLeftRadius = borderTopLeftRadius
+ override fun setBorderTopLeftRadius(view: ButtonViewGroup, value: Float) {
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_TOP_LEFT_RADIUS, value)
}
@ReactProp(name = "borderTopRightRadius")
- override fun setBorderTopRightRadius(view: ButtonViewGroup, borderTopRightRadius: Float) {
- view.borderTopRightRadius = borderTopRightRadius
+ override fun setBorderTopRightRadius(view: ButtonViewGroup, value: Float) {
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_TOP_RIGHT_RADIUS, value)
+ }
+
+ @ReactProp(name = "borderBottomRightRadius")
+ override fun setBorderBottomRightRadius(view: ButtonViewGroup, value: Float) {
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_BOTTOM_RIGHT_RADIUS, value)
}
@ReactProp(name = "borderBottomLeftRadius")
- override fun setBorderBottomLeftRadius(view: ButtonViewGroup, borderBottomLeftRadius: Float) {
- view.borderBottomLeftRadius = borderBottomLeftRadius
+ override fun setBorderBottomLeftRadius(view: ButtonViewGroup, value: Float) {
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_BOTTOM_LEFT_RADIUS, value)
}
- @ReactProp(name = "borderBottomRightRadius")
- override fun setBorderBottomRightRadius(view: ButtonViewGroup, borderBottomRightRadius: Float) {
- view.borderBottomRightRadius = borderBottomRightRadius
+ @ReactProp(name = "borderTopStartRadius")
+ override fun setBorderTopStartRadius(view: ButtonViewGroup, value: Float) {
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_TOP_START_RADIUS, value)
}
- @ReactProp(name = "borderWidth")
- override fun setBorderWidth(view: ButtonViewGroup, borderWidth: Float) {
- view.borderWidth = borderWidth
+ @ReactProp(name = "borderTopEndRadius")
+ override fun setBorderTopEndRadius(view: ButtonViewGroup, value: Float) {
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_TOP_END_RADIUS, value)
}
- @ReactProp(name = "borderColor")
- override fun setBorderColor(view: ButtonViewGroup, borderColor: Int?) {
- view.borderColor = borderColor
+ @ReactProp(name = "borderBottomStartRadius")
+ override fun setBorderBottomStartRadius(view: ButtonViewGroup, value: Float) {
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_BOTTOM_START_RADIUS, value)
}
- @ReactProp(name = "borderStyle")
- override fun setBorderStyle(view: ButtonViewGroup, borderStyle: String?) {
- view.borderStyle = borderStyle
+ @ReactProp(name = "borderBottomEndRadius")
+ override fun setBorderBottomEndRadius(view: ButtonViewGroup, value: Float) {
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_BOTTOM_END_RADIUS, value)
+ }
+
+ @ReactProp(name = "borderEndEndRadius")
+ override fun setBorderEndEndRadius(view: ButtonViewGroup, value: Float) {
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_END_END_RADIUS, value)
+ }
+
+ @ReactProp(name = "borderEndStartRadius")
+ override fun setBorderEndStartRadius(view: ButtonViewGroup, value: Float) {
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_END_START_RADIUS, value)
+ }
+
+ @ReactProp(name = "borderStartEndRadius")
+ override fun setBorderStartEndRadius(view: ButtonViewGroup, value: Float) {
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_START_END_RADIUS, value)
+ }
+
+ @ReactProp(name = "borderStartStartRadius")
+ override fun setBorderStartStartRadius(view: ButtonViewGroup, value: Float) {
+ setBorderRadiusInternal(view, BorderRadiusProp.BORDER_START_START_RADIUS, value)
}
@ReactProp(name = "rippleColor")
@@ -214,45 +344,6 @@ class RNGestureHandlerButtonViewManager :
field = useForeground
}
var useBorderlessDrawable = false
- var borderRadius = 0f
- set(radius) = withBackgroundUpdate {
- field = radius * resources.displayMetrics.density
- }
- var borderTopLeftRadius = 0f
- set(radius) = withBackgroundUpdate {
- field = radius * resources.displayMetrics.density
- }
- var borderTopRightRadius = 0f
- set(radius) = withBackgroundUpdate {
- field = radius * resources.displayMetrics.density
- }
- var borderBottomLeftRadius = 0f
- set(radius) = withBackgroundUpdate {
- field = radius * resources.displayMetrics.density
- }
- var borderBottomRightRadius = 0f
- set(radius) = withBackgroundUpdate {
- field = radius * resources.displayMetrics.density
- }
- var borderWidth = 0f
- set(width) = withBackgroundUpdate {
- field = width * resources.displayMetrics.density
- }
- var borderColor: Int? = null
- set(color) = withBackgroundUpdate {
- field = color
- }
- var borderStyle: String? = "solid"
- set(style) = withBackgroundUpdate {
- field = style
- }
-
- private val hasBorderRadii: Boolean
- get() = borderRadius != 0f ||
- borderTopLeftRadius != 0f ||
- borderTopRightRadius != 0f ||
- borderBottomLeftRadius != 0f ||
- borderBottomRightRadius != 0f
var exclusive = true
var animationDuration: Int = 100
@@ -272,7 +363,6 @@ class RNGestureHandlerButtonViewManager :
override var pointerEvents: PointerEvents = PointerEvents.AUTO
- private var buttonBackgroundColor = Color.TRANSPARENT
private var needBackgroundUpdate = false
private var lastEventTime = -1L
private var lastAction = -1
@@ -280,6 +370,10 @@ class RNGestureHandlerButtonViewManager :
private var currentAnimator: AnimatorSet? = null
private var underlayDrawable: PaintDrawable? = null
+ // When non-null the ripple is drawn in dispatchDraw (above background, below children).
+ // When null the ripple lives on the foreground drawable instead.
+ private var selectableDrawable: Drawable? = null
+
var isTouched = false
init {
@@ -296,30 +390,22 @@ class RNGestureHandlerButtonViewManager :
needBackgroundUpdate = true
}
- private fun buildBorderRadii(): FloatArray {
- // duplicate radius for each corner, as setCornerRadii expects X radius and Y radius for each
- return floatArrayOf(
- borderTopLeftRadius,
- borderTopLeftRadius,
- borderTopRightRadius,
- borderTopRightRadius,
- borderBottomRightRadius,
- borderBottomRightRadius,
- borderBottomLeftRadius,
- borderBottomLeftRadius,
- )
- .map { if (it != 0f) it else borderRadius }
- .toFloatArray()
- }
-
- private fun buildBorderStyle(): PathEffect? = when (borderStyle) {
- "dotted" -> DashPathEffect(floatArrayOf(borderWidth, borderWidth, borderWidth, borderWidth), 0f)
- "dashed" -> DashPathEffect(floatArrayOf(borderWidth * 3, borderWidth * 3, borderWidth * 3, borderWidth * 3), 0f)
- else -> null
+ fun setOverflow(overflow: String?) {
+ when (overflow) {
+ "hidden" -> {
+ clipChildren = true
+ clipToPadding = true
+ }
+ else -> {
+ clipChildren = false
+ clipToPadding = false
+ }
+ }
+ invalidate()
}
- override fun setBackgroundColor(color: Int) = withBackgroundUpdate {
- buttonBackgroundColor = color
+ override fun setBackgroundColor(color: Int) {
+ BackgroundStyleApplicator.setBackgroundColor(this, color)
}
override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) {
@@ -391,14 +477,12 @@ class RNGestureHandlerButtonViewManager :
}
private fun applyStartAnimationState() {
- (parent as? ViewGroup)?.let {
- if (activeOpacity != 1.0f || defaultOpacity != 1.0f) {
- it.alpha = defaultOpacity
- }
- if (activeScale != 1.0f || defaultScale != 1.0f) {
- it.scaleX = defaultScale
- it.scaleY = defaultScale
- }
+ if (activeOpacity != 1.0f || defaultOpacity != 1.0f) {
+ alpha = defaultOpacity
+ }
+ if (activeScale != 1.0f || defaultScale != 1.0f) {
+ scaleX = defaultScale
+ scaleY = defaultScale
}
underlayDrawable?.alpha = (defaultUnderlayOpacity * 255).toInt()
}
@@ -413,15 +497,12 @@ class RNGestureHandlerButtonViewManager :
currentAnimator?.cancel()
val animators = ArrayList()
- if (hasOpacity || hasScale) {
- val parent = this.parent as? ViewGroup ?: return
- if (hasOpacity) {
- animators.add(ObjectAnimator.ofFloat(parent, "alpha", opacity))
- }
- if (hasScale) {
- animators.add(ObjectAnimator.ofFloat(parent, "scaleX", scale))
- animators.add(ObjectAnimator.ofFloat(parent, "scaleY", scale))
- }
+ if (hasOpacity) {
+ animators.add(ObjectAnimator.ofFloat(this, "alpha", opacity))
+ }
+ if (hasScale) {
+ animators.add(ObjectAnimator.ofFloat(this, "scaleX", scale))
+ animators.add(ObjectAnimator.ofFloat(this, "scaleY", scale))
}
if (hasUnderlay) {
animators.add(ObjectAnimator.ofInt(underlayDrawable!!, "alpha", (underlayOpacity * 255).toInt()))
@@ -444,92 +525,74 @@ class RNGestureHandlerButtonViewManager :
private fun createUnderlayDrawable(): PaintDrawable {
val drawable = PaintDrawable(underlayColor ?: Color.BLACK)
- if (hasBorderRadii) {
- drawable.setCornerRadii(buildBorderRadii())
- }
drawable.alpha = (defaultUnderlayOpacity * 255).toInt()
return drawable
}
- private fun updateBackgroundColor(
- backgroundColor: Int,
- underlay: Drawable,
- borderDrawable: Drawable,
- selectable: Drawable?,
- ) {
- val colorDrawable = PaintDrawable(backgroundColor)
-
- if (hasBorderRadii) {
- colorDrawable.setCornerRadii(buildBorderRadii())
- }
-
- val layerDrawable = LayerDrawable(
- if (selectable != null) {
- arrayOf(colorDrawable, underlay, selectable, borderDrawable)
- } else {
- arrayOf(colorDrawable, underlay, borderDrawable)
- },
- )
- background = layerDrawable
- }
-
fun updateBackground() {
if (!needBackgroundUpdate) {
return
}
needBackgroundUpdate = false
- if (buttonBackgroundColor == Color.TRANSPARENT) {
- // reset background
- background = null
- }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- // reset foreground
foreground = null
}
val selectable = createSelectableDrawable()
- val borderDrawable = createBorderDrawable()
val underlay = createUnderlayDrawable()
underlayDrawable = underlay
+ // Set this view as callback so ObjectAnimator alpha changes trigger redraws.
+ underlay.callback = this
- if (hasBorderRadii && selectable is RippleDrawable) {
- val mask = PaintDrawable(Color.WHITE)
- mask.setCornerRadii(buildBorderRadii())
- selectable.setDrawableByLayerId(android.R.id.mask, mask)
- }
-
- if (useDrawableOnForeground && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (useDrawableOnForeground && selectable != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ // Explicit foreground mode — View natively forwards state/hotspot.
foreground = selectable
- if (buttonBackgroundColor != Color.TRANSPARENT) {
- updateBackgroundColor(buttonBackgroundColor, underlay, borderDrawable, null)
- }
- } else if (buttonBackgroundColor == Color.TRANSPARENT && rippleColor == null) {
- background = LayerDrawable(arrayOf(underlay, selectable, borderDrawable))
+ selectableDrawable = null
} else {
- updateBackgroundColor(buttonBackgroundColor, underlay, borderDrawable, selectable)
+ // Default — draw ripple in dispatchDraw above background, below children.
+ // State and hotspot are forwarded manually.
+ selectableDrawable = selectable
+ selectable?.callback = this
}
applyStartAnimationState()
}
- private fun createBorderDrawable(): Drawable {
- val borderDrawable = PaintDrawable(Color.TRANSPARENT)
-
- if (hasBorderRadii) {
- borderDrawable.setCornerRadii(buildBorderRadii())
+ // Draw the underlay and ripple between background and children.
+ // Clip to BackgroundStyleApplicator's padding box so the overlay
+ // never extends beyond the view's resolved border-radius shape.
+ override fun dispatchDraw(canvas: Canvas) {
+ val hasOverlay = underlayDrawable != null || selectableDrawable != null
+ if (hasOverlay) {
+ canvas.save()
+ BackgroundStyleApplicator.clipToPaddingBox(this, canvas)
+ }
+ underlayDrawable?.let {
+ it.setBounds(0, 0, width, height)
+ it.draw(canvas)
+ }
+ selectableDrawable?.let {
+ it.setBounds(0, 0, width, height)
+ it.draw(canvas)
}
+ if (hasOverlay) {
+ canvas.restore()
+ }
+ super.dispatchDraw(canvas)
+ }
- if (borderWidth > 0f) {
- borderDrawable.paint.apply {
- style = Paint.Style.STROKE
- strokeWidth = borderWidth
- color = borderColor ?: Color.BLACK
- pathEffect = buildBorderStyle()
+ override fun verifyDrawable(who: Drawable): Boolean =
+ super.verifyDrawable(who) || who == underlayDrawable || who == selectableDrawable
+
+ override fun drawableStateChanged() {
+ super.drawableStateChanged()
+ // Forward pressed/enabled state to the ripple when it's drawn manually.
+ selectableDrawable?.let {
+ if (it.isStateful) {
+ it.setState(drawableState)
}
}
-
- return borderDrawable
}
private fun createSelectableDrawable(): Drawable? {
@@ -563,6 +626,12 @@ class RNGestureHandlerButtonViewManager :
return drawable
}
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ needBackgroundUpdate = true
+ updateBackground()
+ }
+
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
// No-op
}
@@ -570,6 +639,8 @@ class RNGestureHandlerButtonViewManager :
override fun drawableHotspotChanged(x: Float, y: Float) {
if (touchResponder == null || touchResponder === this) {
super.drawableHotspotChanged(x, y)
+ // Forward hotspot to the ripple when it's drawn manually.
+ selectableDrawable?.setHotspot(x, y)
}
}
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
deleted file mode 100644
index 7137243bd7..0000000000
--- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonWrapperViewManager.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-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/apple/RNGestureHandlerButton.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h
index e421de01b8..2672d0b37a 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.h
@@ -24,7 +24,6 @@
* Insets used when hit testing inside this view.
*/
@property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;
-@property (nonatomic, assign) CGFloat borderRadius;
@property (nonatomic) BOOL userEnabled;
@property (nonatomic, assign) RNGestureHandlerPointerEvents pointerEvents;
@@ -49,6 +48,26 @@
*/
- (void)applyStartAnimationState;
+/**
+ * Updates the underlay layer's corner radii with separate horizontal/vertical
+ * components per corner, supporting elliptical inner corners when border widths
+ * are uneven. Handles both uniform and per-corner (CAShapeLayer mask) cases.
+ */
+- (void)setUnderlayCornerRadiiWithTopLeftHorizontal:(CGFloat)topLeftHorizontal
+ topLeftVertical:(CGFloat)topLeftVertical
+ topRightHorizontal:(CGFloat)topRightHorizontal
+ topRightVertical:(CGFloat)topRightVertical
+ bottomLeftHorizontal:(CGFloat)bottomLeftHorizontal
+ bottomLeftVertical:(CGFloat)bottomLeftVertical
+ bottomRightHorizontal:(CGFloat)bottomRightHorizontal
+ bottomRightVertical:(CGFloat)bottomRightVertical;
+
+/**
+ * Sets the border insets so the underlay is clipped to the padding box
+ * (inside the border).
+ */
+- (void)setUnderlayBorderInsetsWithTop:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom left:(CGFloat)left;
+
#if TARGET_OS_OSX
- (void)mountChildComponentView:(RNGHUIView *)childComponentView index:(NSInteger)index;
- (void)unmountChildComponentView:(RNGHUIView *)childComponentView index:(NSInteger)index;
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm
index ad06c02617..078e543056 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm
@@ -40,8 +40,10 @@
* controlling the touch flow.
*/
@implementation RNGestureHandlerButton {
- CALayer *_underlayLayer;
BOOL _isTouchInsideBounds;
+ CALayer *_underlayLayer;
+ CGFloat _underlayCornerRadii[8]; // [tlH, tlV, trH, trV, blH, blV, brH, brV] outer radii in points
+ UIEdgeInsets _underlayBorderInsets; // border widths for padding-box inset
}
- (void)commonInit
@@ -100,21 +102,28 @@ - (void)setUnderlayColor:(RNGHColor *)underlayColor
_underlayLayer.backgroundColor = underlayColor.CGColor;
}
+#if TARGET_OS_OSX
+// Flip the macOS coordinate system so y=0 is at the top, matching iOS
+// and React Native's layout expectations.
+- (BOOL)isFlipped
+{
+ return YES;
+}
+#endif
+
#if !TARGET_OS_OSX
- (void)layoutSubviews
{
[super layoutSubviews];
- _underlayLayer.frame = self.bounds;
- [self.layer insertSublayer:_underlayLayer atIndex:0];
-}
#else
- (void)layout
{
[super layout];
- _underlayLayer.frame = self.bounds;
+#endif
+ _underlayLayer.frame = UIEdgeInsetsInsetRect(self.bounds, _underlayBorderInsets);
[self.layer insertSublayer:_underlayLayer atIndex:0];
+ [self applyUnderlayCornerRadii];
}
-#endif
- (BOOL)shouldHandleTouch:(RNGHUIView *)view
{
@@ -243,6 +252,208 @@ - (void)handleAnimatePressOut
}
}
+- (void)applyUnderlayCornerRadii
+{
+ CGRect rect = _underlayLayer.bounds;
+ CGFloat w = rect.size.width;
+ CGFloat h = rect.size.height;
+
+ const CGFloat *outerTL = &_underlayCornerRadii[0];
+ const CGFloat *outerTR = &_underlayCornerRadii[2];
+ const CGFloat *outerBL = &_underlayCornerRadii[4];
+ const CGFloat *outerBR = &_underlayCornerRadii[6];
+ CGFloat borderTop = _underlayBorderInsets.top;
+ CGFloat borderBottom = _underlayBorderInsets.bottom;
+ CGFloat borderLeft = _underlayBorderInsets.left;
+ CGFloat borderRight = _underlayBorderInsets.right;
+
+ // Inner border radii: outer radius minus adjacent border width per axis, clamped to 0.
+ CGFloat topLeftHorizontal = MAX(0, outerTL[0] - borderLeft);
+ CGFloat topLeftVertical = MAX(0, outerTL[1] - borderTop);
+ CGFloat topRightHorizontal = MAX(0, outerTR[0] - borderRight);
+ CGFloat topRightVertical = MAX(0, outerTR[1] - borderTop);
+ CGFloat bottomLeftHorizontal = MAX(0, outerBL[0] - borderLeft);
+ CGFloat bottomLeftVertical = MAX(0, outerBL[1] - borderBottom);
+ CGFloat bottomRightHorizontal = MAX(0, outerBR[0] - borderRight);
+ CGFloat bottomRightVertical = MAX(0, outerBR[1] - borderBottom);
+
+ // CSS border-radius proportional scaling: if adjacent radii on any edge
+ // exceed that edge's length, scale all radii down by the same factor.
+ CGFloat f = 1.0;
+ if (topLeftHorizontal + topRightHorizontal > 0)
+ f = MIN(f, w / (topLeftHorizontal + topRightHorizontal));
+ if (bottomLeftHorizontal + bottomRightHorizontal > 0)
+ f = MIN(f, w / (bottomLeftHorizontal + bottomRightHorizontal));
+ if (topLeftVertical + bottomLeftVertical > 0)
+ f = MIN(f, h / (topLeftVertical + bottomLeftVertical));
+ if (topRightVertical + bottomRightVertical > 0)
+ f = MIN(f, h / (topRightVertical + bottomRightVertical));
+
+ if (f < 1.0) {
+ topLeftHorizontal *= f;
+ topLeftVertical *= f;
+ topRightHorizontal *= f;
+ topRightVertical *= f;
+ bottomLeftHorizontal *= f;
+ bottomLeftVertical *= f;
+ bottomRightHorizontal *= f;
+ bottomRightVertical *= f;
+ }
+
+ // Snap sub-pixel radii to 0 to avoid degenerate curves that cause
+ // anti-aliasing artifacts at what should be sharp corners.
+ if (topLeftHorizontal < 0.5)
+ topLeftHorizontal = 0;
+ if (topLeftVertical < 0.5)
+ topLeftVertical = 0;
+ if (topRightHorizontal < 0.5)
+ topRightHorizontal = 0;
+ if (topRightVertical < 0.5)
+ topRightVertical = 0;
+ if (bottomLeftHorizontal < 0.5)
+ bottomLeftHorizontal = 0;
+ if (bottomLeftVertical < 0.5)
+ bottomLeftVertical = 0;
+ if (bottomRightHorizontal < 0.5)
+ bottomRightHorizontal = 0;
+ if (bottomRightVertical < 0.5)
+ bottomRightVertical = 0;
+
+ if (topLeftHorizontal == 0 && topLeftVertical == 0 && topRightHorizontal == 0 && topRightVertical == 0 &&
+ bottomLeftHorizontal == 0 && bottomLeftVertical == 0 && bottomRightHorizontal == 0 && bottomRightVertical == 0) {
+ _underlayLayer.cornerRadius = 0;
+ _underlayLayer.mask = nil;
+ return;
+ }
+
+ // Uniform circular — simple cornerRadius is enough
+ if (topLeftHorizontal == topLeftVertical && topRightHorizontal == topRightVertical &&
+ bottomLeftHorizontal == bottomLeftVertical && bottomRightHorizontal == bottomRightVertical &&
+ topLeftHorizontal == topRightHorizontal && topRightHorizontal == bottomLeftHorizontal &&
+ bottomLeftHorizontal == bottomRightHorizontal) {
+ _underlayLayer.cornerRadius = topLeftHorizontal;
+ _underlayLayer.mask = nil;
+ return;
+ }
+
+ // Non-uniform or elliptical — build a CAShapeLayer mask using cubic
+ // Bezier approximation for quarter-ellipse arcs (kappa ≈ 0.5523).
+ _underlayLayer.cornerRadius = 0;
+ if (CGRectIsEmpty(rect)) {
+ return;
+ }
+
+ CGMutablePathRef path = CGPathCreateMutable();
+ const CGFloat k = 0.5522847498;
+ BOOL hasTL = topLeftHorizontal > 0 && topLeftVertical > 0;
+ BOOL hasTR = topRightHorizontal > 0 && topRightVertical > 0;
+ BOOL hasBR = bottomRightHorizontal > 0 && bottomRightVertical > 0;
+ BOOL hasBL = bottomLeftHorizontal > 0 && bottomLeftVertical > 0;
+
+ // Start at the top edge (after top-left corner if rounded)
+ CGPathMoveToPoint(path, NULL, hasTL ? topLeftHorizontal : 0, 0);
+
+ // Top edge → top-right corner
+ if (hasTR) {
+ CGPathAddLineToPoint(path, NULL, w - topRightHorizontal, 0);
+ CGPathAddCurveToPoint(
+ path,
+ NULL,
+ w - topRightHorizontal + topRightHorizontal * k,
+ 0,
+ w,
+ topRightVertical - topRightVertical * k,
+ w,
+ topRightVertical);
+ } else {
+ CGPathAddLineToPoint(path, NULL, w, 0);
+ }
+
+ // Right edge → bottom-right corner
+ if (hasBR) {
+ CGPathAddLineToPoint(path, NULL, w, h - bottomRightVertical);
+ CGPathAddCurveToPoint(
+ path,
+ NULL,
+ w,
+ h - bottomRightVertical + bottomRightVertical * k,
+ w - bottomRightHorizontal + bottomRightHorizontal * k,
+ h,
+ w - bottomRightHorizontal,
+ h);
+ } else {
+ CGPathAddLineToPoint(path, NULL, w, h);
+ }
+
+ // Bottom edge → bottom-left corner
+ if (hasBL) {
+ CGPathAddLineToPoint(path, NULL, bottomLeftHorizontal, h);
+ CGPathAddCurveToPoint(
+ path,
+ NULL,
+ bottomLeftHorizontal - bottomLeftHorizontal * k,
+ h,
+ 0,
+ h - bottomLeftVertical + bottomLeftVertical * k,
+ 0,
+ h - bottomLeftVertical);
+ } else {
+ CGPathAddLineToPoint(path, NULL, 0, h);
+ }
+
+ // Left edge → top-left corner
+ if (hasTL) {
+ CGPathAddLineToPoint(path, NULL, 0, topLeftVertical);
+ CGPathAddCurveToPoint(
+ path,
+ NULL,
+ 0,
+ topLeftVertical - topLeftVertical * k,
+ topLeftHorizontal - topLeftHorizontal * k,
+ 0,
+ topLeftHorizontal,
+ 0);
+ }
+ // closePath returns to the start point — (0,0) if no TL curve
+
+ CGPathCloseSubpath(path);
+
+ CAShapeLayer *mask = [CAShapeLayer new];
+ mask.path = path;
+ CGPathRelease(path);
+ _underlayLayer.mask = mask;
+}
+
+- (void)setUnderlayCornerRadiiWithTopLeftHorizontal:(CGFloat)topLeftHorizontal
+ topLeftVertical:(CGFloat)topLeftVertical
+ topRightHorizontal:(CGFloat)topRightHorizontal
+ topRightVertical:(CGFloat)topRightVertical
+ bottomLeftHorizontal:(CGFloat)bottomLeftHorizontal
+ bottomLeftVertical:(CGFloat)bottomLeftVertical
+ bottomRightHorizontal:(CGFloat)bottomRightHorizontal
+ bottomRightVertical:(CGFloat)bottomRightVertical
+{
+ _underlayCornerRadii[0] = topLeftHorizontal;
+ _underlayCornerRadii[1] = topLeftVertical;
+ _underlayCornerRadii[2] = topRightHorizontal;
+ _underlayCornerRadii[3] = topRightVertical;
+ _underlayCornerRadii[4] = bottomLeftHorizontal;
+ _underlayCornerRadii[5] = bottomLeftVertical;
+ _underlayCornerRadii[6] = bottomRightHorizontal;
+ _underlayCornerRadii[7] = bottomRightVertical;
+ [self applyUnderlayCornerRadii];
+}
+
+- (void)setUnderlayBorderInsetsWithTop:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom left:(CGFloat)left
+{
+ _underlayBorderInsets = UIEdgeInsetsMake(top, left, bottom, right);
+#if !TARGET_OS_OSX
+ [self setNeedsLayout];
+#else
+ [self setNeedsLayout:YES];
+#endif
+}
+
#if TARGET_OS_OSX
- (void)mouseDown:(NSEvent *)event
{
@@ -367,30 +578,6 @@ - (RNGHUIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
return inner;
}
-- (void)setBorderRadius:(CGFloat)radius
-{
- if (_borderRadius == radius) {
- return;
- }
-
- _borderRadius = radius;
- [self.layer setNeedsDisplay];
-}
-
-- (void)displayLayer:(CALayer *)layer
-{
- if (CGSizeEqualToSize(layer.bounds.size, CGSizeZero)) {
- return;
- }
-
- const CGFloat radius = MAX(0, _borderRadius);
- const CGSize size = self.bounds.size;
- const CGFloat scaleFactor = RCTZeroIfNaN(MIN(1, size.width / (2 * radius)));
- const CGFloat currentBorderRadius = radius * scaleFactor;
- layer.cornerRadius = currentBorderRadius;
- _underlayLayer.cornerRadius = currentBorderRadius;
-}
-
- (NSString *)accessibilityLabel
{
NSString *label = super.accessibilityLabel;
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm
index a87ca71a71..e840cfb296 100644
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm
+++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonComponentView.mm
@@ -7,6 +7,7 @@
#import
#import
#import
+#import
#import
#import "RNGestureHandlerButton.h"
@@ -56,6 +57,7 @@ - (instancetype)initWithFrame:(CGRect)frame
static const auto defaultProps = std::make_shared();
_props = defaultProps;
_buttonView = [[RNGestureHandlerButton alloc] initWithFrame:self.bounds];
+ _buttonView.animationTarget = self;
self.contentView = _buttonView;
}
@@ -63,26 +65,6 @@ - (instancetype)initWithFrame:(CGRect)frame
return self;
}
-#if !TARGET_OS_OSX
-- (void)willMoveToSuperview:(RNGHUIView *)newSuperview
-{
- [super willMoveToSuperview:newSuperview];
- _buttonView.animationTarget = newSuperview;
- if (newSuperview != nil) {
- [_buttonView applyStartAnimationState];
- }
-}
-#else
-- (void)viewWillMoveToSuperview:(RNGHUIView *)newSuperview
-{
- [super viewWillMoveToSuperview:newSuperview];
- _buttonView.animationTarget = newSuperview;
- if (newSuperview != nil) {
- [_buttonView applyStartAnimationState];
- }
-}
-#endif
-
- (void)mountChildComponentView:(RNGHUIView *)childComponentView index:(NSInteger)index
{
[_buttonView mountChildComponentView:childComponentView index:index];
@@ -128,6 +110,27 @@ - (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetric
[_buttonView updateLayoutMetrics:buttonMetrics oldLayoutMetrics:oldbuttonMetrics];
}
+- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
+{
+ [super finalizeUpdates:updateMask];
+
+ // Resolve per-corner border radii from props and forward to the button
+ // so its underlay CALayer gets the matching shape.
+ const auto borderMetrics = _props->resolveBorderMetrics(_layoutMetrics);
+ [_buttonView setUnderlayCornerRadiiWithTopLeftHorizontal:borderMetrics.borderRadii.topLeft.horizontal
+ topLeftVertical:borderMetrics.borderRadii.topLeft.vertical
+ topRightHorizontal:borderMetrics.borderRadii.topRight.horizontal
+ topRightVertical:borderMetrics.borderRadii.topRight.vertical
+ bottomLeftHorizontal:borderMetrics.borderRadii.bottomLeft.horizontal
+ bottomLeftVertical:borderMetrics.borderRadii.bottomLeft.vertical
+ bottomRightHorizontal:borderMetrics.borderRadii.bottomRight.horizontal
+ bottomRightVertical:borderMetrics.borderRadii.bottomRight.vertical];
+ [_buttonView setUnderlayBorderInsetsWithTop:borderMetrics.borderWidths.top
+ right:borderMetrics.borderWidths.right
+ bottom:borderMetrics.borderWidths.bottom
+ left:borderMetrics.borderWidths.left];
+}
+
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
@@ -268,9 +271,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
}
[super updateProps:props oldProps:oldProps];
- if (_buttonView.animationTarget != nil) {
- [_buttonView applyStartAnimationState];
- }
+ [_buttonView applyStartAnimationState];
}
#if !TARGET_OS_OSX
diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.h
deleted file mode 100644
index c94ecf946e..0000000000
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#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
deleted file mode 100644
index 526c7743d6..0000000000
--- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButtonWrapper.mm
+++ /dev/null
@@ -1,46 +0,0 @@
-#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/package.json b/packages/react-native-gesture-handler/package.json
index 3d11ad594b..da5575a62a 100644
--- a/packages/react-native-gesture-handler/package.json
+++ b/packages/react-native-gesture-handler/package.json
@@ -129,8 +129,7 @@
"ios": {
"componentProvider": {
"RNGestureHandlerButton": "RNGestureHandlerButtonComponentView",
- "RNGestureHandlerDetector": "RNGestureHandlerDetector",
- "RNGestureHandlerButtonWrapper": "RNGestureHandlerButtonWrapper"
+ "RNGestureHandlerDetector": "RNGestureHandlerDetector"
}
}
},
diff --git a/packages/react-native-gesture-handler/react-native.config.js b/packages/react-native-gesture-handler/react-native.config.js
index 46e3de4682..d19502f02d 100644
--- a/packages/react-native-gesture-handler/react-native.config.js
+++ b/packages/react-native-gesture-handler/react-native.config.js
@@ -4,7 +4,6 @@ module.exports = {
android: {
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 5621db20e7..9ca21c682c 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,7 +15,6 @@
#include
#include
-#include
#include
namespace facebook::react {
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
deleted file mode 100644
index c3323ae823..0000000000
--- a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperComponentDescriptor.h
+++ /dev/null
@@ -1,32 +0,0 @@
-
-/**
- * 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
deleted file mode 100644
index 682baf51f8..0000000000
--- a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-
-#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) {
- 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]);
-
- auto gradChildWithProtectedAccess =
- std::static_pointer_cast(
- grandChild);
-
- auto shouldSkipCustomLayout =
- !gradChildWithProtectedAccess->yogaNode_.getHasNewLayout();
-
- YogaLayoutableShadowNode::layout(layoutContext);
-
- child->ensureUnsealed();
- grandChild->ensureUnsealed();
-
- auto mutableChild = std::const_pointer_cast(child);
- auto mutableGrandChild =
- std::const_pointer_cast(grandChild);
-
- // The grand child node did not have its layout changed, we can reuse previous
- // values
- if (shouldSkipCustomLayout) {
- react_native_assert(previousGrandChildLayoutMetrics_.has_value());
- setLayoutMetrics(previousGrandChildLayoutMetrics_.value());
-
- auto metricsNoOrigin = previousGrandChildLayoutMetrics_.value();
- metricsNoOrigin.frame.origin = Point{};
-
- mutableChild->setLayoutMetrics(metricsNoOrigin);
- mutableGrandChild->setLayoutMetrics(metricsNoOrigin);
- return;
- }
-
- auto metrics = grandChild->getLayoutMetrics();
- previousGrandChildLayoutMetrics_ = metrics;
-
- setLayoutMetrics(metrics);
-
- auto metricsNoOrigin = grandChild->getLayoutMetrics();
- metricsNoOrigin.frame.origin = Point{};
-
- mutableChild->setLayoutMetrics(metricsNoOrigin);
- 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
deleted file mode 100644
index f04021c054..0000000000
--- a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperShadowNode.h
+++ /dev/null
@@ -1,56 +0,0 @@
-#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) {
- const auto &sourceWrapperNode =
- static_cast(
- sourceShadowNode);
- previousGrandChildLayoutMetrics_ =
- sourceWrapperNode.previousGrandChildLayoutMetrics_;
-
- initialize();
- }
-
- void layout(LayoutContext layoutContext) override;
- void appendChild(const std::shared_ptr &child) override;
-
- private:
- void initialize();
- void prepareChildren();
-
- std::optional previousGrandChildLayoutMetrics_;
-};
-
-} // 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
deleted file mode 100644
index 7d9fa242e0..0000000000
--- a/packages/react-native-gesture-handler/shared/shadowNodes/react/renderer/components/rngesturehandler_codegen/RNGestureHandlerButtonWrapperState.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * 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/GestureButtons.tsx b/packages/react-native-gesture-handler/src/components/GestureButtons.tsx
index 2832752605..238e2ff671 100644
--- a/packages/react-native-gesture-handler/src/components/GestureButtons.tsx
+++ b/packages/react-native-gesture-handler/src/components/GestureButtons.tsx
@@ -19,12 +19,13 @@ import type {
LegacyBorderlessButtonProps,
LegacyRawButtonProps,
} from './GestureButtonsProps';
+import type { HostComponent } from 'react-native';
/**
* @deprecated use `RawButton` instead
*/
export const LegacyRawButton = createNativeWrapper(
- GestureHandlerButton,
+ GestureHandlerButton as unknown as HostComponent,
{
shouldCancelWhenOutside: false,
shouldActivateOnStart: false,
diff --git a/packages/react-native-gesture-handler/src/components/GestureHandlerButton.tsx b/packages/react-native-gesture-handler/src/components/GestureHandlerButton.tsx
index 30ea4c0404..7e13963721 100644
--- a/packages/react-native-gesture-handler/src/components/GestureHandlerButton.tsx
+++ b/packages/react-native-gesture-handler/src/components/GestureHandlerButton.tsx
@@ -4,17 +4,10 @@ import {
HostComponent,
LayoutChangeEvent,
StyleProp,
- StyleSheet,
- View,
ViewProps,
ViewStyle,
} from 'react-native';
import RNGestureHandlerButtonNativeComponent from '../specs/RNGestureHandlerButtonNativeComponent';
-import RNGestureHandlerButtonWrapperNativeComponent from '../specs/RNGestureHandlerButtonWrapperNativeComponent';
-import { useMemo } from 'react';
-
-export const ButtonComponent =
- RNGestureHandlerButtonNativeComponent as HostComponent;
export interface ButtonProps extends ViewProps, AccessibilityProps {
children?: React.ReactNode;
@@ -144,150 +137,7 @@ export interface ButtonProps extends ViewProps, AccessibilityProps {
testOnly_onLongPress?: Function | null | undefined;
}
-export default function GestureHandlerButton({ style, ...rest }: ButtonProps) {
- 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]
- );
-
- const { defaultOpacity, defaultScale } = rest;
-
- const buttonRestingStyle = useMemo(
- (): ViewStyle => ({
- opacity: defaultOpacity,
- transform:
- defaultScale !== undefined ? [{ scale: defaultScale }] : undefined,
- }),
- [defaultOpacity, defaultScale]
- );
-
- return (
-
-
-
-
-
- );
-}
+export const ButtonComponent =
+ RNGestureHandlerButtonNativeComponent as HostComponent;
-const styles = StyleSheet.create({
- contents: {
- display: 'contents',
- },
- overflowHidden: {
- overflow: 'hidden',
- },
-});
+export default ButtonComponent;
diff --git a/packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonNativeComponent.ts b/packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonNativeComponent.ts
index 47d37f37cf..6ab8bcaca7 100644
--- a/packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonNativeComponent.ts
+++ b/packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonNativeComponent.ts
@@ -15,9 +15,6 @@ interface NativeProps extends ViewProps {
rippleColor?: ColorValue;
rippleRadius?: Int32;
touchSoundDisabled?: WithDefault;
- borderWidth?: Float;
- borderColor?: ColorValue;
- borderStyle?: WithDefault;
pointerEvents?: WithDefault<
'box-none' | 'none' | 'box-only' | 'auto',
'auto'
@@ -30,6 +27,43 @@ interface NativeProps extends ViewProps {
defaultScale?: WithDefault;
defaultUnderlayOpacity?: WithDefault;
underlayColor?: ColorValue;
+
+ // Border style
+ borderWidth?: Float;
+ borderColor?: ColorValue;
+ borderStyle?: WithDefault;
+ overflow?: WithDefault;
+
+ // Border width per-edge
+ borderLeftWidth?: Float;
+ borderRightWidth?: Float;
+ borderTopWidth?: Float;
+ borderBottomWidth?: Float;
+ borderStartWidth?: Float;
+ borderEndWidth?: Float;
+
+ // Border color per-edge
+ borderLeftColor?: ColorValue;
+ borderRightColor?: ColorValue;
+ borderTopColor?: ColorValue;
+ borderBottomColor?: ColorValue;
+ borderStartColor?: ColorValue;
+ borderEndColor?: ColorValue;
+ borderBlockColor?: ColorValue;
+ borderBlockEndColor?: ColorValue;
+ borderBlockStartColor?: ColorValue;
+
+ // Border radius — logical variants beyond what ViewProps provides
+ // WithDefault -1 so the codegen sends -1 (our "unset" sentinel) instead of 0
+ // when the prop is absent, letting physical / general radii take effect.
+ borderTopStartRadius?: WithDefault;
+ borderTopEndRadius?: WithDefault;
+ borderBottomStartRadius?: WithDefault;
+ borderBottomEndRadius?: WithDefault;
+ borderEndEndRadius?: WithDefault;
+ borderEndStartRadius?: WithDefault;
+ borderStartEndRadius?: WithDefault;
+ borderStartStartRadius?: WithDefault;
}
export default codegenNativeComponent('RNGestureHandlerButton');
diff --git a/packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonWrapperNativeComponent.ts b/packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonWrapperNativeComponent.ts
deleted file mode 100644
index d84e610aaa..0000000000
--- a/packages/react-native-gesture-handler/src/specs/RNGestureHandlerButtonWrapperNativeComponent.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
-import type { ViewProps } from 'react-native';
-
-interface NativeProps extends ViewProps {}
-
-export default codegenNativeComponent(
- 'RNGestureHandlerButtonWrapper',
- {
- interfaceOnly: true,
- }
-);