Skip to content

Commit e477092

Browse files
Abbondanzofacebook-github-bot
authored andcommitted
Add support for blur and focus on View
Summary: As the title suggests: adds support strictly to `View` components on Android for `onFocus` and `onBlur` events. This is especially helpful for apps that respond to controller or remote inputs and aligns with existing support for the `focusable` prop. In order to make this change cross-compatible with text inputs, `TextInputFocusEvent` has been deprecated in favor of the `BlurEvent`/`FocusEvent` types now available from core. Their type signatures are identical but `BlurEvent`/`FocusEvent` should be the type going forward for all views that intend to support focus/blur. Text inputs intentionally do not forward information about their state upon focus/blur and docs specifically call out `onEndEditing` as a means of reading state synchronously when blurring. Therefore, the changes to the native side to remove the event type specifically for text inputs is not breaking. Changelog: [Android][Added] - Support for `onFocus` and `onBlur` function calls in `View` components Differential Revision: D75238291
1 parent fa79fd8 commit e477092

File tree

15 files changed

+185
-90
lines changed

15 files changed

+185
-90
lines changed

packages/react-native/Libraries/Components/TextInput/TextInput.d.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
import {ColorValue, StyleProp} from '../../StyleSheet/StyleSheet';
1818
import {TextStyle} from '../../StyleSheet/StyleSheetTypes';
1919
import {
20+
BlurEvent,
21+
FocusEvent,
2022
NativeSyntheticEvent,
2123
NativeTouchEvent,
2224
TargetedEvent,
@@ -455,7 +457,7 @@ export interface TextInputAndroidProps {
455457
}
456458

457459
/**
458-
* @deprecated Use `TextInputFocusEvent` instead
460+
* @deprecated Use `FocusEvent` instead
459461
*/
460462
export interface TextInputFocusEventData extends TargetedEvent {
461463
text: string;
@@ -464,6 +466,7 @@ export interface TextInputFocusEventData extends TargetedEvent {
464466

465467
/**
466468
* @see TextInputProps.onFocus
469+
* @deprecated Use `FocusEvent` instead
467470
*/
468471
export type TextInputFocusEvent = NativeSyntheticEvent<TextInputFocusEventData>;
469472

@@ -809,8 +812,11 @@ export interface TextInputProps
809812

810813
/**
811814
* Callback that is called when the text input is blurred
815+
*
816+
* Note: If you are trying to find the last value of TextInput, you can use the `onEndEditing`
817+
* event, which is fired upon completion of editing.
812818
*/
813-
onBlur?: ((e: TextInputFocusEvent) => void) | undefined;
819+
onBlur?: ((e: BlurEvent) => void) | undefined;
814820

815821
/**
816822
* Callback that is called when the text input's text changes.
@@ -859,7 +865,7 @@ export interface TextInputProps
859865
/**
860866
* Callback that is called when the text input is focused
861867
*/
862-
onFocus?: ((e: TextInputFocusEvent) => void) | undefined;
868+
onFocus?: ((e: FocusEvent) => void) | undefined;
863869

864870
/**
865871
* Callback that is called when the text input selection is changed.

packages/react-native/Libraries/Components/TextInput/TextInput.flow.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
import type {HostInstance} from '../../../src/private/types/HostInstance';
1212
import type {
13+
BlurEvent,
14+
FocusEvent,
1315
GestureResponderEvent,
1416
NativeSyntheticEvent,
1517
ScrollEvent,
@@ -58,22 +60,22 @@ type TextInputContentSizeChangeEventData = $ReadOnly<{
5860
export type TextInputContentSizeChangeEvent =
5961
NativeSyntheticEvent<TextInputContentSizeChangeEventData>;
6062

61-
type TargetEvent = $ReadOnly<{
62-
target: number,
63-
...
64-
}>;
65-
66-
type TextInputFocusEventData = TargetEvent;
67-
6863
/**
6964
* @see TextInputProps.onBlur
65+
* @deprecated Use `BlurEvent` instead.
7066
*/
71-
export type TextInputBlurEvent = NativeSyntheticEvent<TextInputFocusEventData>;
67+
export type TextInputBlurEvent = BlurEvent;
7268

7369
/**
7470
* @see TextInputProps.onFocus
71+
* @deprecated Use `FocusEvent` instead.
7572
*/
76-
export type TextInputFocusEvent = NativeSyntheticEvent<TextInputFocusEventData>;
73+
export type TextInputFocusEvent = FocusEvent;
74+
75+
type TargetEvent = $ReadOnly<{
76+
target: number,
77+
...
78+
}>;
7779

7880
export type Selection = $ReadOnly<{
7981
start: number,

packages/react-native/Libraries/Components/TextInput/TextInput.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
import type {HostInstance} from '../../../src/private/types/HostInstance';
1212
import type {____TextStyle_Internal as TextStyleInternal} from '../../StyleSheet/StyleSheetTypes';
1313
import type {
14+
BlurEvent,
1415
GestureResponderEvent,
16+
FocusEvent,
1517
ScrollEvent,
1618
} from '../../Types/CoreEventTypes';
1719
import type {
@@ -86,10 +88,12 @@ if (Platform.OS === 'android') {
8688

8789
export type {
8890
AutoCapitalize,
91+
BlurEvent,
8992
EnterKeyHintType,
9093
EnterKeyHintTypeAndroid,
9194
EnterKeyHintTypeIOS,
9295
EnterKeyHintTypeOptions,
96+
FocusEvent,
9397
InputModeOptions,
9498
KeyboardType,
9599
KeyboardTypeAndroid,
@@ -520,14 +524,14 @@ function InternalTextInput(props: TextInputProps): React.Node {
520524
});
521525
};
522526

523-
const _onFocus = (event: TextInputFocusEvent) => {
527+
const _onFocus = (event: FocusEvent) => {
524528
TextInputState.focusInput(inputRef.current);
525529
if (props.onFocus) {
526530
props.onFocus(event);
527531
}
528532
};
529533

530-
const _onBlur = (event: TextInputBlurEvent) => {
534+
const _onBlur = (event: BlurEvent) => {
531535
TextInputState.blurInput(inputRef.current);
532536
if (props.onBlur) {
533537
props.onBlur(event);

packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ import {Insets} from '../../../types/public/Insets';
1212
import {GestureResponderHandlers} from '../../../types/public/ReactNativeRenderer';
1313
import {StyleProp} from '../../StyleSheet/StyleSheet';
1414
import {ViewStyle} from '../../StyleSheet/StyleSheetTypes';
15-
import {LayoutChangeEvent, PointerEvents} from '../../Types/CoreEventTypes';
15+
import {
16+
BlurEvent,
17+
FocusEvent,
18+
LayoutChangeEvent,
19+
PointerEvents,
20+
} from '../../Types/CoreEventTypes';
1621
import {Touchable} from '../Touchable/Touchable';
1722
import {AccessibilityProps} from './ViewAccessibility';
1823

@@ -76,6 +81,20 @@ export interface ViewPropsIOS extends TVViewPropsIOS {
7681
}
7782

7883
export interface ViewPropsAndroid {
84+
/**
85+
* Callback that is called when the view is blurred.
86+
*
87+
* Note: This will only be called if the view is focusable.
88+
*/
89+
onBlur?: ((e: BlurEvent) => void) | null | undefined;
90+
91+
/**
92+
* Callback that is called when the view is focused.
93+
*
94+
* Note: This will only be called if the view is focusable.
95+
*/
96+
onFocus?: ((e: FocusEvent) => void) | null | undefined;
97+
7998
/**
8099
* Whether this view should render itself (and all of its children) into a single hardware texture on the GPU.
81100
*

packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,18 @@ const bubblingEventTypes = {
111111
bubbled: 'onClick',
112112
},
113113
},
114+
topBlur: {
115+
phasedRegistrationNames: {
116+
captured: 'onBlurCapture',
117+
bubbled: 'onBlur',
118+
},
119+
},
120+
topFocus: {
121+
phasedRegistrationNames: {
122+
captured: 'onFocusCapture',
123+
bubbled: 'onFocus',
124+
},
125+
},
114126
};
115127

116128
const directEventTypes = {

packages/react-native/Libraries/Types/CoreEventTypes.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,10 @@ export interface TargetedEvent {
248248
target: number;
249249
}
250250

251+
export type BlurEvent = NativeSyntheticEvent<TargetedEvent>;
252+
253+
export type FocusEvent = NativeSyntheticEvent<TargetedEvent>;
254+
251255
export interface PointerEvents {
252256
onPointerEnter?: ((event: PointerEvent) => void) | undefined;
253257
onPointerEnterCapture?: ((event: PointerEvent) => void) | undefined;

packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2672,13 +2672,12 @@ type TextInputContentSizeChangeEventData = $ReadOnly<{
26722672
}>;
26732673
export type TextInputContentSizeChangeEvent =
26742674
NativeSyntheticEvent<TextInputContentSizeChangeEventData>;
2675+
export type TextInputBlurEvent = BlurEvent;
2676+
export type TextInputFocusEvent = FocusEvent;
26752677
type TargetEvent = $ReadOnly<{
26762678
target: number,
26772679
...
26782680
}>;
2679-
type TextInputFocusEventData = TargetEvent;
2680-
export type TextInputBlurEvent = NativeSyntheticEvent<TextInputFocusEventData>;
2681-
export type TextInputFocusEvent = NativeSyntheticEvent<TextInputFocusEventData>;
26822681
export type Selection = $ReadOnly<{
26832682
start: number,
26842683
end: number,

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4906,6 +4906,17 @@ public abstract interface class com/facebook/react/uimanager/events/BatchEventDi
49064906
public abstract fun onBatchEventDispatched ()V
49074907
}
49084908

4909+
public final class com/facebook/react/uimanager/events/BlurEvent : com/facebook/react/uimanager/events/Event {
4910+
public static final field Companion Lcom/facebook/react/uimanager/events/BlurEvent$Companion;
4911+
public static final field EVENT_NAME Ljava/lang/String;
4912+
public fun <init> (II)V
4913+
public fun canCoalesce ()Z
4914+
public fun getEventName ()Ljava/lang/String;
4915+
}
4916+
4917+
public final class com/facebook/react/uimanager/events/BlurEvent$Companion {
4918+
}
4919+
49094920
public final class com/facebook/react/uimanager/events/ContentSizeChangeEvent : com/facebook/react/uimanager/events/Event {
49104921
public fun <init> (III)V
49114922
public fun <init> (IIII)V
@@ -4971,6 +4982,17 @@ public abstract interface class com/facebook/react/uimanager/events/EventDispatc
49714982
public abstract fun onEventDispatch (Lcom/facebook/react/uimanager/events/Event;)V
49724983
}
49734984

4985+
public final class com/facebook/react/uimanager/events/FocusEvent : com/facebook/react/uimanager/events/Event {
4986+
public static final field Companion Lcom/facebook/react/uimanager/events/FocusEvent$Companion;
4987+
public static final field EVENT_NAME Ljava/lang/String;
4988+
public fun <init> (II)V
4989+
public fun canCoalesce ()Z
4990+
public fun getEventName ()Ljava/lang/String;
4991+
}
4992+
4993+
public final class com/facebook/react/uimanager/events/FocusEvent$Companion {
4994+
}
4995+
49744996
public final class com/facebook/react/uimanager/events/NativeGestureUtil {
49754997
public static final field INSTANCE Lcom/facebook/react/uimanager/events/NativeGestureUtil;
49764998
public static final fun notifyNativeGestureEnded (Landroid/view/View;Landroid/view/MotionEvent;)V
@@ -6779,9 +6801,12 @@ public class com/facebook/react/views/view/ReactViewManager : com/facebook/react
67796801
public static final field Companion Lcom/facebook/react/views/view/ReactViewManager$Companion;
67806802
public static final field REACT_CLASS Ljava/lang/String;
67816803
public fun <init> ()V
6804+
public synthetic fun addEventEmitters (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)V
6805+
protected fun addEventEmitters (Lcom/facebook/react/uimanager/ThemedReactContext;Lcom/facebook/react/views/view/ReactViewGroup;)V
67826806
public synthetic fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Landroid/view/View;
67836807
public fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Lcom/facebook/react/views/view/ReactViewGroup;
67846808
public fun getCommandsMap ()Ljava/util/Map;
6809+
public fun getExportedCustomBubblingEventTypeConstants ()Ljava/util/Map;
67856810
public fun getName ()Ljava/lang/String;
67866811
public fun nextFocusDown (Lcom/facebook/react/views/view/ReactViewGroup;I)V
67876812
public fun nextFocusForward (Lcom/facebook/react/views/view/ReactViewGroup;I)V
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
8+
package com.facebook.react.uimanager.events
9+
10+
import com.facebook.react.bridge.Arguments
11+
import com.facebook.react.bridge.WritableMap
12+
13+
/** Represents a View losing focus */
14+
public class BlurEvent(surfaceId: Int, viewId: Int) : Event<BlurEvent>(surfaceId, viewId) {
15+
16+
override fun getEventName(): String = EVENT_NAME
17+
18+
override fun canCoalesce(): Boolean = false
19+
20+
protected override fun getEventData(): WritableMap {
21+
return Arguments.createMap().apply { putInt("target", viewTag) }
22+
}
23+
24+
public companion object {
25+
public const val EVENT_NAME: String = "topBlur"
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
8+
package com.facebook.react.uimanager.events
9+
10+
import com.facebook.react.bridge.Arguments
11+
import com.facebook.react.bridge.WritableMap
12+
13+
/** Represents a View gaining focus */
14+
public class FocusEvent(surfaceId: Int, viewId: Int) : Event<FocusEvent>(surfaceId, viewId) {
15+
16+
override fun getEventName(): String = EVENT_NAME
17+
18+
override fun canCoalesce(): Boolean = false
19+
20+
protected override fun getEventData(): WritableMap {
21+
return Arguments.createMap().apply { putInt("target", viewTag) }
22+
}
23+
24+
public companion object {
25+
public const val EVENT_NAME: String = "topFocus"
26+
}
27+
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputBlurEvent.kt

Lines changed: 0 additions & 34 deletions
This file was deleted.

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputFocusEvent.kt

Lines changed: 0 additions & 34 deletions
This file was deleted.

0 commit comments

Comments
 (0)