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

Create gesturized pressable component #2942

Merged
merged 149 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
149 commits
Select commit Hold shift + click to select a range
50d69a9
initial pressable component
latekvo Jun 12, 2024
9bb949c
create package config for Pressable
latekvo Jun 13, 2024
4215df4
add styling to pressable
latekvo Jun 13, 2024
81fe6ce
add pressable example
latekvo Jun 13, 2024
1980e3c
improve example
latekvo Jun 13, 2024
0e5bb4e
add gesture tracking to pressable
latekvo Jun 13, 2024
dbaaff0
add support for all callbacks covered in docs
latekvo Jun 13, 2024
c4e1d7e
use all callbacks in the example
latekvo Jun 13, 2024
a2235ca
add delays, remove redundancy, remove old comments
latekvo Jun 13, 2024
8c84f3e
fix critical typos
latekvo Jun 13, 2024
ecc9bcc
add pressRetentionOffset support
latekvo Jun 13, 2024
ea53645
fix invalid gesture composition
latekvo Jun 13, 2024
91dff67
apply review suggestions
latekvo Jun 14, 2024
e0b1fcb
Merge branch 'main' into @latekvo/add_gesture_handler_pressable_imple…
latekvo Jun 14, 2024
5f274cb
make gestures run on JS
latekvo Jun 14, 2024
052351a
Merge branch '@latekvo/add_gesture_handler_pressable_implementation' …
latekvo Jun 14, 2024
626b346
remove Pressable inner package and fix config
latekvo Jun 14, 2024
2b96ebf
untangle circular deps
latekvo Jun 14, 2024
1514f63
move pressable interfaces to different file
latekvo Jun 14, 2024
efae47f
fix exports config
latekvo Jun 14, 2024
3b623f9
align onPressIn behaviour with original function
latekvo Jun 14, 2024
73ec123
pressIn checking now allows for multi touch
latekvo Jun 14, 2024
b7075fd
improve onPressOut handling
latekvo Jun 14, 2024
d8864fd
cleanup touchWithinBounds and comments
latekvo Jun 14, 2024
6fcdbe8
fix math for touchWithinBounds
latekvo Jun 14, 2024
8918ab1
add onPress() callback
latekvo Jun 14, 2024
4a053c0
Merge branch 'main' into @latekvo/add_gesture_handler_pressable_imple…
latekvo Jun 14, 2024
77e61b7
Merge branch '@latekvo/add_gesture_handler_pressable_implementation' …
latekvo Jun 14, 2024
f628f08
add onPress callback to example
latekvo Jun 14, 2024
51f3eba
fix longpress reactivity
latekvo Jun 18, 2024
ce96381
add option to add ripple
latekvo Jun 18, 2024
6098a41
remove accidental line
latekvo Jun 18, 2024
502b4d0
fix disabling pressable
latekvo Jun 18, 2024
e461a5a
change pressable types back to the original ones and remove custom pr…
latekvo Jun 18, 2024
d76f8f5
add touch cancellation detection
latekvo Jun 18, 2024
98f569a
treat touchCancel like touchUp, fix crashing
latekvo Jun 18, 2024
2b234be
add longPressEvent adapter
latekvo Jun 18, 2024
403ad8c
cleanup conversion functions
latekvo Jun 18, 2024
0581b59
fix type issues
latekvo Jun 18, 2024
e114dab
complete part of nativeTouch adapter
latekvo Jun 19, 2024
9d5887e
test nativeEvent and remove unnecessary fields
latekvo Jun 19, 2024
45d827d
add release_test showing both legacy and new pressable
latekvo Jun 19, 2024
17172c1
add missing timestamp field
latekvo Jun 19, 2024
7fc3722
Merge branch 'main' into @latekvo/add_gesture_handler_pressable_imple…
latekvo Jun 19, 2024
bdf37b3
fix typo
latekvo Jun 19, 2024
d0e4610
add types and rewrite code to fit react native docs
latekvo Jun 19, 2024
9d56d47
Merge branch '@latekvo/add_gesture_handler_pressable_implementation' …
latekvo Jun 19, 2024
fb60554
fix typos
latekvo Jun 19, 2024
30ccf0a
fix example and import issues
latekvo Jun 19, 2024
040d669
Merge branch 'main' into @latekvo/add_gesture_handler_pressable_imple…
latekvo Jun 19, 2024
c836879
show hitSlop and pressRetentionOffset in example
latekvo Jun 19, 2024
f731f5b
fix available ref object and naming scheme
latekvo Jun 19, 2024
ac52e93
adjust colors to match RN docs
latekvo Jun 19, 2024
691254e
update to useCallback instead of useRef
latekvo Jun 19, 2024
7b26790
add hitslop and slop indicators to example
latekvo Jun 20, 2024
7a7bec3
add press retention to tested cases, add notes
latekvo Jun 20, 2024
0e258eb
fix crashing on android
latekvo Jun 20, 2024
e59e11d
fix hitSlop for ios and android
latekvo Jun 20, 2024
1c13234
corrective changes to align with the original
latekvo Jun 20, 2024
253d996
remove unnecessary wrappers
latekvo Jun 20, 2024
a453345
removed spamming event outputs
latekvo Jun 20, 2024
57a7162
add inset recalculations to implement custom hitSlop
latekvo Jun 20, 2024
41f9bb6
fix invalid pressRetentionOffset value in example
latekvo Jun 20, 2024
f29c82f
complete slop+retention functionality on android
latekvo Jun 20, 2024
2834d02
complete ios implementation
latekvo Jun 20, 2024
9d228c4
revert some code to fix android
latekvo Jun 20, 2024
4720d60
enable functional style and children usage
latekvo Jun 20, 2024
cf00fd5
fix style setting states not triggering
latekvo Jun 21, 2024
6d87412
remove holding unnecessary old states
latekvo Jun 21, 2024
cac52c4
fix state setting, further simplification
latekvo Jun 21, 2024
909f295
fix touch bounds calculation
latekvo Jun 21, 2024
af60b00
cleanup
latekvo Jun 21, 2024
f6b49ed
change button type to work on web
latekvo Jun 21, 2024
5d790c4
fix longpress behaviour
latekvo Jun 21, 2024
704b2eb
adjust RawButton API to use styles
latekvo Jun 21, 2024
b9185c5
change pressable to use raw RNButton
latekvo Jun 21, 2024
a9aebfb
update new_api example
latekvo Jun 21, 2024
69db9e6
atomize comparison example into multiple smaller examples
latekvo Jun 21, 2024
6b4445f
adjust hitslop example to fit new format better
latekvo Jun 21, 2024
babe339
add missing test props to pressable
latekvo Jun 21, 2024
d721cf5
add ripple example and extend testing capabilities
latekvo Jun 21, 2024
dc20ff7
add functional styling example
latekvo Jun 21, 2024
727c069
improve ripple example
latekvo Jun 21, 2024
70ef07b
add correct config to ripple
latekvo Jun 21, 2024
a8e770f
add delayed press and longpress example
latekvo Jun 21, 2024
2941a2c
fix testingBase typing
latekvo Jun 21, 2024
db1f36b
cleanup
latekvo Jun 21, 2024
1b98577
add clear visual click signalling with Reanimated
latekvo Jun 21, 2024
8c9cd32
add comments, visual improvements
latekvo Jun 21, 2024
e3439fb
fix delayed pressable example
latekvo Jun 24, 2024
7a3e3f5
adjust hitslop example alignment
latekvo Jun 24, 2024
e017f88
fux setting ripple color on android
latekvo Jun 24, 2024
3223351
add comments, improve visuals, add platform dependant example differe…
latekvo Jun 24, 2024
3f46092
code cleanup
latekvo Jun 24, 2024
25c43d1
move pressable to separate directory
latekvo Jun 24, 2024
f6c60fa
move all utility functions to separate file
latekvo Jun 24, 2024
8b40fc6
cleanup and add missing parameters
latekvo Jun 24, 2024
72cc53a
remove unnecessary useEffect
latekvo Jun 24, 2024
b1cb50a
add index exporting neccessary files from Pressable
latekvo Jun 24, 2024
a9efaab
add index.ts for pressable
latekvo Jun 24, 2024
595d66e
Merge branch '@latekvo/add_gesture_handler_pressable_implementation' …
latekvo Jun 24, 2024
9e68280
remove unnecessary null assertions and cases
latekvo Jun 24, 2024
4127414
change directory name - tmp
latekvo Jun 24, 2024
da1a165
rename pressable directory
latekvo Jun 24, 2024
9867e95
fix index exporting invalid directory
latekvo Jun 24, 2024
ab318c6
fix invalid import statements
latekvo Jun 24, 2024
b3bfb2f
add missing types
latekvo Jun 24, 2024
5c678f6
memoize gestures
latekvo Jun 24, 2024
2e1a728
remove unnecessary states
latekvo Jun 24, 2024
8551aa3
add debug overlay
latekvo Jun 24, 2024
e33031a
add pressDelay functionality
latekvo Jun 24, 2024
7bfe52e
add border example, missing props and styles
latekvo Jun 24, 2024
0ca50e3
rename sub example snippets
latekvo Jun 24, 2024
1ea72ca
fix IOS crash
latekvo Jun 24, 2024
bb1dc49
after testing, reduce cursor poiner to work just on the web
latekvo Jun 24, 2024
4bc432f
improve verbosity
latekvo Jun 24, 2024
cad4eab
apply review suggestions and other cleanup
latekvo Jun 24, 2024
14012b3
remove unnecessary not-null check
latekvo Jun 24, 2024
aeb265e
fix styling to work on all platforms
latekvo Jun 26, 2024
a298a11
fix delay press, fix styles and cleanup
latekvo Jun 26, 2024
389e31e
remove debugging artifacts
latekvo Jun 26, 2024
33e3819
review suggestions and cleanup
latekvo Jun 26, 2024
758c6d2
change realease_test titles and comments
latekvo Jun 26, 2024
9012cdf
Merge branch 'main' into @latekvo/add_gesture_handler_pressable_imple…
latekvo Jun 26, 2024
be54954
fix unusual unsupported border style behaviour
latekvo Jun 26, 2024
5c8d189
fix pointer style on web
latekvo Jun 26, 2024
23ead08
apply review suggestions
latekvo Jun 28, 2024
c38d33b
apply review suggestions
latekvo Jun 28, 2024
af73988
clear up a comment
latekvo Jun 28, 2024
8bf5921
add hover delay example
latekvo Jul 1, 2024
70a5aed
add handling stuck press on light touch
latekvo Jul 1, 2024
d9906ad
remove deafult hover delay, remove windows compatibility docstring
latekvo Jul 1, 2024
f5ba145
change config blocks into a config loop
latekvo Jul 1, 2024
c351938
fix double hover out bug
latekvo Jul 1, 2024
2b548ef
rename variable as per suggestion
latekvo Jul 2, 2024
86eac66
fix hit slop on IOS
latekvo Jul 2, 2024
df5d2fd
fix double calling of press up
latekvo Jul 2, 2024
2917b06
apply suggestions to new_api example
latekvo Jul 2, 2024
9683172
fix down state getting stuck when pressing down on scroll
latekvo Jul 2, 2024
20f246b
cancel hoverIn/Out with delay if the other one happens before delay p…
latekvo Jul 2, 2024
d4f3659
apply review suggestion
latekvo Jul 2, 2024
30e6274
update comment
latekvo Jul 2, 2024
37afb78
fix scroll plus tap glitches
latekvo Jul 2, 2024
b9ba1d5
remove unnecessary comment
latekvo Jul 2, 2024
362f894
remove borders example
latekvo Jul 3, 2024
3d804ac
fix incorrect cancel handling (functional styling bug)
latekvo Jul 3, 2024
d1d23ba
Merge 'main', resolve conflicts.
latekvo Jul 3, 2024
a026c9c
remove unnecessary comment
latekvo Jul 3, 2024
9d004a5
clarify comment
latekvo Jul 3, 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
6 changes: 6 additions & 0 deletions Pressable/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
latekvo marked this conversation as resolved.
Show resolved Hide resolved
"main": "../lib/commonjs/components/Pressable",
"module": "../lib/module/components/Pressable",
"react-native": "../src/components/Pressable",
"types": "../lib/typescript/components/Pressable.d.ts"
}
2 changes: 2 additions & 0 deletions example/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import HoverableIcons from './src/new_api/hoverable_icons';
import VelocityTest from './src/new_api/velocityTest';
import Swipeable from 'src/new_api/swipeable';
import Pressable from 'src/new_api/pressable';

import EmptyExample from './src/empty/EmptyExample';
import RectButtonBorders from './src/release_tests/rectButton';
Expand Down Expand Up @@ -162,6 +163,7 @@
{ name: 'Chat Heads', component: ChatHeadsNewApi },
{ name: 'Drag and drop', component: DragNDrop },
{ name: 'Swipeable', component: Swipeable },
{ name: 'Pressable', component: Pressable },
{
name: 'Horizontal Drawer (Reanimated 2 & RNGH 2)',
component: BetterHorizontalDrawer,
Expand Down Expand Up @@ -260,7 +262,7 @@
renderSectionHeader={({ section: { sectionTitle } }) => (
<Text style={styles.sectionTitle}>{sectionTitle}</Text>
)}
ItemSeparatorComponent={() => <View style={styles.separator} />}

Check warning on line 265 in example/App.tsx

View workflow job for this annotation

GitHub Actions / check (example)

Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (https://reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “MainScreen” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true
/>
</SafeAreaView>
);
Expand Down
73 changes: 73 additions & 0 deletions example/src/new_api/pressable/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Pressable } from 'react-native-gesture-handler';

export default function EmptyExample() {
latekvo marked this conversation as resolved.
Show resolved Hide resolved
const pressIn = () => {
console.log('Pressable pressed in');
};

const pressOut = () => {
console.log('Pressable pressed out');
};

const hoverIn = () => {
console.log('Hovered in');
};

const hoverOut = () => {
console.log('Hovered out');
};

const focus = () => {
console.log('Focused pressable');
};

const blur = () => {
console.log('Blurred pressable');
};

const longPress = () => {
console.log('Long pressed');
};
return (
<View style={styles.container}>
<Pressable
style={styles.pressable}
onPressIn={pressIn}
onPressOut={pressOut}
onHoverIn={hoverIn}
onHoverOut={hoverOut}
onFocus={focus}
onBlur={blur}
onLongPress={longPress}>
<View style={styles.textWrapper}>
<Text style={styles.text}>Pressable!</Text>
</View>
</Pressable>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
pressable: {
backgroundColor: 'mediumpurple',
width: 120,
height: 120,
margin: 'auto',
},
textWrapper: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
color: '#F5FCFF',
},
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"android/noreanimated/src/main/java/",
"apple/",
"Swipeable/",
"Pressable/",
latekvo marked this conversation as resolved.
Show resolved Hide resolved
"jest-utils/",
"DrawerLayout/",
"README.md",
Expand Down
281 changes: 281 additions & 0 deletions src/components/Pressable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
import React, { useRef } from 'react';
import {
View,
AccessibilityProps,
ColorValue,
Insets,
NativeSyntheticEvent,
StyleProp,
TargetedEvent,
ViewProps,
ViewStyle,
StyleSheet,
} from 'react-native';

import {
Gesture,
GestureDetector,
GestureHandlerRootView,
GestureStateChangeEvent,
GestureTouchEvent,
LongPressGestureHandlerEventPayload,
TouchData,
} from '../.';
latekvo marked this conversation as resolved.
Show resolved Hide resolved
import { HoverGestureHandlerEventPayload } from '../handlers/gestures/hoverGesture';

const DEFAULT_LONG_PRESS_DURATION = 500;
const DEFAULT_HOVER_DELAY = 0;

export interface PressableStateCallbackType {
readonly pressed: boolean;
}

export interface PressableAndroidRippleConfig {
color?: null | ColorValue | undefined;
borderless?: null | boolean | undefined;
radius?: null | number | undefined;
foreground?: null | boolean | undefined;
}

export interface PressableProps
latekvo marked this conversation as resolved.
Show resolved Hide resolved
extends AccessibilityProps,
Omit<ViewProps, 'children' | 'style' | 'hitSlop'> {
/**
* Called when the hover is activated to provide visual feedback.
*/
onHoverIn?:
| null
| ((
event: GestureStateChangeEvent<HoverGestureHandlerEventPayload>
) => void);

/**
* Called when the hover is deactivated to undo visual feedback.
*/
onHoverOut?:
| null
| ((
event: GestureStateChangeEvent<HoverGestureHandlerEventPayload>
) => void);

/**
* Called when a single tap gesture is detected.
*/
onPress?: null | ((event: GestureTouchEvent) => void);

/**
* Called when a touch is engaged before `onPress`.
*/
onPressIn?: null | ((event: GestureTouchEvent) => void);

/**
* Called when a touch is released before `onPress`.
*/
onPressOut?: null | ((event: GestureTouchEvent) => void);

/**
* Called when a long-tap gesture is detected.
*/
onLongPress?:
| null
| ((
event: GestureStateChangeEvent<LongPressGestureHandlerEventPayload>
) => void);

/**
* Called after the element loses focus.
* @platform macos windows
*/
onBlur?: null | ((event: NativeSyntheticEvent<TargetedEvent>) => void);

/**
* Called after the element is focused.
* @platform macos windows
*/
onFocus?: null | ((event: NativeSyntheticEvent<TargetedEvent>) => void);

/**
* Either children or a render prop that receives a boolean reflecting whether
* the component is currently pressed.
*/
children?:
| React.ReactNode
| ((state: PressableStateCallbackType) => React.ReactNode);

/**
* Whether a press gesture can be interrupted by a parent gesture such as a
* scroll event. Defaults to true.
*/
cancelable?: null | boolean;

/**
* Duration to wait after hover in before calling `onHoverIn`.
* @platform macos windows
*/
delayHoverIn?: number | null;

/**
* Duration to wait after hover out before calling `onHoverOut`.
* @platform macos windows
*/
delayHoverOut?: number | null;

/**
* Duration (in milliseconds) from `onPressIn` before `onLongPress` is called.
*/
delayLongPress?: null | number;

/**
* Whether the press behavior is disabled.
*/
disabled?: null | boolean;

/**
* Additional distance outside of this view in which a press is detected.
*/
hitSlop?: null | Insets | number;

/**
* Additional distance outside of this view in which a touch is considered a
* press before `onPressOut` is triggered.
*/
pressRetentionOffset?: null | Insets | number;

/**
* If true, doesn't play system sound on touch.
*/
android_disableSound?: null | boolean;

/**
* Enables the Android ripple effect and configures its color.
*/
android_ripple?: null | PressableAndroidRippleConfig;

/**
* Used only for documentation or testing (e.g. snapshot testing).
*/
testOnly_pressed?: null | boolean;

/**
* Either view styles or a function that receives a boolean reflecting whether
* the component is currently pressed and returns view styles.
*/
style?:
| StyleProp<ViewStyle>
| ((state: PressableStateCallbackType) => StyleProp<ViewStyle>);

/**
* Duration (in milliseconds) to wait after press down before calling onPressIn.
*/
unstable_pressDelay?: number;
}

function touchWithinBounds(touch: TouchData, bounds: Insets): boolean {
const leftbound = bounds.left ? bounds.left < touch.absoluteX : true;
const rightbound = bounds.right ? bounds.right > touch.absoluteX : true;
const bottombound = bounds.bottom ? bounds.bottom < touch.absoluteY : true;
const topbound = bounds.top ? bounds.top > touch.absoluteY : true;

return leftbound && rightbound && topbound && bottombound;
}

export default function Pressable(props: PressableProps) {
const previousTouchData = useRef<TouchData[] | null>(null);

const pressRetentionOffset: Insets | null | undefined =
typeof props.pressRetentionOffset === 'number'
? {
top: props.pressRetentionOffset,
left: props.pressRetentionOffset,
bottom: props.pressRetentionOffset,
right: props.pressRetentionOffset,
}
: props.pressRetentionOffset;
latekvo marked this conversation as resolved.
Show resolved Hide resolved

const touch = Gesture.Manual()
latekvo marked this conversation as resolved.
Show resolved Hide resolved
latekvo marked this conversation as resolved.
Show resolved Hide resolved
latekvo marked this conversation as resolved.
Show resolved Hide resolved
.onTouchesDown((event) => {
// note: hitslop checking support is built in
props.onPressIn?.(event);
previousTouchData.current = event.allTouches;
latekvo marked this conversation as resolved.
Show resolved Hide resolved
})
.onTouchesUp((event) => {
// doesn't call onPressOut untill the last pointer leaves, while within bounds
if (event.allTouches.length > 1) {
return;
}

if (!pressRetentionOffset) {
props.onPressOut?.(event);
return;
}

if (
previousTouchData.current?.find((touch) =>
touchWithinBounds(touch, pressRetentionOffset)
)
) {
props.onPressOut?.(event);
}
});

const press = Gesture.LongPress().onEnd((event, success) => {
latekvo marked this conversation as resolved.
Show resolved Hide resolved
if (success) {
props.onLongPress?.(event);
}
});

const hover = Gesture.Hover()
latekvo marked this conversation as resolved.
Show resolved Hide resolved
.onBegin((event) => {
setTimeout(
() => props.onHoverIn?.(event),
props.delayHoverIn ?? DEFAULT_HOVER_DELAY
);
})
.onEnd((event) => {
setTimeout(
() => props.onHoverOut?.(event),
props.delayHoverOut ?? DEFAULT_HOVER_DELAY
);
});

press.minDuration(props.delayLongPress ?? DEFAULT_LONG_PRESS_DURATION);

// onBlur and onFocus don't exist in the docs

touch.hitSlop(props.hitSlop);
press.hitSlop(props.hitSlop);
hover.hitSlop(props.hitSlop);

// add props.pressRetentionOffset, according to docs, they're relative to pressable, not hitSlop

touch.enabled(!(props.disabled ?? false));
latekvo marked this conversation as resolved.
Show resolved Hide resolved
press.enabled(!(props.disabled ?? false));
hover.enabled(!(props.disabled ?? false));

const gesture = Gesture.Simultaneous(hover, press, touch);

return (
<GestureHandlerRootView>
<GestureDetector gesture={gesture}>
<View
latekvo marked this conversation as resolved.
Show resolved Hide resolved
style={[
styles.container,
typeof props.style === 'function'
? props.style({ pressed: false })
: props.style,
]}>
{typeof props.children === 'function'
? props.children({ pressed: false })
: props.children}
</View>
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
width: 'auto',
height: 'auto',
},
});
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ export type {

export type { SwipeableProps } from './components/Swipeable';
export { default as Swipeable } from './components/Swipeable';
export type { PressableProps } from './components/Pressable';
export { default as Pressable } from './components/Pressable';

export type {
DrawerLayoutProps,
DrawerPosition,
Expand Down
Loading