Skip to content

Commit 52a1891

Browse files
authored
Merge pull request #21371 from akinwale/task-20836
2 parents 82558a1 + 812bbc5 commit 52a1891

File tree

3 files changed

+30
-40
lines changed

3 files changed

+30
-40
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Focus a multiline text input and place the cursor at the end of the value (if there is a value in the input).
3+
*
4+
* When a multiline input contains a text value that goes beyond the scroll height, the cursor will be placed
5+
* at the end of the text value, and automatically scroll the input field to this position after the field gains
6+
* focus. This provides a better user experience in cases where the text in the field has to be edited. The auto-
7+
* scroll behaviour works on all platforms except iOS native.
8+
* See https://github.com/Expensify/App/issues/20836 for more details.
9+
*
10+
* @param {Object} input the input element
11+
*/
12+
export default function focusAndUpdateMultilineInputRange(input) {
13+
if (!input) {
14+
return;
15+
}
16+
17+
input.focus();
18+
if (input.value && input.setSelectionRange) {
19+
const length = input.value.length;
20+
input.setSelectionRange(length, length);
21+
// eslint-disable-next-line no-param-reassign
22+
input.scrollTop = input.scrollHeight;
23+
}
24+
}

src/pages/tasks/NewTaskDescriptionPage.js

+3-23
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useEffect, useRef, useState} from 'react';
1+
import React, {useRef} from 'react';
22
import {View} from 'react-native';
33
import {withOnyx} from 'react-native-onyx';
44
import PropTypes from 'prop-types';
@@ -14,6 +14,7 @@ import TextInput from '../../components/TextInput';
1414
import Permissions from '../../libs/Permissions';
1515
import ROUTES from '../../ROUTES';
1616
import * as TaskUtils from '../../libs/actions/Task';
17+
import focusAndUpdateMultilineInputRange from '../../libs/focusAndUpdateMultilineInputRange';
1718

1819
const propTypes = {
1920
/** Beta features list */
@@ -38,17 +39,6 @@ const defaultProps = {
3839
function NewTaskDescriptionPage(props) {
3940
const inputRef = useRef(null);
4041

41-
// The selection will be used to place the cursor at the end if there is prior text in the text input area
42-
const [selection, setSelection] = useState({start: 0, end: 0});
43-
44-
// eslint-disable-next-line rulesdir/prefer-early-return
45-
useEffect(() => {
46-
if (props.task.description) {
47-
const length = props.task.description.length;
48-
setSelection({start: length, end: length});
49-
}
50-
}, [props.task.description]);
51-
5242
// On submit, we want to call the assignTask function and wait to validate
5343
// the response
5444
const onSubmit = (values) => {
@@ -63,13 +53,7 @@ function NewTaskDescriptionPage(props) {
6353
return (
6454
<ScreenWrapper
6555
includeSafeAreaPaddingBottom={false}
66-
onEntryTransitionEnd={() => {
67-
if (!inputRef.current) {
68-
return;
69-
}
70-
71-
inputRef.current.focus();
72-
}}
56+
onEntryTransitionEnd={() => focusAndUpdateMultilineInputRange(inputRef.current)}
7357
>
7458
<HeaderWithBackButton
7559
title={props.translate('newTaskPage.description')}
@@ -93,10 +77,6 @@ function NewTaskDescriptionPage(props) {
9377
submitOnEnter
9478
containerStyles={[styles.autoGrowHeightMultilineInput]}
9579
textAlignVertical="top"
96-
selection={selection}
97-
onSelectionChange={(e) => {
98-
setSelection(e.nativeEvent.selection);
99-
}}
10080
/>
10181
</View>
10282
</Form>

src/pages/tasks/TaskDescriptionPage.js

+3-17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useCallback, useEffect, useRef, useState} from 'react';
1+
import React, {useCallback, useRef} from 'react';
22
import PropTypes from 'prop-types';
33
import {View} from 'react-native';
44
import {withOnyx} from 'react-native-onyx';
@@ -12,6 +12,7 @@ import styles from '../../styles/styles';
1212
import compose from '../../libs/compose';
1313
import reportPropTypes from '../reportPropTypes';
1414
import * as TaskUtils from '../../libs/actions/Task';
15+
import focusAndUpdateMultilineInputRange from '../../libs/focusAndUpdateMultilineInputRange';
1516

1617
const propTypes = {
1718
/** Current user session */
@@ -48,21 +49,10 @@ function TaskDescriptionPage(props) {
4849

4950
const inputRef = useRef(null);
5051

51-
// Same as NewtaskDescriptionPage, use the selection to place the cursor correctly if there is prior text
52-
const [selection, setSelection] = useState({start: 0, end: 0});
53-
54-
// eslint-disable-next-line rulesdir/prefer-early-return
55-
useEffect(() => {
56-
if (props.task.report && props.task.report.description) {
57-
const length = props.task.report.description.length;
58-
setSelection({start: length, end: length});
59-
}
60-
}, [props.task.report]);
61-
6252
return (
6353
<ScreenWrapper
6454
includeSafeAreaPaddingBottom={false}
65-
onEntryTransitionEnd={() => inputRef.current && inputRef.current.focus()}
55+
onEntryTransitionEnd={() => focusAndUpdateMultilineInputRange(inputRef.current)}
6656
>
6757
<HeaderWithBackButton title={props.translate('newTaskPage.task')} />
6858
<Form
@@ -84,10 +74,6 @@ function TaskDescriptionPage(props) {
8474
submitOnEnter
8575
containerStyles={[styles.autoGrowHeightMultilineInput]}
8676
textAlignVertical="top"
87-
selection={selection}
88-
onSelectionChange={(e) => {
89-
setSelection(e.nativeEvent.selection);
90-
}}
9177
/>
9278
</View>
9379
</Form>

0 commit comments

Comments
 (0)