Skip to content

Commit

Permalink
Merge pull request #35256 from suneox/fix/26401-scroll-on-virtual-vie…
Browse files Browse the repository at this point in the history
…wport

26401 avoid scroll on virtual viewport
  • Loading branch information
luacmartins authored Feb 7, 2024
2 parents fe61eda + c9acdde commit 82e93b8
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 2 deletions.
11 changes: 9 additions & 2 deletions src/components/ScreenWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import useEnvironment from '@hooks/useEnvironment';
import useInitialDimensions from '@hooks/useInitialWindowDimensions';
import useKeyboardState from '@hooks/useKeyboardState';
import useNetwork from '@hooks/useNetwork';
import useTackInputFocus from '@hooks/useTackInputFocus';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Browser from '@libs/Browser';
Expand Down Expand Up @@ -79,6 +80,9 @@ type ScreenWrapperProps = {
/** Whether to show offline indicator */
shouldShowOfflineIndicator?: boolean;

/** Whether to avoid scroll on virtual viewport */
shouldAvoidScrollOnVirtualViewport?: boolean;

/**
* The navigation prop is passed by the navigator. It is used to trigger the onEntryTransitionEnd callback
* when the screen transition ends.
Expand Down Expand Up @@ -109,6 +113,7 @@ function ScreenWrapper(
onEntryTransitionEnd,
testID,
navigation: navigationProp,
shouldAvoidScrollOnVirtualViewport = true,
shouldShowOfflineIndicatorInWideScreen = false,
}: ScreenWrapperProps,
ref: ForwardedRef<View>,
Expand Down Expand Up @@ -192,6 +197,8 @@ function ScreenWrapper(
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const isAvoidingViewportScroll = useTackInputFocus(shouldEnableMaxHeight && shouldAvoidScrollOnVirtualViewport && Browser.isMobileSafari());

return (
<SafeAreaConsumer>
{({insets, paddingTop, paddingBottom, safeAreaPaddingBottomStyle}) => {
Expand Down Expand Up @@ -220,12 +227,12 @@ function ScreenWrapper(
{...keyboardDissmissPanResponder.panHandlers}
>
<KeyboardAvoidingView
style={[styles.w100, styles.h100, {maxHeight}]}
style={[styles.w100, styles.h100, {maxHeight}, isAvoidingViewportScroll ? [styles.overflowAuto, styles.overscrollBehaviorContain] : {}]}
behavior={keyboardAvoidingViewBehavior}
enabled={shouldEnableKeyboardAvoidingView}
>
<PickerAvoidingView
style={styles.flex1}
style={isAvoidingViewportScroll ? [styles.h100, {marginTop: 1}] : styles.flex1}
enabled={shouldEnablePickerAvoiding}
>
<HeaderGap styles={headerGapStyles} />
Expand Down
6 changes: 6 additions & 0 deletions src/hooks/useTackInputFocus/index.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Detects input or text area focus on browser. Native doesn't support DOM so default to false
*/
export default function useTackInputFocus(): boolean {
return false;
}
49 changes: 49 additions & 0 deletions src/hooks/useTackInputFocus/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {useCallback, useEffect} from 'react';
import useDebouncedState from '@hooks/useDebouncedState';

/**
* Detects input or text area focus on browsers, to avoid scrolling on virtual viewports
*/
export default function useTackInputFocus(enable = false): boolean {
const [, isInputFocusDebounced, setIsInputFocus] = useDebouncedState(false);

const handleFocusIn = useCallback(
(event: FocusEvent) => {
const targetElement = event.target as HTMLElement;
if (targetElement.tagName === 'INPUT' || targetElement.tagName === 'TEXTAREA') {
setIsInputFocus(true);
}
},
[setIsInputFocus],
);

const handleFocusOut = useCallback(
(event: FocusEvent) => {
const targetElement = event.target as HTMLElement;
if (targetElement.tagName === 'INPUT' || targetElement.tagName === 'TEXTAREA') {
setIsInputFocus(false);
}
},
[setIsInputFocus],
);

const resetScrollPositionOnVisualViewport = useCallback(() => {
window.scrollTo({top: 0});
}, []);

useEffect(() => {
if (!enable) {
return;
}
window.addEventListener('focusin', handleFocusIn);
window.addEventListener('focusout', handleFocusOut);
window.visualViewport?.addEventListener('scroll', resetScrollPositionOnVisualViewport);
return () => {
window.removeEventListener('focusin', handleFocusIn);
window.removeEventListener('focusout', handleFocusOut);
window.visualViewport?.removeEventListener('scroll', resetScrollPositionOnVisualViewport);
};
}, [enable, handleFocusIn, handleFocusOut, resetScrollPositionOnVisualViewport]);

return isInputFocusDebounced;
}

0 comments on commit 82e93b8

Please sign in to comment.