Skip to content

Commit

Permalink
Add onFocus and onBlur to Pressable. (#30405)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Igor Klemenski authored and facebook-github-bot committed Jan 8, 2021
1 parent 47bb3be commit cab4da7
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 16 deletions.
23 changes: 22 additions & 1 deletion Libraries/Components/Pressable/Pressable.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ import type {
import {PressabilityDebugView} from '../../Pressability/PressabilityDebug';
import usePressability from '../../Pressability/usePressability';
import {normalizeRect, type RectOrSize} from '../../StyleSheet/Rect';
import type {LayoutEvent, PressEvent} from '../../Types/CoreEventTypes';
import type {
LayoutEvent,
PressEvent,
BlurEvent,
FocusEvent,
} from '../../Types/CoreEventTypes';
import View from '../View/View';

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

/**
* Called after the element loses focus.
*/
onBlur?: ?(event: BlurEvent) => mixed,

/**
* Called after the element is focused.
*/
onFocus?: ?(event: FocusEvent) => mixed,

/**
* Either view styles or a function that receives a boolean reflecting whether
* the component is currently pressed and returns view styles.
Expand Down Expand Up @@ -154,6 +169,8 @@ function Pressable(props: Props, forwardedRef): React.Node {
onPress,
onPressIn,
onPressOut,
onBlur,
onFocus,
pressRetentionOffset,
style,
testOnly_pressed,
Expand Down Expand Up @@ -207,6 +224,8 @@ function Pressable(props: Props, forwardedRef): React.Node {
onPressOut(event);
}
},
onBlur,
onFocus,
}),
[
android_disableSound,
Expand All @@ -218,6 +237,8 @@ function Pressable(props: Props, forwardedRef): React.Node {
onPress,
onPressIn,
onPressOut,
onBlur,
onFocus,
pressRetentionOffset,
setPressed,
unstable_pressDelay,
Expand Down
36 changes: 34 additions & 2 deletions Libraries/Components/View/View.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
'use strict';

import type {ViewProps} from './ViewPropTypes';

import type {BlurEvent, FocusEvent} from '../../Types/CoreEventTypes';
const React = require('react');
import ViewNativeComponent from './ViewNativeComponent';
const TextAncestor = require('../../Text/TextAncestor');
const TextInputState = require('../TextInput/TextInputState');
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
const setAndForwardRef = require('../../Utilities/setAndForwardRef');
const {useRef} = React;

export type Props = ViewProps;

Expand All @@ -29,9 +33,37 @@ const View: React.AbstractComponent<
ViewProps,
React.ElementRef<typeof ViewNativeComponent>,
> = React.forwardRef((props: ViewProps, forwardedRef) => {
const viewRef = useRef<null | React.ElementRef<HostComponent<mixed>>>(null);

const _setNativeRef = setAndForwardRef({
getForwardedRef: () => forwardedRef,
setLocalRef: ref => {
viewRef.current = ref;
},
});

const _onBlur = (event: BlurEvent) => {
TextInputState.blurInput(viewRef.current);
if (props.onBlur) {
props.onBlur(event);
}
};

const _onFocus = (event: FocusEvent) => {
TextInputState.focusInput(viewRef.current);
if (props.onFocus) {
props.onFocus(event);
}
};

return (
<TextAncestor.Provider value={false}>
<ViewNativeComponent {...props} ref={forwardedRef} />
<ViewNativeComponent
{...props}
onBlur={_onBlur}
onFocus={_onFocus}
ref={_setNativeRef}
/>
</TextAncestor.Provider>
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_library")
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library")

rn_android_library(
name = "common",
Expand All @@ -18,5 +18,8 @@ rn_android_library(
"PUBLIC",
],
deps = [
react_native_target("java/com/facebook/react/uimanager:uimanager"),
react_native_target("java/com/facebook/react/uimanager/annotations:annotations"),
react_native_target("java/com/facebook/react/bridge:bridge"),
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.views.textinput;
package com.facebook.react.views.common;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;

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

private static final String EVENT_NAME = "topBlur";

public ReactTextInputBlurEvent(int viewId) {
public ReactViewBlurEvent(int viewId) {
super(viewId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.views.textinput;
package com.facebook.react.views.common;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;

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

private static final String EVENT_NAME = "topFocus";

public ReactTextInputFocusEvent(int viewId) {
public ReactViewFocusEvent(int viewId) {
super(viewId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ rn_android_library(
react_native_target("java/com/facebook/react/modules/core:core"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
react_native_target("java/com/facebook/react/uimanager/annotations:annotations"),
react_native_target("java/com/facebook/react/views/common:common"),
react_native_target("java/com/facebook/react/views/imagehelper:imagehelper"),
react_native_target("java/com/facebook/react/views/scroll:scroll"),
react_native_target("java/com/facebook/react/views/text:text"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.annotations.ReactPropGroup;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.views.common.ReactViewBlurEvent;
import com.facebook.react.views.common.ReactViewFocusEvent;
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper;
import com.facebook.react.views.scroll.ScrollEvent;
import com.facebook.react.views.scroll.ScrollEventType;
Expand Down Expand Up @@ -974,9 +976,9 @@ protected void addEventEmitters(
public void onFocusChange(View v, boolean hasFocus) {
EventDispatcher eventDispatcher = getEventDispatcher(reactContext, editText);
if (hasFocus) {
eventDispatcher.dispatchEvent(new ReactTextInputFocusEvent(editText.getId()));
eventDispatcher.dispatchEvent(new ReactViewFocusEvent(editText.getId()));
} else {
eventDispatcher.dispatchEvent(new ReactTextInputBlurEvent(editText.getId()));
eventDispatcher.dispatchEvent(new ReactViewBlurEvent(editText.getId()));

eventDispatcher.dispatchEvent(
new ReactTextInputEndEditingEvent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.annotations.ReactPropGroup;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.views.common.ReactViewBlurEvent;
import com.facebook.react.views.common.ReactViewFocusEvent;
import com.facebook.yoga.YogaConstants;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -234,8 +236,7 @@ public void setFocusable(final ReactViewGroup view, boolean focusable) {
@Override
public void onClick(View v) {
final EventDispatcher mEventDispatcher =
UIManagerHelper.getEventDispatcherForReactTag(
(ReactContext) view.getContext(), view.getId());
getEventDispatcher((ReactContext) view.getContext(), view);
if (mEventDispatcher == null) {
return;
}
Expand All @@ -254,6 +255,27 @@ public void onClick(View v) {
}
}

private static EventDispatcher getEventDispatcher(
ReactContext reactContext, ReactViewGroup view) {
return UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.getId());
}

@Override
protected void addEventEmitters(
final ThemedReactContext reactContext, final ReactViewGroup view) {
view.setOnFocusChangeListener(
new View.OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
EventDispatcher eventDispatcher = getEventDispatcher(reactContext, view);
if (hasFocus) {
eventDispatcher.dispatchEvent(new ReactViewFocusEvent(view.getId()));
} else {
eventDispatcher.dispatchEvent(new ReactViewBlurEvent(view.getId()));
}
}
});
}

@ReactProp(name = ViewProps.OVERFLOW)
public void setOverflow(ReactViewGroup view, String overflow) {
view.setOverflow(overflow);
Expand Down
34 changes: 34 additions & 0 deletions packages/rn-tester/js/examples/Pressable/PressableExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,32 @@ function PressableDisabled() {
);
}

function PressableFocusBlurEvents() {
const [lastEvent, setLastEvent] = useState('');

return (
<View testID="pressable_hit_slop">
<View style={[styles.row, styles.centered]}>
<Pressable
onFocus={() => {
console.log('Focused!');
setLastEvent('Received focus event');
}}
onBlur={() => {
console.log('Blurred!');
setLastEvent('Received blur event');
}}
testID="pressable_focus_blur_button">
<Text style={styles.text}>Use keyboard to move focus to me</Text>
</Pressable>
</View>
<View style={styles.logBox}>
<Text>{lastEvent}</Text>
</View>
</View>
);
}

const styles = StyleSheet.create({
row: {
justifyContent: 'center',
Expand Down Expand Up @@ -478,4 +504,12 @@ exports.examples = [
return <PressableDisabled />;
},
},
{
title: 'Pressable onFocus/onBlur',
description: ('<Pressable> components can receive focus/blur events.': string),
platform: 'android',
render: function(): React.Node {
return <PressableFocusBlurEvents />;
},
},
];

0 comments on commit cab4da7

Please sign in to comment.