Skip to content

Commit 01364aa

Browse files
authored
Focusable Pressables should take focus when they are clicked on (#15327)
* Pressables should take focus on press * Change files * fix
1 parent 33cd92d commit 01364aa

File tree

5 files changed

+232
-2
lines changed

5 files changed

+232
-2
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Pressables should take focus on press",
4+
"packageName": "react-native-windows",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/e2e-test-app-fabric/test/__snapshots__/PressableComponentTest.test.ts.snap

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,13 @@ exports[`Pressable Tests Pressables can have event handlers, hover and click 2`]
846846
"Name": "pressOut",
847847
"TextRangePattern.GetText": "pressOut",
848848
},
849+
{
850+
"AutomationId": "",
851+
"ControlType": 50020,
852+
"LocalizedControlType": "text",
853+
"Name": "focus",
854+
"TextRangePattern.GetText": "focus",
855+
},
849856
{
850857
"AutomationId": "",
851858
"ControlType": 50020,
@@ -891,6 +898,10 @@ exports[`Pressable Tests Pressables can have event handlers, hover and click 2`]
891898
"Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
892899
"_Props": {},
893900
},
901+
{
902+
"Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
903+
"_Props": {},
904+
},
894905
],
895906
},
896907
"Visual Tree": {
@@ -991,6 +1002,18 @@ exports[`Pressable Tests Pressables can have event handlers, hover and click 2`]
9911002
},
9921003
],
9931004
},
1005+
{
1006+
"Offset": "11, 85, 0",
1007+
"Size": "874, 20",
1008+
"Visual Type": "SpriteVisual",
1009+
"__Children": [
1010+
{
1011+
"Offset": "0, 0, 0",
1012+
"Size": "874, 20",
1013+
"Visual Type": "SpriteVisual",
1014+
},
1015+
],
1016+
},
9941017
],
9951018
},
9961019
}

vnext/overrides.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,12 @@
358358
"type": "platform",
359359
"file": "src-win/Libraries/Components/Popup/PopupNativeComponent.js"
360360
},
361+
{
362+
"type": "derived",
363+
"file": "src-win/Libraries/Components/Pressable/Pressable.d.ts",
364+
"baseFile": "packages/react-native/Libraries/Components/Pressable/Pressable.d.ts",
365+
"baseHash": "d4dd8fc82436617bfeca9807be274aa5416d748c"
366+
},
361367
{
362368
"type": "patch",
363369
"file": "src-win/Libraries/Components/Pressable/Pressable.windows.js",
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
*/
9+
10+
import type * as React from 'react';
11+
import {Insets} from '../../../types/public/Insets';
12+
import {ColorValue, StyleProp} from '../../StyleSheet/StyleSheet';
13+
import {ViewStyle} from '../../StyleSheet/StyleSheetTypes';
14+
import {
15+
GestureResponderEvent,
16+
MouseEvent,
17+
NativeSyntheticEvent,
18+
TargetedEvent,
19+
} from '../../Types/CoreEventTypes';
20+
import {View} from '../View/View';
21+
import {AccessibilityProps} from '../View/ViewAccessibility';
22+
import {ViewProps} from '../View/ViewPropTypes';
23+
24+
export interface PressableStateCallbackType {
25+
readonly pressed: boolean;
26+
}
27+
28+
export interface PressableAndroidRippleConfig {
29+
color?: null | ColorValue | undefined;
30+
borderless?: null | boolean | undefined;
31+
radius?: null | number | undefined;
32+
foreground?: null | boolean | undefined;
33+
}
34+
35+
export interface PressableProps
36+
extends AccessibilityProps,
37+
Omit<ViewProps, 'children' | 'style' | 'hitSlop'> {
38+
/**
39+
* Called when the hover is activated to provide visual feedback.
40+
*/
41+
onHoverIn?: null | ((event: MouseEvent) => void) | undefined;
42+
43+
/**
44+
* Called when the hover is deactivated to undo visual feedback.
45+
*/
46+
onHoverOut?: null | ((event: MouseEvent) => void) | undefined;
47+
48+
/**
49+
* Called when a single tap gesture is detected.
50+
*/
51+
onPress?: null | ((event: GestureResponderEvent) => void) | undefined;
52+
53+
/**
54+
* Called when a touch is engaged before `onPress`.
55+
*/
56+
onPressIn?: null | ((event: GestureResponderEvent) => void) | undefined;
57+
58+
/**
59+
* Called when a touch is released before `onPress`.
60+
*/
61+
onPressOut?: null | ((event: GestureResponderEvent) => void) | undefined;
62+
63+
/**
64+
* Called when a long-tap gesture is detected.
65+
*/
66+
onLongPress?: null | ((event: GestureResponderEvent) => void) | undefined;
67+
68+
/**
69+
* Called after the element loses focus.
70+
* @platform macos windows
71+
*/
72+
onBlur?:
73+
| null
74+
| ((event: NativeSyntheticEvent<TargetedEvent>) => void)
75+
| undefined;
76+
77+
/**
78+
* Called after the element is focused.
79+
* @platform macos windows
80+
*/
81+
onFocus?:
82+
| null
83+
| ((event: NativeSyntheticEvent<TargetedEvent>) => void)
84+
| undefined;
85+
86+
/**
87+
* Either children or a render prop that receives a boolean reflecting whether
88+
* the component is currently pressed.
89+
*/
90+
children?:
91+
| React.ReactNode
92+
| ((state: PressableStateCallbackType) => React.ReactNode)
93+
| undefined;
94+
95+
/**
96+
* Whether a press gesture can be interrupted by a parent gesture such as a
97+
* scroll event. Defaults to true.
98+
*/
99+
cancelable?: null | boolean | undefined;
100+
101+
/**
102+
* Duration to wait after hover in before calling `onHoverIn`.
103+
* @platform macos windows
104+
*/
105+
delayHoverIn?: number | null | undefined;
106+
107+
/**
108+
* Duration to wait after hover out before calling `onHoverOut`.
109+
* @platform macos windows
110+
*/
111+
delayHoverOut?: number | null | undefined;
112+
113+
/**
114+
* Duration (in milliseconds) from `onPressIn` before `onLongPress` is called.
115+
*/
116+
delayLongPress?: null | number | undefined;
117+
118+
/**
119+
* Whether the press behavior is disabled.
120+
*/
121+
disabled?: null | boolean | undefined;
122+
123+
//[ Windows
124+
/**
125+
* When the pressable is pressed it will take focus
126+
* Default value: true
127+
*/
128+
focusOnPress?: null | boolean | undefined;
129+
// Windows]
130+
131+
/**
132+
* Additional distance outside of this view in which a press is detected.
133+
*/
134+
hitSlop?: null | Insets | number | undefined;
135+
136+
/**
137+
* Additional distance outside of this view in which a touch is considered a
138+
* press before `onPressOut` is triggered.
139+
*/
140+
pressRetentionOffset?: null | Insets | number | undefined;
141+
142+
/**
143+
* If true, doesn't play system sound on touch.
144+
*/
145+
android_disableSound?: null | boolean | undefined;
146+
147+
/**
148+
* Enables the Android ripple effect and configures its color.
149+
*/
150+
android_ripple?: null | PressableAndroidRippleConfig | undefined;
151+
152+
/**
153+
* Used only for documentation or testing (e.g. snapshot testing).
154+
*/
155+
testOnly_pressed?: null | boolean | undefined;
156+
157+
/**
158+
* Either view styles or a function that receives a boolean reflecting whether
159+
* the component is currently pressed and returns view styles.
160+
*/
161+
style?:
162+
| StyleProp<ViewStyle>
163+
| ((state: PressableStateCallbackType) => StyleProp<ViewStyle>)
164+
| undefined;
165+
166+
/**
167+
* Duration (in milliseconds) to wait after press down before calling onPressIn.
168+
*/
169+
unstable_pressDelay?: number | undefined;
170+
}
171+
172+
// TODO use React.AbstractComponent when available
173+
export const Pressable: React.ForwardRefExoticComponent<
174+
PressableProps & React.RefAttributes<View>
175+
>;

vnext/src-win/Libraries/Components/Pressable/Pressable.windows.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ type PressableBaseProps = $ReadOnly<{
7171
*/
7272
disabled?: ?boolean,
7373

74+
// [Windows
75+
/**
76+
* When the pressable is pressed it will take focus
77+
* Default value: true
78+
*/
79+
focusOnPress?: ?boolean,
80+
// Windows]
81+
7482
/**
7583
* Additional distance outside of this view in which a press is detected.
7684
*/
@@ -245,6 +253,7 @@ function Pressable({
245253
delayLongPress,
246254
disabled,
247255
focusable,
256+
focusOnPress, // Windows
248257
hitSlop,
249258
onBlur,
250259
onFocus,
@@ -316,6 +325,16 @@ function Pressable({
316325
hitSlop,
317326
};
318327

328+
const onPressWithFocus = React.useCallback(
329+
(args: GestureResponderEvent) => {
330+
if (focusable !== false && focusOnPress !== false) {
331+
viewRef?.current?.focus();
332+
}
333+
onPress?.(args);
334+
},
335+
[focusOnPress, onPress, focusable],
336+
);
337+
319338
const config = useMemo(
320339
() => ({
321340
cancelable,
@@ -332,7 +351,7 @@ function Pressable({
332351
onHoverIn,
333352
onHoverOut,
334353
onLongPress,
335-
onPress,
354+
onPress: onPressWithFocus,
336355
onPressIn(event: GestureResponderEvent): void {
337356
if (android_rippleConfig != null) {
338357
android_rippleConfig.onPressIn(event);
@@ -378,7 +397,7 @@ function Pressable({
378397
onHoverIn,
379398
onHoverOut,
380399
onLongPress,
381-
onPress,
400+
onPressWithFocus,
382401
onPressIn,
383402
onPressMove,
384403
onPressOut,

0 commit comments

Comments
 (0)