Skip to content

Commit 7f90209

Browse files
authored
Fix android ripple color bug on fabric (#3369)
## Description Android ripple currently does not work on `Fabric`, as all values passed to `RawButton` and it's inheritors are passed through `processColor`, which is crucial on `Paper`, and broken on `Fabric`. This PR disables usage of `processColor`, when running on `Fabric`. Note: `isFabric` cannot be moved out of the body of the `Pressable`, as it likely won't be initialised before the `Pressable` starts being rendered. More details [here](https://github.com/software-mansion/react-native-gesture-handler/blob/b3d8fd91dca195267bdc33aa20fd6f5cd37d7fe2/src/utils.ts#L47). Fixes #3246 Fixes #3312 Supersedes #3250 Likely could add a workaround for software-mansion/react-native-reanimated#6935 ## Test plan <details> <summary> Collapsed test code </summary> ```tsx import React from 'react'; import { Pressable as RNPressable, StyleSheet, Text } from 'react-native'; import { BaseButton, GestureHandlerRootView, RectButton, Pressable, } from 'react-native-gesture-handler'; const App = () => { return ( <GestureHandlerRootView> <RectButton style={styles.wrapperCustom} rippleColor={'blue'}> <Text style={styles.text}>RectButton</Text> </RectButton> <BaseButton style={styles.wrapperCustom} rippleColor={'blue'}> <Text style={styles.text}>BaseButton</Text> </BaseButton> <Pressable style={styles.wrapperCustom} android_ripple={{ color: 'blue' }}> {({ pressed }) => ( <Text style={styles.text}> {pressed ? 'Pressed!' : 'Pressable from react-native'} </Text> )} </Pressable> <RNPressable style={styles.wrapperCustom} android_ripple={{ color: 'blue' }}> {({ pressed }) => ( <Text style={styles.text}> {pressed ? 'Pressed!' : 'Pressable from react-native'} </Text> )} </RNPressable> <Pressable style={styles.wrapperCustom} android_ripple={{ color: '#00f' }}> {({ pressed }) => ( <Text style={styles.text}> {pressed ? 'Pressed!' : 'Pressable from react-native'} </Text> )} </Pressable> <RNPressable style={styles.wrapperCustom} android_ripple={{ color: '#00f' }}> {({ pressed }) => ( <Text style={styles.text}> {pressed ? 'Pressed!' : 'Pressable from react-native'} </Text> )} </RNPressable> </GestureHandlerRootView> ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', gap: 16, padding: 16, }, text: { fontSize: 32, }, wrapperCustom: { borderRadius: 8, padding: 6, flex: 1, backgroundColor: 'papayawhip', borderColor: 'red', borderWidth: 2, }, logBox: { flex: 1, padding: 20, margin: 10, borderWidth: StyleSheet.hairlineWidth, borderColor: '#f0f0f0', backgroundColor: '#f9f9f9', }, }); export default App; ``` </details>
1 parent 1e21262 commit 7f90209

File tree

3 files changed

+36
-8
lines changed

3 files changed

+36
-8
lines changed

src/components/GestureButtons.tsx

+13-2
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ import type {
1818
BorderlessButtonWithRefProps,
1919
BorderlessButtonProps,
2020
} from './GestureButtonsProps';
21+
import { isFabric } from '../utils';
2122

2223
export const RawButton = createNativeWrapper(GestureHandlerButton, {
2324
shouldCancelWhenOutside: false,
2425
shouldActivateOnStart: false,
2526
});
2627

28+
let IS_FABRIC: null | boolean = null;
29+
2730
class InnerBaseButton extends React.Component<BaseButtonWithRefProps> {
2831
static defaultProps = {
2932
delayLongPress: 600,
@@ -120,12 +123,20 @@ class InnerBaseButton extends React.Component<BaseButtonWithRefProps> {
120123
};
121124

122125
render() {
123-
const { rippleColor, style, ...rest } = this.props;
126+
const { rippleColor: unprocessedRippleColor, style, ...rest } = this.props;
127+
128+
if (IS_FABRIC === null) {
129+
IS_FABRIC = isFabric();
130+
}
131+
132+
const rippleColor = IS_FABRIC
133+
? unprocessedRippleColor
134+
: processColor(unprocessedRippleColor ?? undefined);
124135

125136
return (
126137
<RawButton
127138
ref={this.props.innerRef}
128-
rippleColor={processColor(rippleColor)}
139+
rippleColor={rippleColor}
129140
style={[style, Platform.OS === 'ios' && { cursor: undefined }]}
130141
{...rest}
131142
onGestureEvent={this.onGestureEvent}

src/components/GestureButtonsProps.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import * as React from 'react';
2-
import { AccessibilityProps, StyleProp, ViewStyle } from 'react-native';
2+
import {
3+
AccessibilityProps,
4+
ColorValue,
5+
StyleProp,
6+
ViewStyle,
7+
} from 'react-native';
38
import type { NativeViewGestureHandlerProps } from '../handlers/NativeViewGestureHandler';
49

510
export interface RawButtonProps
@@ -16,7 +21,7 @@ export interface RawButtonProps
1621
*
1722
* Defines color of native ripple animation used since API level 21.
1823
*/
19-
rippleColor?: any; // it was present in BaseButtonProps before but is used here in code
24+
rippleColor?: number | ColorValue | null;
2025

2126
/**
2227
* Android only.

src/components/Pressable/Pressable.tsx

+16-4
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ import {
2020
} from './utils';
2121
import { PressabilityDebugView } from '../../handlers/PressabilityDebugView';
2222
import { GestureTouchEvent } from '../../handlers/gestureHandlerCommon';
23-
import { INT32_MAX, isTestEnv } from '../../utils';
23+
import { INT32_MAX, isFabric, isTestEnv } from '../../utils';
2424

2525
const DEFAULT_LONG_PRESS_DURATION = 500;
2626
const IS_TEST_ENV = isTestEnv();
2727

28+
let IS_FABRIC: null | boolean = null;
29+
2830
export default function Pressable(props: PressableProps) {
2931
const {
3032
testOnly_pressed,
@@ -367,8 +369,6 @@ export default function Pressable(props: PressableProps) {
367369

368370
const gesture = Gesture.Simultaneous(...gestures);
369371

370-
const defaultRippleColor = android_ripple ? undefined : 'transparent';
371-
372372
// `cursor: 'pointer'` on `RNButton` crashes iOS
373373
const pointerStyle: StyleProp<ViewStyle> =
374374
Platform.OS === 'web' ? { cursor: 'pointer' } : {};
@@ -381,6 +381,18 @@ export default function Pressable(props: PressableProps) {
381381
? children({ pressed: pressedState })
382382
: children;
383383

384+
const rippleColor = useMemo(() => {
385+
if (IS_FABRIC === null) {
386+
IS_FABRIC = isFabric();
387+
}
388+
389+
const defaultRippleColor = android_ripple ? undefined : 'transparent';
390+
const unprocessedRippleColor = android_ripple?.color ?? defaultRippleColor;
391+
return IS_FABRIC
392+
? unprocessedRippleColor
393+
: processColor(unprocessedRippleColor);
394+
}, [android_ripple]);
395+
384396
return (
385397
<GestureDetector gesture={gesture}>
386398
<NativeButton
@@ -389,7 +401,7 @@ export default function Pressable(props: PressableProps) {
389401
hitSlop={appliedHitSlop}
390402
enabled={isPressableEnabled}
391403
touchSoundDisabled={android_disableSound ?? undefined}
392-
rippleColor={processColor(android_ripple?.color ?? defaultRippleColor)}
404+
rippleColor={rippleColor}
393405
rippleRadius={android_ripple?.radius ?? undefined}
394406
style={[pointerStyle, styleProp]}
395407
testOnly_onPress={IS_TEST_ENV ? onPress : undefined}

0 commit comments

Comments
 (0)