From ba972973901a1b61481538d81254c1e558d506d0 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 26 Oct 2024 13:04:28 +0800 Subject: [PATCH 1/5] fix exit survey response page validation runs when open --- src/hooks/useAutoFocusInput.ts | 19 +++++++++++++------ src/libs/InputUtils/index.ts | 6 ++++++ src/libs/InputUtils/index.web.ts | 10 ++++++++++ .../ExitSurvey/ExitSurveyResponsePage.tsx | 14 ++------------ 4 files changed, 31 insertions(+), 18 deletions(-) create mode 100644 src/libs/InputUtils/index.ts create mode 100644 src/libs/InputUtils/index.web.ts diff --git a/src/hooks/useAutoFocusInput.ts b/src/hooks/useAutoFocusInput.ts index 5509d3635299..99ef9eb7627e 100644 --- a/src/hooks/useAutoFocusInput.ts +++ b/src/hooks/useAutoFocusInput.ts @@ -1,23 +1,24 @@ import {useFocusEffect} from '@react-navigation/native'; import {useCallback, useEffect, useRef, useState} from 'react'; import type {RefObject} from 'react'; -import type {TextInput} from 'react-native'; import {InteractionManager} from 'react-native'; +import {AnimatedTextInputRef} from '@components/RNTextInput'; +import {moveSelectionToEnd, scrollToBottom} from '@libs/InputUtils'; import CONST from '@src/CONST'; import {useSplashScreenStateContext} from '@src/SplashScreenStateContext'; type UseAutoFocusInput = { - inputCallbackRef: (ref: TextInput | null) => void; - inputRef: RefObject; + inputCallbackRef: (ref: AnimatedTextInputRef | null) => void; + inputRef: RefObject; }; -export default function useAutoFocusInput(): UseAutoFocusInput { +export default function useAutoFocusInput(isMultiline = false): UseAutoFocusInput { const [isInputInitialized, setIsInputInitialized] = useState(false); const [isScreenTransitionEnded, setIsScreenTransitionEnded] = useState(false); const {splashScreenState} = useSplashScreenStateContext(); - const inputRef = useRef(null); + const inputRef = useRef(null); const focusTimeoutRef = useRef(null); useEffect(() => { @@ -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); }); @@ -49,11 +53,14 @@ export default function useAutoFocusInput(): UseAutoFocusInput { }, []), ); - const inputCallbackRef = (ref: TextInput | null) => { + const inputCallbackRef = (ref: AnimatedTextInputRef | null) => { inputRef.current = ref; if (isInputInitialized) { return; } + if (ref && isMultiline) { + scrollToBottom(ref); + } setIsInputInitialized(true); }; diff --git a/src/libs/InputUtils/index.ts b/src/libs/InputUtils/index.ts new file mode 100644 index 000000000000..be2e528e7fd8 --- /dev/null +++ b/src/libs/InputUtils/index.ts @@ -0,0 +1,6 @@ +import type {TextInput} from 'react-native'; + +function scrollToBottom(_: TextInput) {} +function moveSelectionToEnd(_: TextInput) {} + +export {scrollToBottom, moveSelectionToEnd}; diff --git a/src/libs/InputUtils/index.web.ts b/src/libs/InputUtils/index.web.ts new file mode 100644 index 000000000000..4a8abd027045 --- /dev/null +++ b/src/libs/InputUtils/index.web.ts @@ -0,0 +1,10 @@ +function scrollToBottom(input: HTMLInputElement) { + input.scrollTop = input.scrollHeight; +} + +function moveSelectionToEnd(input: HTMLInputElement) { + const length = input.value.length; + input.setSelectionRange(length, length); +} + +export {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 From ddd3433e53873c6c9e51a83885af7aaede546ce8 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 26 Oct 2024 13:10:51 +0800 Subject: [PATCH 2/5] reveret the type --- src/hooks/useAutoFocusInput.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hooks/useAutoFocusInput.ts b/src/hooks/useAutoFocusInput.ts index 99ef9eb7627e..4a85a45acbd8 100644 --- a/src/hooks/useAutoFocusInput.ts +++ b/src/hooks/useAutoFocusInput.ts @@ -1,15 +1,15 @@ import {useFocusEffect} from '@react-navigation/native'; import {useCallback, useEffect, useRef, useState} from 'react'; import type {RefObject} from 'react'; +import type {TextInput} from 'react-native'; import {InteractionManager} from 'react-native'; -import {AnimatedTextInputRef} from '@components/RNTextInput'; import {moveSelectionToEnd, scrollToBottom} from '@libs/InputUtils'; import CONST from '@src/CONST'; import {useSplashScreenStateContext} from '@src/SplashScreenStateContext'; type UseAutoFocusInput = { - inputCallbackRef: (ref: AnimatedTextInputRef | null) => void; - inputRef: RefObject; + inputCallbackRef: (ref: TextInput | null) => void; + inputRef: RefObject; }; export default function useAutoFocusInput(isMultiline = false): UseAutoFocusInput { @@ -18,7 +18,7 @@ export default function useAutoFocusInput(isMultiline = false): UseAutoFocusInpu const {splashScreenState} = useSplashScreenStateContext(); - const inputRef = useRef(null); + const inputRef = useRef(null); const focusTimeoutRef = useRef(null); useEffect(() => { @@ -53,7 +53,7 @@ export default function useAutoFocusInput(isMultiline = false): UseAutoFocusInpu }, []), ); - const inputCallbackRef = (ref: AnimatedTextInputRef | null) => { + const inputCallbackRef = (ref: TextInput | null) => { inputRef.current = ref; if (isInputInitialized) { return; From 147b929be57520e6ee62b150a4cafae613111a13 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 26 Oct 2024 13:33:21 +0800 Subject: [PATCH 3/5] lint --- src/hooks/useAutoFocusInput.ts | 2 +- src/libs/InputUtils/index.ts | 4 ++-- src/libs/InputUtils/index.web.ts | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hooks/useAutoFocusInput.ts b/src/hooks/useAutoFocusInput.ts index 4a85a45acbd8..c8fdafd417b7 100644 --- a/src/hooks/useAutoFocusInput.ts +++ b/src/hooks/useAutoFocusInput.ts @@ -36,7 +36,7 @@ export default function useAutoFocusInput(isMultiline = false): UseAutoFocusInpu return () => { focusTaskHandle.cancel(); }; - }, [isScreenTransitionEnded, isInputInitialized, splashScreenState]); + }, [isMultiline, isScreenTransitionEnded, isInputInitialized, splashScreenState]); useFocusEffect( useCallback(() => { diff --git a/src/libs/InputUtils/index.ts b/src/libs/InputUtils/index.ts index be2e528e7fd8..aed918251782 100644 --- a/src/libs/InputUtils/index.ts +++ b/src/libs/InputUtils/index.ts @@ -1,6 +1,6 @@ import type {TextInput} from 'react-native'; -function scrollToBottom(_: TextInput) {} -function moveSelectionToEnd(_: TextInput) {} +function scrollToBottom(_input: TextInput) {} +function moveSelectionToEnd(_input: TextInput) {} export {scrollToBottom, moveSelectionToEnd}; diff --git a/src/libs/InputUtils/index.web.ts b/src/libs/InputUtils/index.web.ts index 4a8abd027045..e6ad87ce61f2 100644 --- a/src/libs/InputUtils/index.web.ts +++ b/src/libs/InputUtils/index.web.ts @@ -1,4 +1,5 @@ function scrollToBottom(input: HTMLInputElement) { + // eslint-disable-next-line no-param-reassign input.scrollTop = input.scrollHeight; } From 283c00ca7bb6981045ce07418f6c727d398af73e Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 26 Oct 2024 13:49:09 +0800 Subject: [PATCH 4/5] lint --- src/libs/InputUtils/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/InputUtils/index.ts b/src/libs/InputUtils/index.ts index aed918251782..185e83b12565 100644 --- a/src/libs/InputUtils/index.ts +++ b/src/libs/InputUtils/index.ts @@ -1,6 +1,8 @@ import type {TextInput} from 'react-native'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars function scrollToBottom(_input: TextInput) {} +// eslint-disable-next-line @typescript-eslint/no-unused-vars function moveSelectionToEnd(_input: TextInput) {} export {scrollToBottom, moveSelectionToEnd}; From 55fd5975cefbcad88fc69a7bc22b733f3392f115 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 28 Oct 2024 15:37:12 +0800 Subject: [PATCH 5/5] rename file name and extract the type --- src/libs/InputUtils/index.native.ts | 6 ++++++ src/libs/InputUtils/index.ts | 21 ++++++++++++++++----- src/libs/InputUtils/index.web.ts | 11 ----------- src/libs/InputUtils/types.ts | 6 ++++++ 4 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 src/libs/InputUtils/index.native.ts delete mode 100644 src/libs/InputUtils/index.web.ts create mode 100644 src/libs/InputUtils/types.ts 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 index 185e83b12565..19943bf3132a 100644 --- a/src/libs/InputUtils/index.ts +++ b/src/libs/InputUtils/index.ts @@ -1,8 +1,19 @@ -import type {TextInput} from 'react-native'; +import type {MoveSelectiontoEnd, ScrollToBottom} from './types'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function scrollToBottom(_input: TextInput) {} -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function moveSelectionToEnd(_input: TextInput) {} +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/index.web.ts b/src/libs/InputUtils/index.web.ts deleted file mode 100644 index e6ad87ce61f2..000000000000 --- a/src/libs/InputUtils/index.web.ts +++ /dev/null @@ -1,11 +0,0 @@ -function scrollToBottom(input: HTMLInputElement) { - // eslint-disable-next-line no-param-reassign - input.scrollTop = input.scrollHeight; -} - -function moveSelectionToEnd(input: HTMLInputElement) { - 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};