Skip to content

Commit 39a8fd0

Browse files
Focus TextInput when a long press takes the place of its press
The release of a press is where TextInput focuses itself, and a long press removes that release, so a held field was focused only by the platform: on Android, a field with contextMenuHidden ended focused with no keyboard. TextInput now focuses the input when onLongPress fires, as it does on a press. Without onLongPress nothing changes. The first onLongPress case now lifts its finger: the touch it left down kept the responder, so the next case's touches never reached its field.
1 parent 30ecc6f commit 39a8fd0

4 files changed

Lines changed: 40 additions & 27 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -855,8 +855,8 @@ type TextInputBaseProps = Readonly<{
855855
onKeyPress?: ?(e: TextInputKeyPressEvent) => unknown,
856856

857857
/**
858-
* Called when a touch is held for 500ms. The release that follows does not
859-
* call `onPress`.
858+
* Called when a touch is held for 500ms. The input is focused then, as on a
859+
* press, and the release that follows does not call `onPress`.
860860
*/
861861
onLongPress?: ?(event: GestureResponderEvent) => unknown,
862862

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

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -475,32 +475,38 @@ function InternalTextInput(props: TextInputProps): React.Node {
475475
rejectResponderTermination,
476476
} = props;
477477

478-
const config = useMemo(
479-
() => ({
478+
const config = useMemo(() => {
479+
const focusIfEditable = () => {
480+
if (editable !== false && inputRef.current != null) {
481+
inputRef.current.focus();
482+
}
483+
};
484+
return {
480485
cancelable: Platform.OS === 'ios' ? !rejectResponderTermination : null,
481486
hitSlop,
482-
onLongPress,
487+
onLongPress:
488+
onLongPress == null
489+
? null
490+
: (event: GestureResponderEvent) => {
491+
onLongPress(event);
492+
focusIfEditable();
493+
},
483494
onPress: (event: GestureResponderEvent) => {
484495
onPress?.(event);
485-
if (editable !== false) {
486-
if (inputRef.current != null) {
487-
inputRef.current.focus();
488-
}
489-
}
496+
focusIfEditable();
490497
},
491498
onPressIn,
492499
onPressOut,
493-
}),
494-
[
495-
editable,
496-
hitSlop,
497-
onLongPress,
498-
onPress,
499-
onPressIn,
500-
onPressOut,
501-
rejectResponderTermination,
502-
],
503-
);
500+
};
501+
}, [
502+
editable,
503+
hitSlop,
504+
onLongPress,
505+
onPress,
506+
onPressIn,
507+
onPressOut,
508+
rejectResponderTermination,
509+
]);
504510

505511
// Hide caret during test runs due to a flashing caret
506512
// makes screenshot tests flakey

‎packages/react-native/Libraries/Components/TextInput/__tests__/TextInput-itest.js‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,12 @@ describe('<TextInput>', () => {
312312

313313
timers.advanceTimersByTime(1);
314314
expect(onLongPress).toHaveBeenCalledTimes(1);
315+
316+
touchEnd(element);
317+
timers.runAllTimers();
315318
});
316319

317-
it('takes the place of the press, so the release calls no onPress and sends no focus command', () => {
320+
it('focuses the input as a press does, and the release that follows calls no onPress', () => {
318321
const root = Fantom.createRoot();
319322
const nodeRef = createRef<HostInstance>();
320323
const onPress = jest.fn();
@@ -336,14 +339,18 @@ describe('<TextInput>', () => {
336339
const element = nullthrows(nodeRef.current);
337340
touchStart(element);
338341
timers.advanceTimersByTime(500);
342+
343+
expect(root.takeMountingManagerLogs()).toEqual([
344+
'Command {type: "AndroidTextInput", nativeID: "text-input", name: "focus"}',
345+
]);
346+
339347
touchEnd(element);
340348
timers.runAllTimers();
341349

342350
expect(onPress).toHaveBeenCalledTimes(0);
343-
expect(root.takeMountingManagerLogs()).toEqual([]);
344351
});
345352

346-
it('leaves a press released before the delay a press that focuses', () => {
353+
it('does not change a press released before the delay, which calls onPress and focuses', () => {
347354
const root = Fantom.createRoot();
348355
const nodeRef = createRef<HostInstance>();
349356
const onLongPress = jest.fn();
@@ -376,7 +383,7 @@ describe('<TextInput>', () => {
376383
]);
377384
});
378385

379-
it('leaves a held press a press that focuses when it is not set', () => {
386+
it('when not set, leaves a held press calling onPress and focusing', () => {
380387
const root = Fantom.createRoot();
381388
const nodeRef = createRef<HostInstance>();
382389
const onPress = jest.fn();

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -829,8 +829,8 @@ export interface TextInputProps
829829
onEndEditing?: ((e: TextInputEndEditingEvent) => void) | undefined;
830830

831831
/**
832-
* Called when a touch is held for 500ms. The release that follows does not
833-
* call `onPress`.
832+
* Called when a touch is held for 500ms. The input is focused then, as on a
833+
* press, and the release that follows does not call `onPress`.
834834
*/
835835
onLongPress?:
836836
((e: NativeSyntheticEvent<NativeTouchEvent>) => void) | undefined;

0 commit comments

Comments
 (0)