Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix inner styling of Pressable #2982

Merged
merged 30 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
734c429
add flex styling example
latekvo Jul 4, 2024
923b95c
add initial inner style extraction
latekvo Jul 4, 2024
aa493dd
split styleProp into inner and outer styles
latekvo Jul 4, 2024
7aa9566
update example
latekvo Jul 4, 2024
95d06cd
add custom type utility
latekvo Jul 4, 2024
b83faa9
fix babel import/export errors
latekvo Jul 4, 2024
c848804
[unsafe] initial working version
latekvo Jul 4, 2024
4e26276
fixed insufficient outer styles deletion, fixed clarity, fixed most t…
latekvo Jul 4, 2024
a1f0f2c
move style manipulating functions to utils
latekvo Jul 4, 2024
9d4446a
change types to remove more errors
latekvo Jul 4, 2024
4c75fa7
flatten styles, remove web issues
latekvo Jul 5, 2024
6af3cd1
remove pressable import error, fix types
latekvo Jul 5, 2024
9f471cc
fix typing issues on extractStyles
latekvo Jul 5, 2024
36f1a24
imporve variable names
latekvo Jul 5, 2024
4b799d1
remove temporary styling example
latekvo Jul 5, 2024
01348b4
remove unnecessary comment
latekvo Jul 5, 2024
4a7a00e
add source clarification for inner style keys
latekvo Jul 5, 2024
12a69f8
remove outer style from the inner style keys
latekvo Jul 5, 2024
f00a18a
convert for-of loops into function streams
latekvo Jul 5, 2024
c1933eb
Merge branch 'main' into @latekvo/fix-pressable-flex-styling
latekvo Jul 8, 2024
83efbed
apply style splitting change proposals
latekvo Jul 8, 2024
5595026
combine style splitting into a single function
latekvo Jul 8, 2024
259f0d3
fix dependency array lints
latekvo Jul 8, 2024
e348d8f
combine both inner and outer key separation into a single pass
latekvo Jul 8, 2024
9aa0c0f
remove unnecessary exports
latekvo Jul 8, 2024
f612444
Merge remote-tracking branch 'origin/main' into @latekvo/fix-pressabl…
latekvo Jul 8, 2024
b3278cc
remove unncessary Gesture flag
latekvo Jul 8, 2024
75d4109
change style splitting from streams to for loop
latekvo Jul 8, 2024
4846fd7
remove unnecessary type casts
latekvo Jul 9, 2024
60b77b9
remove unnecessary null check
latekvo Jul 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 63 additions & 49 deletions src/components/Pressable/Pressable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
isTouchWithinInset,
adaptTouchEvent,
addInsets,
splitStyles,
} from './utils';
import { PressabilityDebugView } from '../../handlers/PressabilityDebugView';
import { GestureTouchEvent } from '../../handlers/gestureHandlerCommon';
Expand All @@ -35,15 +36,21 @@ export default function Pressable(props: PressableProps) {
const isPressCallbackEnabled = useRef<boolean>(true);
const isPressedDown = useRef<boolean>(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(
() =>
Expand All @@ -53,7 +60,7 @@ export default function Pressable(props: PressableProps) {
isPressCallbackEnabled.current = false;
}
}),
[isPressCallbackEnabled, props.onLongPress, isPressedDown]
[props]
);

const hoverInTimeout = useRef<number | null>(null);
Expand Down Expand Up @@ -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<number | null>(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<boolean>(false);
const onEndHandlingTouchesDown = useRef<(() => void) | null>(null);
Expand Down Expand Up @@ -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,
]
);

Expand Down Expand Up @@ -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 (
<View style={styleProp}>
<View style={outerStyles}>
<GestureDetector gesture={gesture}>
<NativeButton
ref={pressableRef}
Expand All @@ -269,7 +283,7 @@ export default function Pressable(props: PressableProps) {
props.android_ripple?.color ?? defaultRippleColor
)}
rippleRadius={props.android_ripple?.radius ?? undefined}
style={[StyleSheet.absoluteFill, pointerStyle]}>
style={[StyleSheet.absoluteFill, pointerStyle, innerStyles]}>
{childrenProp}
{__DEV__ ? (
<PressabilityDebugView color="red" hitSlop={normalizedHitSlop} />
Expand Down
48 changes: 47 additions & 1 deletion src/components/Pressable/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Insets } from 'react-native';
import { Insets, ViewStyle } from 'react-native';
import { LongPressGestureHandlerEventPayload } from '../../handlers/GestureHandlerEventPayload';
import {
TouchData,
Expand Down Expand Up @@ -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<string, unknown> = {};
const innerStyles: Record<string, unknown> = {};

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,
Expand All @@ -127,4 +172,5 @@ export {
isTouchWithinInset,
adaptStateChangeEvent,
adaptTouchEvent,
splitStyles,
};
Loading