diff --git a/src/hooks/useAutoFocusInput.ts b/src/hooks/useAutoFocusInput.ts index 5509d3635299..c8fdafd417b7 100644 --- a/src/hooks/useAutoFocusInput.ts +++ b/src/hooks/useAutoFocusInput.ts @@ -3,6 +3,7 @@ import {useCallback, useEffect, useRef, useState} from 'react'; import type {RefObject} from 'react'; import type {TextInput} from 'react-native'; import {InteractionManager} from 'react-native'; +import {moveSelectionToEnd, scrollToBottom} from '@libs/InputUtils'; import CONST from '@src/CONST'; import {useSplashScreenStateContext} from '@src/SplashScreenStateContext'; @@ -11,7 +12,7 @@ type UseAutoFocusInput = { inputRef: RefObject; }; -export default function useAutoFocusInput(): UseAutoFocusInput { +export default function useAutoFocusInput(isMultiline = false): UseAutoFocusInput { const [isInputInitialized, setIsInputInitialized] = useState(false); const [isScreenTransitionEnded, setIsScreenTransitionEnded] = useState(false); @@ -25,6 +26,9 @@ export default function useAutoFocusInput(): UseAutoFocusInput { return; } const focusTaskHandle = InteractionManager.runAfterInteractions(() => { + if (inputRef.current && isMultiline) { + moveSelectionToEnd(inputRef.current); + } inputRef.current?.focus(); setIsScreenTransitionEnded(false); }); @@ -32,7 +36,7 @@ export default function useAutoFocusInput(): UseAutoFocusInput { return () => { focusTaskHandle.cancel(); }; - }, [isScreenTransitionEnded, isInputInitialized, splashScreenState]); + }, [isMultiline, isScreenTransitionEnded, isInputInitialized, splashScreenState]); useFocusEffect( useCallback(() => { @@ -54,6 +58,9 @@ export default function useAutoFocusInput(): UseAutoFocusInput { if (isInputInitialized) { return; } + if (ref && isMultiline) { + scrollToBottom(ref); + } setIsInputInitialized(true); }; diff --git a/src/libs/InputUtils/index.native.ts b/src/libs/InputUtils/index.native.ts new file mode 100644 index 000000000000..3db4280b7d73 --- /dev/null +++ b/src/libs/InputUtils/index.native.ts @@ -0,0 +1,6 @@ +import type {MoveSelectiontoEnd, ScrollToBottom} from './types'; + +const scrollToBottom: ScrollToBottom = () => {}; +const moveSelectionToEnd: MoveSelectiontoEnd = () => {}; + +export {scrollToBottom, moveSelectionToEnd}; diff --git a/src/libs/InputUtils/index.ts b/src/libs/InputUtils/index.ts new file mode 100644 index 000000000000..19943bf3132a --- /dev/null +++ b/src/libs/InputUtils/index.ts @@ -0,0 +1,19 @@ +import type {MoveSelectiontoEnd, ScrollToBottom} from './types'; + +const scrollToBottom: ScrollToBottom = (input) => { + if (!('scrollTop' in input)) { + return; + } + // eslint-disable-next-line no-param-reassign + input.scrollTop = input.scrollHeight; +}; + +const moveSelectionToEnd: MoveSelectiontoEnd = (input) => { + if (!('setSelectionRange' in input)) { + return; + } + const length = input.value.length; + input.setSelectionRange(length, length); +}; + +export {scrollToBottom, moveSelectionToEnd}; diff --git a/src/libs/InputUtils/types.ts b/src/libs/InputUtils/types.ts new file mode 100644 index 000000000000..875ac6b602e4 --- /dev/null +++ b/src/libs/InputUtils/types.ts @@ -0,0 +1,6 @@ +import type {TextInput} from 'react-native'; + +type ScrollToBottom = (input: HTMLInputElement | TextInput) => void; +type MoveSelectiontoEnd = (input: HTMLInputElement | TextInput) => void; + +export type {ScrollToBottom, MoveSelectiontoEnd}; diff --git a/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx b/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx index 1fb7cfbc94ab..82f245e2dd96 100644 --- a/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx +++ b/src/pages/settings/ExitSurvey/ExitSurveyResponsePage.tsx @@ -4,7 +4,6 @@ import {useOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; @@ -18,7 +17,6 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import StatusBar from '@libs/StatusBar'; -import updateMultilineInputRange from '@libs/updateMultilineInputRange'; import Navigation from '@navigation/Navigation'; import type {SettingsNavigatorParamList} from '@navigation/types'; import variables from '@styles/variables'; @@ -40,7 +38,7 @@ function ExitSurveyResponsePage({route, navigation}: ExitSurveyResponsePageProps const StyleUtils = useStyleUtils(); const {keyboardHeight} = useKeyboardState(); const {windowHeight} = useWindowDimensions(); - const {inputCallbackRef, inputRef} = useAutoFocusInput(); + const {inputCallbackRef} = useAutoFocusInput(true); // Device safe area top and bottom insets. // When the keyboard is shown, the bottom inset doesn't affect the height, so we take it out from the calculation. @@ -120,15 +118,7 @@ function ExitSurveyResponsePage({route, navigation}: ExitSurveyResponsePageProps autoGrowHeight maxAutoGrowHeight={variables.textInputAutoGrowMaxHeight} maxLength={CONST.MAX_COMMENT_LENGTH} - ref={(el: AnimatedTextInputRef) => { - if (!el) { - return; - } - if (!inputRef.current) { - updateMultilineInputRange(el); - } - inputCallbackRef(el); - }} + ref={inputCallbackRef} containerStyles={[baseResponseInputContainerStyle]} shouldSaveDraft shouldSubmitForm