Skip to content

Commit cab4da7

Browse files
Igor Klemenskifacebook-github-bot
Igor Klemenski
authored andcommitted
Add onFocus and onBlur to Pressable. (#30405)
Summary: Starting to upstream keyboard-related features from React Native Windows - this is the Android implementation. Exposing onFocus and onBlur callbacks on Pressable; they're already declared for View in ViewPropTypes.js, which Pressable wraps. Registering event listeners in ReactViewManager to listen for native focus events. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [Android] [Added] - Add onFocus/onBlur for Pressable on Android. Pull Request resolved: #30405 Test Plan: Tested on v63-stable, since building master on Windows is now broken. Screenshots from RNTester running on the emulator: ![image](https://user-images.githubusercontent.com/12816515/99320373-59777e80-2820-11eb-91a8-704fff4aa13d.png) ![image](https://user-images.githubusercontent.com/12816515/99320412-6f853f00-2820-11eb-98f2-f9cd29e8aa0d.png) Reviewed By: mdvacca Differential Revision: D25444566 Pulled By: appden fbshipit-source-id: ce0efd3e3b199a508df0ba1ce484b4de17471432
1 parent 47bb3be commit cab4da7

File tree

9 files changed

+131
-16
lines changed

9 files changed

+131
-16
lines changed

Libraries/Components/Pressable/Pressable.js

+22-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ import type {
2525
import {PressabilityDebugView} from '../../Pressability/PressabilityDebug';
2626
import usePressability from '../../Pressability/usePressability';
2727
import {normalizeRect, type RectOrSize} from '../../StyleSheet/Rect';
28-
import type {LayoutEvent, PressEvent} from '../../Types/CoreEventTypes';
28+
import type {
29+
LayoutEvent,
30+
PressEvent,
31+
BlurEvent,
32+
FocusEvent,
33+
} from '../../Types/CoreEventTypes';
2934
import View from '../View/View';
3035

3136
type ViewStyleProp = $ElementType<React.ElementConfig<typeof View>, 'style'>;
@@ -105,6 +110,16 @@ type Props = $ReadOnly<{|
105110
*/
106111
onPressOut?: ?(event: PressEvent) => void,
107112

113+
/**
114+
* Called after the element loses focus.
115+
*/
116+
onBlur?: ?(event: BlurEvent) => mixed,
117+
118+
/**
119+
* Called after the element is focused.
120+
*/
121+
onFocus?: ?(event: FocusEvent) => mixed,
122+
108123
/**
109124
* Either view styles or a function that receives a boolean reflecting whether
110125
* the component is currently pressed and returns view styles.
@@ -154,6 +169,8 @@ function Pressable(props: Props, forwardedRef): React.Node {
154169
onPress,
155170
onPressIn,
156171
onPressOut,
172+
onBlur,
173+
onFocus,
157174
pressRetentionOffset,
158175
style,
159176
testOnly_pressed,
@@ -207,6 +224,8 @@ function Pressable(props: Props, forwardedRef): React.Node {
207224
onPressOut(event);
208225
}
209226
},
227+
onBlur,
228+
onFocus,
210229
}),
211230
[
212231
android_disableSound,
@@ -218,6 +237,8 @@ function Pressable(props: Props, forwardedRef): React.Node {
218237
onPress,
219238
onPressIn,
220239
onPressOut,
240+
onBlur,
241+
onFocus,
221242
pressRetentionOffset,
222243
setPressed,
223244
unstable_pressDelay,

Libraries/Components/View/View.js

+34-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111
'use strict';
1212

1313
import type {ViewProps} from './ViewPropTypes';
14-
14+
import type {BlurEvent, FocusEvent} from '../../Types/CoreEventTypes';
1515
const React = require('react');
1616
import ViewNativeComponent from './ViewNativeComponent';
1717
const TextAncestor = require('../../Text/TextAncestor');
18+
const TextInputState = require('../TextInput/TextInputState');
19+
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
20+
const setAndForwardRef = require('../../Utilities/setAndForwardRef');
21+
const {useRef} = React;
1822

1923
export type Props = ViewProps;
2024

@@ -29,9 +33,37 @@ const View: React.AbstractComponent<
2933
ViewProps,
3034
React.ElementRef<typeof ViewNativeComponent>,
3135
> = React.forwardRef((props: ViewProps, forwardedRef) => {
36+
const viewRef = useRef<null | React.ElementRef<HostComponent<mixed>>>(null);
37+
38+
const _setNativeRef = setAndForwardRef({
39+
getForwardedRef: () => forwardedRef,
40+
setLocalRef: ref => {
41+
viewRef.current = ref;
42+
},
43+
});
44+
45+
const _onBlur = (event: BlurEvent) => {
46+
TextInputState.blurInput(viewRef.current);
47+
if (props.onBlur) {
48+
props.onBlur(event);
49+
}
50+
};
51+
52+
const _onFocus = (event: FocusEvent) => {
53+
TextInputState.focusInput(viewRef.current);
54+
if (props.onFocus) {
55+
props.onFocus(event);
56+
}
57+
};
58+
3259
return (
3360
<TextAncestor.Provider value={false}>
34-
<ViewNativeComponent {...props} ref={forwardedRef} />
61+
<ViewNativeComponent
62+
{...props}
63+
onBlur={_onBlur}
64+
onFocus={_onFocus}
65+
ref={_setNativeRef}
66+
/>
3567
</TextAncestor.Provider>
3668
);
3769
});

ReactAndroid/src/main/java/com/facebook/react/views/common/BUCK

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_library")
1+
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library")
22

33
rn_android_library(
44
name = "common",
@@ -18,5 +18,8 @@ rn_android_library(
1818
"PUBLIC",
1919
],
2020
deps = [
21+
react_native_target("java/com/facebook/react/uimanager:uimanager"),
22+
react_native_target("java/com/facebook/react/uimanager/annotations:annotations"),
23+
react_native_target("java/com/facebook/react/bridge:bridge"),
2124
],
2225
)

ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputBlurEvent.java ReactAndroid/src/main/java/com/facebook/react/views/common/ReactViewBlurEvent.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
package com.facebook.react.views.textinput;
8+
package com.facebook.react.views.common;
99

1010
import com.facebook.react.bridge.Arguments;
1111
import com.facebook.react.bridge.WritableMap;
1212
import com.facebook.react.uimanager.events.Event;
1313
import com.facebook.react.uimanager.events.RCTEventEmitter;
1414

15-
/** Event emitted by EditText native view when it loses focus. */
16-
/* package */ class ReactTextInputBlurEvent extends Event<ReactTextInputBlurEvent> {
15+
/** Event emitted by a native view when it loses focus. */
16+
public class ReactViewBlurEvent extends Event<ReactViewBlurEvent> {
1717

1818
private static final String EVENT_NAME = "topBlur";
1919

20-
public ReactTextInputBlurEvent(int viewId) {
20+
public ReactViewBlurEvent(int viewId) {
2121
super(viewId);
2222
}
2323

ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputFocusEvent.java ReactAndroid/src/main/java/com/facebook/react/views/common/ReactViewFocusEvent.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
package com.facebook.react.views.textinput;
8+
package com.facebook.react.views.common;
99

1010
import com.facebook.react.bridge.Arguments;
1111
import com.facebook.react.bridge.WritableMap;
1212
import com.facebook.react.uimanager.events.Event;
1313
import com.facebook.react.uimanager.events.RCTEventEmitter;
1414

15-
/** Event emitted by EditText native view when it receives focus. */
16-
/* package */ class ReactTextInputFocusEvent extends Event<ReactTextInputFocusEvent> {
15+
/** Event emitted by a native view when it receives focus. */
16+
public class ReactViewFocusEvent extends Event<ReactViewFocusEvent> {
1717

1818
private static final String EVENT_NAME = "topFocus";
1919

20-
public ReactTextInputFocusEvent(int viewId) {
20+
public ReactViewFocusEvent(int viewId) {
2121
super(viewId);
2222
}
2323

ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ rn_android_library(
2121
react_native_target("java/com/facebook/react/modules/core:core"),
2222
react_native_target("java/com/facebook/react/uimanager:uimanager"),
2323
react_native_target("java/com/facebook/react/uimanager/annotations:annotations"),
24+
react_native_target("java/com/facebook/react/views/common:common"),
2425
react_native_target("java/com/facebook/react/views/imagehelper:imagehelper"),
2526
react_native_target("java/com/facebook/react/views/scroll:scroll"),
2627
react_native_target("java/com/facebook/react/views/text:text"),

ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
import com.facebook.react.uimanager.annotations.ReactProp;
6060
import com.facebook.react.uimanager.annotations.ReactPropGroup;
6161
import com.facebook.react.uimanager.events.EventDispatcher;
62+
import com.facebook.react.views.common.ReactViewBlurEvent;
63+
import com.facebook.react.views.common.ReactViewFocusEvent;
6264
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper;
6365
import com.facebook.react.views.scroll.ScrollEvent;
6466
import com.facebook.react.views.scroll.ScrollEventType;
@@ -974,9 +976,9 @@ protected void addEventEmitters(
974976
public void onFocusChange(View v, boolean hasFocus) {
975977
EventDispatcher eventDispatcher = getEventDispatcher(reactContext, editText);
976978
if (hasFocus) {
977-
eventDispatcher.dispatchEvent(new ReactTextInputFocusEvent(editText.getId()));
979+
eventDispatcher.dispatchEvent(new ReactViewFocusEvent(editText.getId()));
978980
} else {
979-
eventDispatcher.dispatchEvent(new ReactTextInputBlurEvent(editText.getId()));
981+
eventDispatcher.dispatchEvent(new ReactViewBlurEvent(editText.getId()));
980982

981983
eventDispatcher.dispatchEvent(
982984
new ReactTextInputEndEditingEvent(

ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java

+24-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import com.facebook.react.uimanager.annotations.ReactProp;
3030
import com.facebook.react.uimanager.annotations.ReactPropGroup;
3131
import com.facebook.react.uimanager.events.EventDispatcher;
32+
import com.facebook.react.views.common.ReactViewBlurEvent;
33+
import com.facebook.react.views.common.ReactViewFocusEvent;
3234
import com.facebook.yoga.YogaConstants;
3335
import java.util.Locale;
3436
import java.util.Map;
@@ -234,8 +236,7 @@ public void setFocusable(final ReactViewGroup view, boolean focusable) {
234236
@Override
235237
public void onClick(View v) {
236238
final EventDispatcher mEventDispatcher =
237-
UIManagerHelper.getEventDispatcherForReactTag(
238-
(ReactContext) view.getContext(), view.getId());
239+
getEventDispatcher((ReactContext) view.getContext(), view);
239240
if (mEventDispatcher == null) {
240241
return;
241242
}
@@ -254,6 +255,27 @@ public void onClick(View v) {
254255
}
255256
}
256257

258+
private static EventDispatcher getEventDispatcher(
259+
ReactContext reactContext, ReactViewGroup view) {
260+
return UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.getId());
261+
}
262+
263+
@Override
264+
protected void addEventEmitters(
265+
final ThemedReactContext reactContext, final ReactViewGroup view) {
266+
view.setOnFocusChangeListener(
267+
new View.OnFocusChangeListener() {
268+
public void onFocusChange(View v, boolean hasFocus) {
269+
EventDispatcher eventDispatcher = getEventDispatcher(reactContext, view);
270+
if (hasFocus) {
271+
eventDispatcher.dispatchEvent(new ReactViewFocusEvent(view.getId()));
272+
} else {
273+
eventDispatcher.dispatchEvent(new ReactViewBlurEvent(view.getId()));
274+
}
275+
}
276+
});
277+
}
278+
257279
@ReactProp(name = ViewProps.OVERFLOW)
258280
public void setOverflow(ReactViewGroup view, String overflow) {
259281
view.setOverflow(overflow);

packages/rn-tester/js/examples/Pressable/PressableExample.js

+34
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,32 @@ function PressableDisabled() {
250250
);
251251
}
252252

253+
function PressableFocusBlurEvents() {
254+
const [lastEvent, setLastEvent] = useState('');
255+
256+
return (
257+
<View testID="pressable_hit_slop">
258+
<View style={[styles.row, styles.centered]}>
259+
<Pressable
260+
onFocus={() => {
261+
console.log('Focused!');
262+
setLastEvent('Received focus event');
263+
}}
264+
onBlur={() => {
265+
console.log('Blurred!');
266+
setLastEvent('Received blur event');
267+
}}
268+
testID="pressable_focus_blur_button">
269+
<Text style={styles.text}>Use keyboard to move focus to me</Text>
270+
</Pressable>
271+
</View>
272+
<View style={styles.logBox}>
273+
<Text>{lastEvent}</Text>
274+
</View>
275+
</View>
276+
);
277+
}
278+
253279
const styles = StyleSheet.create({
254280
row: {
255281
justifyContent: 'center',
@@ -478,4 +504,12 @@ exports.examples = [
478504
return <PressableDisabled />;
479505
},
480506
},
507+
{
508+
title: 'Pressable onFocus/onBlur',
509+
description: ('<Pressable> components can receive focus/blur events.': string),
510+
platform: 'android',
511+
render: function(): React.Node {
512+
return <PressableFocusBlurEvents />;
513+
},
514+
},
481515
];

0 commit comments

Comments
 (0)