Skip to content

Commit c1548cd

Browse files
committed
Pressable works
1 parent 42918b4 commit c1548cd

2 files changed

Lines changed: 73 additions & 3 deletions

File tree

packages/react-native-gesture-handler/src/components/Pressable/utils.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,53 @@ const gestureTouchToPressableEvent = (
142142
};
143143
};
144144

145+
const mockCenterPressableEvent = (
146+
dimensions: PressableDimensions
147+
): PressableEvent => {
148+
const timestamp = Date.now();
149+
150+
// As far as I can see, there isn't a conventional way of getting targetId with the data we get
151+
const targetId = 0;
152+
153+
// tvOS activates a focused button via the Select button rather than a touch, so there are no
154+
// touch coordinates. Report the view's center as the press location — a sensible representative
155+
// point for the event handed to user callbacks.
156+
const locationX = dimensions.width / 2;
157+
const locationY = dimensions.height / 2;
158+
159+
const pressEvent: InnerPressableEvent = {
160+
identifier: targetId,
161+
locationX,
162+
locationY,
163+
pageX: locationX,
164+
pageY: locationY,
165+
target: targetId,
166+
timestamp,
167+
touches: [],
168+
changedTouches: [],
169+
};
170+
171+
return {
172+
nativeEvent: {
173+
touches: [pressEvent],
174+
changedTouches: [pressEvent],
175+
identifier: targetId,
176+
locationX,
177+
locationY,
178+
pageX: locationX,
179+
pageY: locationY,
180+
target: targetId,
181+
timestamp,
182+
force: undefined,
183+
},
184+
};
185+
};
186+
145187
export {
146188
addInsets,
147189
gestureToPressableEvent,
148190
gestureTouchToPressableEvent,
149191
isTouchWithinInset,
192+
mockCenterPressableEvent,
150193
numberAsInset,
151194
};

packages/react-native-gesture-handler/src/v3/components/Pressable.tsx

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
gestureToPressableEvent,
2929
gestureTouchToPressableEvent,
3030
isTouchWithinInset,
31+
mockCenterPressableEvent,
3132
numberAsInset,
3233
} from '../../components/Pressable/utils';
3334
import { PressabilityDebugView } from '../../handlers/PressabilityDebugView';
@@ -151,8 +152,9 @@ const Pressable = (props: PressableProps) => {
151152
}, [cancelDelayedPress, cancelLongPress]);
152153

153154
const handlePressIn = useCallback(
154-
(event: PressableEvent) => {
155+
(event: PressableEvent, skipBoundsCheck = false) => {
155156
if (
157+
!skipBoundsCheck &&
156158
!isTouchWithinInset(
157159
dimensions.current,
158160
normalizedHitSlop,
@@ -305,11 +307,18 @@ const Pressable = (props: PressableProps) => {
305307
}
306308
},
307309
onBegin: () => {
310+
if (Platform.isTV) {
311+
// tvOS drives this native gesture from the focus-engine Select press.
312+
// The press state machine is touch-based and never
313+
// receives LONG_PRESS_TOUCHES_DOWN here, so bypass it and drive the press handlers directly.
314+
// A focus-driven press has no coordinates, so skip the hit-slop bounds check entirely.
315+
handlePressIn(mockCenterPressableEvent(dimensions.current), true);
316+
return;
317+
}
308318
stateMachine.handleEvent(StateMachineEvent.NATIVE_BEGIN);
309319
},
310320
onActivate: () => {
311-
if (Platform.OS !== 'android') {
312-
// Native.onActivate is broken with Android + hitSlop
321+
if (!Platform.isTV && Platform.OS !== 'android') {
313322
stateMachine.handleEvent(StateMachineEvent.NATIVE_START);
314323
}
315324
},
@@ -319,6 +328,16 @@ const Pressable = (props: PressableProps) => {
319328
if (Platform.OS === 'web') {
320329
return;
321330
}
331+
332+
if (Platform.isTV) {
333+
handlePressOut(
334+
mockCenterPressableEvent(dimensions.current),
335+
!event.canceled
336+
);
337+
handleFinalize();
338+
return;
339+
}
340+
322341
stateMachine.handleEvent(
323342
event.canceled ? StateMachineEvent.CANCEL : StateMachineEvent.FINALIZE
324343
);
@@ -365,10 +384,18 @@ const Pressable = (props: PressableProps) => {
365384
[onLayout]
366385
);
367386

387+
const tvProps = Platform.isTV
388+
? {
389+
isTVSelectable:
390+
remainingProps.focusable ?? remainingProps.isTVSelectable ?? false,
391+
}
392+
: null;
393+
368394
return (
369395
<GestureDetector gesture={gesture}>
370396
<PureNativeButton
371397
{...remainingProps}
398+
{...tvProps}
372399
onLayout={setDimensions}
373400
accessible={accessible !== false}
374401
hitSlop={appliedHitSlop}

0 commit comments

Comments
 (0)