Skip to content

Commit af0a76c

Browse files
Abbondanzofacebook-github-bot
authored andcommitted
Add support for blur and focus on View (#51570)
Summary: Pull Request resolved: #51570 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 Reviewed By: mdvacca Differential Revision: D75238291 fbshipit-source-id: b991d1f24fc094ba9d5d466201ecd058f59258e9
1 parent dd9d7c0 commit af0a76c

16 files changed

Lines changed: 168 additions & 102 deletions

File tree

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -617,24 +617,12 @@ export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
617617
export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
618618
uiViewClassName: 'AndroidTextInput',
619619
bubblingEventTypes: {
620-
topBlur: {
621-
phasedRegistrationNames: {
622-
bubbled: 'onBlur',
623-
captured: 'onBlurCapture',
624-
},
625-
},
626620
topEndEditing: {
627621
phasedRegistrationNames: {
628622
bubbled: 'onEndEditing',
629623
captured: 'onEndEditingCapture',
630624
},
631625
},
632-
topFocus: {
633-
phasedRegistrationNames: {
634-
bubbled: 'onFocus',
635-
captured: 'onFocusCapture',
636-
},
637-
},
638626
topKeyPress: {
639627
phasedRegistrationNames: {
640628
bubbled: 'onKeyPress',

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,6 +11,8 @@
1111
import type {HostInstance} from '../../../src/private/types/HostInstance';
1212
import type {____TextStyle_Internal as TextStyleInternal} from '../../StyleSheet/StyleSheetTypes';
1313
import type {
14+
BlurEvent,
15+
FocusEvent,
1416
GestureResponderEvent,
1517
ScrollEvent,
1618
} from '../../Types/CoreEventTypes';
@@ -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: 4 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,
@@ -3010,10 +3009,12 @@ export type TextInputType = InternalTextInput & TextInputComponentStatics;
30103009
exports[`public API should not change unintentionally Libraries/Components/TextInput/TextInput.js 1`] = `
30113010
"export type {
30123011
AutoCapitalize,
3012+
BlurEvent,
30133013
EnterKeyHintType,
30143014
EnterKeyHintTypeAndroid,
30153015
EnterKeyHintTypeIOS,
30163016
EnterKeyHintTypeOptions,
3017+
FocusEvent,
30173018
InputModeOptions,
30183019
KeyboardType,
30193020
KeyboardTypeAndroid,

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6777,9 +6777,12 @@ public class com/facebook/react/views/view/ReactViewManager : com/facebook/react
67776777
public static final field Companion Lcom/facebook/react/views/view/ReactViewManager$Companion;
67786778
public static final field REACT_CLASS Ljava/lang/String;
67796779
public fun <init> ()V
6780+
public synthetic fun addEventEmitters (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)V
6781+
protected fun addEventEmitters (Lcom/facebook/react/uimanager/ThemedReactContext;Lcom/facebook/react/views/view/ReactViewGroup;)V
67806782
public synthetic fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Landroid/view/View;
67816783
public fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Lcom/facebook/react/views/view/ReactViewGroup;
67826784
public fun getCommandsMap ()Ljava/util/Map;
6785+
public fun getExportedCustomBubblingEventTypeConstants ()Ljava/util/Map;
67836786
public fun getName ()Ljava/lang/String;
67846787
public fun nextFocusDown (Lcom/facebook/react/views/view/ReactViewGroup;I)V
67856788
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+
internal 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+
internal companion object {
25+
internal const val EVENT_NAME: String = "topBlur"
26+
}
27+
}

0 commit comments

Comments
 (0)