diff --git a/src/hooks/useAutoFocusInput.js b/src/hooks/useAutoFocusInput.ts similarity index 73% rename from src/hooks/useAutoFocusInput.js rename to src/hooks/useAutoFocusInput.ts index b65b5242fa32..ad66a01b157d 100644 --- a/src/hooks/useAutoFocusInput.js +++ b/src/hooks/useAutoFocusInput.ts @@ -1,24 +1,29 @@ import {useFocusEffect} from '@react-navigation/native'; import {useCallback, useContext, useEffect, useRef, useState} from 'react'; -import {InteractionManager} from 'react-native'; +import {InteractionManager, TextInput} from 'react-native'; import CONST from '@src/CONST'; import * as Expensify from '@src/Expensify'; -export default function useAutoFocusInput() { +type UseAutoFocusInput = { + inputCallbackRef: (ref: TextInput | null) => void; +}; + +export default function useAutoFocusInput(): UseAutoFocusInput { const [isInputInitialized, setIsInputInitialized] = useState(false); const [isScreenTransitionEnded, setIsScreenTransitionEnded] = useState(false); + // @ts-expect-error TODO: Remove this when Expensify.js is migrated. const {isSplashHidden} = useContext(Expensify.SplashScreenHiddenContext); - const inputRef = useRef(null); - const focusTimeoutRef = useRef(null); + const inputRef = useRef(null); + const focusTimeoutRef = useRef(null); useEffect(() => { if (!isScreenTransitionEnded || !isInputInitialized || !inputRef.current || !isSplashHidden) { return; } InteractionManager.runAfterInteractions(() => { - inputRef.current.focus(); + inputRef.current?.focus(); setIsScreenTransitionEnded(false); }); }, [isScreenTransitionEnded, isInputInitialized, isSplashHidden]); @@ -38,7 +43,7 @@ export default function useAutoFocusInput() { }, []), ); - const inputCallbackRef = (ref) => { + const inputCallbackRef = (ref: TextInput | null) => { inputRef.current = ref; setIsInputInitialized(true); }; diff --git a/src/hooks/useDelayedInputFocus.js b/src/hooks/useDelayedInputFocus.ts similarity index 62% rename from src/hooks/useDelayedInputFocus.js rename to src/hooks/useDelayedInputFocus.ts index 7a4a64104e48..d062f8a03d25 100644 --- a/src/hooks/useDelayedInputFocus.js +++ b/src/hooks/useDelayedInputFocus.ts @@ -1,19 +1,17 @@ import {useFocusEffect} from '@react-navigation/native'; -import {useCallback, useRef} from 'react'; +import {MutableRefObject, useCallback, useRef} from 'react'; +import {TextInput} from 'react-native'; import CONST from '@src/CONST'; /** * Focus a text input when a screen is navigated to, after the specified time delay has elapsed. - * - * @param {Object} inputRef - * @param {Number} [delay] */ -export default function useDelayedInputFocus(inputRef, delay = CONST.ANIMATED_TRANSITION) { - const timeoutRef = useRef(null); +export default function useDelayedInputFocus(inputRef: MutableRefObject, delay: number = CONST.ANIMATED_TRANSITION) { + const timeoutRef = useRef(null); useFocusEffect( useCallback(() => { - timeoutRef.current = setTimeout(() => inputRef.current && inputRef.current.focus(), delay); + timeoutRef.current = setTimeout(() => inputRef.current?.focus(), delay); return () => { if (!timeoutRef.current) { return; diff --git a/src/hooks/useInitialWindowDimensions/index.js b/src/hooks/useInitialWindowDimensions/index.ts similarity index 80% rename from src/hooks/useInitialWindowDimensions/index.js rename to src/hooks/useInitialWindowDimensions/index.ts index 5878c8b3371f..e0882e820e9b 100644 --- a/src/hooks/useInitialWindowDimensions/index.js +++ b/src/hooks/useInitialWindowDimensions/index.ts @@ -1,14 +1,24 @@ // eslint-disable-next-line no-restricted-imports import {useEffect, useState} from 'react'; -import {Dimensions} from 'react-native'; +import {Dimensions, type ScaledSize} from 'react-native'; + +type InitialWindowDimensions = { + initialWidth: number; + initialHeight: number; +}; + +type NewDimensions = { + window: ScaledSize; + screen: ScaledSize; +}; /** * A convenience hook that provides initial size (width and height). * An initial height allows to know the real height of window, * while the standard useWindowDimensions hook return the height minus Virtual keyboard height - * @returns {Object} with information about initial width and height + * @returns with information about initial width and height */ -export default function () { +export default function (): InitialWindowDimensions { const [dimensions, setDimensions] = useState(() => { const window = Dimensions.get('window'); const screen = Dimensions.get('screen'); @@ -22,7 +32,7 @@ export default function () { }); useEffect(() => { - const onDimensionChange = (newDimensions) => { + const onDimensionChange = (newDimensions: NewDimensions) => { const {window, screen} = newDimensions; setDimensions((oldState) => { diff --git a/src/hooks/useTabNavigatorFocus/index.js b/src/hooks/useTabNavigatorFocus/index.ts similarity index 83% rename from src/hooks/useTabNavigatorFocus/index.js rename to src/hooks/useTabNavigatorFocus/index.ts index f83ec5bd9270..9138cbbdab48 100644 --- a/src/hooks/useTabNavigatorFocus/index.js +++ b/src/hooks/useTabNavigatorFocus/index.ts @@ -1,8 +1,17 @@ import {useTabAnimation} from '@react-navigation/material-top-tabs'; import {useIsFocused} from '@react-navigation/native'; import {useEffect, useState} from 'react'; +import {Animated} from 'react-native'; import DomUtils from '@libs/DomUtils'; +type UseTabNavigatorFocusParams = { + tabIndex: number; +}; + +type PositionAnimationListenerCallback = { + value: number; +}; + /** * Custom React hook to determine the focus status of a tab in a Material Top Tab Navigator. * It evaluates whether the current tab is focused based on the tab's animation position and @@ -17,15 +26,16 @@ import DomUtils from '@libs/DomUtils'; * might not be used within a Material Top Tabs Navigator context. Proper usage should ensure that * this hook is only used where appropriate. * - * @param {Object} params - The parameters object. - * @param {Number} params.tabIndex - The index of the tab for which focus status is being determined. - * @returns {Boolean} Returns `true` if the tab is both animation-focused and screen-focused, otherwise `false`. + * @param params - The parameters object. + * @param params.tabIndex - The index of the tab for which focus status is being determined. + * @returns Returns `true` if the tab is both animation-focused and screen-focused, otherwise `false`. * * @example * const isTabFocused = useTabNavigatorFocus({ tabIndex: 1 }); */ -function useTabNavigatorFocus({tabIndex}) { - let tabPositionAnimation = null; +function useTabNavigatorFocus({tabIndex}: UseTabNavigatorFocusParams): boolean { + let tabPositionAnimation: Animated.AnimatedInterpolation | null = null; + try { // Retrieve the animation value from the tab navigator, which ranges from 0 to the total number of pages displayed. // Even a minimal scroll towards the camera page (e.g., a value of 0.001 at start) should activate the camera for immediate responsiveness. @@ -46,7 +56,7 @@ function useTabNavigatorFocus({tabIndex}) { } const index = Number(tabIndex); - const listenerId = tabPositionAnimation.addListener(({value}) => { + const listenerId = tabPositionAnimation.addListener(({value}: PositionAnimationListenerCallback) => { // Activate camera as soon the index is animating towards the `tabIndex` DomUtils.requestAnimationFrame(() => { setIsTabFocused(value > index - 1 && value < index + 1); @@ -56,6 +66,7 @@ function useTabNavigatorFocus({tabIndex}) { // We need to get the position animation value on component initialization to determine // if the tab is focused or not. Since it's an Animated.Value the only synchronous way // to retrieve the value is to use a private method. + // @ts-expect-error -- __getValue is a private method // eslint-disable-next-line no-underscore-dangle const initialTabPositionValue = tabPositionAnimation.__getValue(); diff --git a/src/types/modules/material-top-tabs.d.ts b/src/types/modules/material-top-tabs.d.ts new file mode 100644 index 000000000000..693d82d07c57 --- /dev/null +++ b/src/types/modules/material-top-tabs.d.ts @@ -0,0 +1,8 @@ +/** This direct import is required, because this function was added by a patch, + * and its typings are not supported by default */ +import {useTabAnimation} from '@react-navigation/material-top-tabs/src/utils/useTabAnimation'; + +declare module '@react-navigation/material-top-tabs' { + // eslint-disable-next-line import/prefer-default-export + export {useTabAnimation}; +}