diff --git a/src/components/Pressable/Pressable.tsx b/src/components/Pressable/Pressable.tsx index 3cf15914b9..b9c91e3260 100644 --- a/src/components/Pressable/Pressable.tsx +++ b/src/components/Pressable/Pressable.tsx @@ -18,6 +18,7 @@ import { isTouchWithinInset, adaptTouchEvent, addInsets, + splitStyles, } from './utils'; import { PressabilityDebugView } from '../../handlers/PressabilityDebugView'; import { GestureTouchEvent } from '../../handlers/gestureHandlerCommon'; @@ -35,15 +36,21 @@ export default function Pressable(props: PressableProps) { const isPressCallbackEnabled = useRef(true); const isPressedDown = useRef(false); - const normalizedHitSlop: Insets = - typeof props.hitSlop === 'number' - ? numberAsInset(props.hitSlop) - : props.hitSlop ?? {}; + const normalizedHitSlop: Insets = useMemo( + () => + typeof props.hitSlop === 'number' + ? numberAsInset(props.hitSlop) + : props.hitSlop ?? {}, + [props.hitSlop] + ); - const normalizedPressRetentionOffset: Insets = - typeof props.pressRetentionOffset === 'number' - ? numberAsInset(props.pressRetentionOffset) - : props.pressRetentionOffset ?? {}; + const normalizedPressRetentionOffset: Insets = useMemo( + () => + typeof props.pressRetentionOffset === 'number' + ? numberAsInset(props.pressRetentionOffset) + : props.pressRetentionOffset ?? {}, + [props.pressRetentionOffset] + ); const pressGesture = useMemo( () => @@ -53,7 +60,7 @@ export default function Pressable(props: PressableProps) { isPressCallbackEnabled.current = false; } }), - [isPressCallbackEnabled, props.onLongPress, isPressedDown] + [props] ); const hoverInTimeout = useRef(null); @@ -88,41 +95,48 @@ export default function Pressable(props: PressableProps) { } props.onHoverOut?.(adaptStateChangeEvent(event)); }), - [props.onHoverIn, props.onHoverOut, props.delayHoverIn, props.delayHoverOut] + [props] ); const pressDelayTimeoutRef = useRef(null); - const pressInHandler = useCallback((event: GestureTouchEvent) => { - props.onPressIn?.(adaptTouchEvent(event)); - isPressCallbackEnabled.current = true; - pressDelayTimeoutRef.current = null; - setPressedState(true); - }, []); - const pressOutHandler = useCallback((event: GestureTouchEvent) => { - if ( - !isPressedDown.current || - event.allTouches.length > event.changedTouches.length - ) { - return; - } - - if (props.unstable_pressDelay && pressDelayTimeoutRef.current !== null) { - // When delay is preemptively finished by lifting touches, - // we want to immediately activate it's effects - pressInHandler, - // even though we are located at the pressOutHandler - clearTimeout(pressDelayTimeoutRef.current); - pressInHandler(event); - } - - props.onPressOut?.(adaptTouchEvent(event)); - - if (isPressCallbackEnabled.current) { - props.onPress?.(adaptTouchEvent(event)); - } + const pressInHandler = useCallback( + (event: GestureTouchEvent) => { + props.onPressIn?.(adaptTouchEvent(event)); + isPressCallbackEnabled.current = true; + pressDelayTimeoutRef.current = null; + setPressedState(true); + }, + [props] + ); - isPressedDown.current = false; - setPressedState(false); - }, []); + const pressOutHandler = useCallback( + (event: GestureTouchEvent) => { + if ( + !isPressedDown.current || + event.allTouches.length > event.changedTouches.length + ) { + return; + } + + if (props.unstable_pressDelay && pressDelayTimeoutRef.current !== null) { + // When delay is preemptively finished by lifting touches, + // we want to immediately activate it's effects - pressInHandler, + // even though we are located at the pressOutHandler + clearTimeout(pressDelayTimeoutRef.current); + pressInHandler(event); + } + + props.onPressOut?.(adaptTouchEvent(event)); + + if (isPressCallbackEnabled.current) { + props.onPress?.(adaptTouchEvent(event)); + } + + isPressedDown.current = false; + setPressedState(false); + }, + [pressInHandler, props] + ); const handlingOnTouchesDown = useRef(false); const onEndHandlingTouchesDown = useRef<(() => void) | null>(null); @@ -192,14 +206,10 @@ export default function Pressable(props: PressableProps) { pressOutHandler(event); }), [ - props.onPress, - props.onPressIn, - props.onPressOut, - setPressedState, - isPressedDown, - isPressCallbackEnabled, normalizedHitSlop, - pressDelayTimeoutRef, + props.unstable_pressDelay, + pressInHandler, + pressOutHandler, ] ); @@ -256,8 +266,12 @@ export default function Pressable(props: PressableProps) { ? props.children({ pressed: pressedState }) : props.children; + const flattenedStyles = StyleSheet.flatten(styleProp ?? {}); + + const [innerStyles, outerStyles] = splitStyles(flattenedStyles); + return ( - + + style={[StyleSheet.absoluteFill, pointerStyle, innerStyles]}> {childrenProp} {__DEV__ ? ( diff --git a/src/components/Pressable/utils.ts b/src/components/Pressable/utils.ts index f94f9dbafb..3b3342bc13 100644 --- a/src/components/Pressable/utils.ts +++ b/src/components/Pressable/utils.ts @@ -1,4 +1,4 @@ -import { Insets } from 'react-native'; +import { Insets, ViewStyle } from 'react-native'; import { LongPressGestureHandlerEventPayload } from '../../handlers/GestureHandlerEventPayload'; import { TouchData, @@ -119,6 +119,51 @@ const adaptTouchEvent = (event: GestureTouchEvent): PressableEvent => { }; }; +type StylePropKeys = (keyof ViewStyle)[]; + +// Source: +// - From ViewStyle extracted FlexStyle sub-interface which contains all of the box-model manipulating props. +// - From FlexStyle handpicked those styles, which act on the inner part of the box-model. +const innerStyleKeys = new Set([ + 'alignContent', + 'alignItems', + 'flexBasis', + 'flexDirection', + 'flexWrap', + 'rowGap', + 'gap', + 'columnGap', + 'justifyContent', + 'overflow', + 'padding', + 'paddingBottom', + 'paddingEnd', + 'paddingHorizontal', + 'paddingLeft', + 'paddingRight', + 'paddingStart', + 'paddingTop', + 'paddingVertical', + 'start', + 'end', + 'direction', // iOS only +] as StylePropKeys); + +const splitStyles = (from: ViewStyle): [ViewStyle, ViewStyle] => { + const outerStyles: Record = {}; + const innerStyles: Record = {}; + + for (const key in from) { + if (innerStyleKeys.has(key as keyof ViewStyle)) { + innerStyles[key] = from[key as keyof ViewStyle]; + } else { + outerStyles[key] = from[key as keyof ViewStyle]; + } + } + + return [innerStyles, outerStyles]; +}; + export { numberAsInset, addInsets, @@ -127,4 +172,5 @@ export { isTouchWithinInset, adaptStateChangeEvent, adaptTouchEvent, + splitStyles, };