Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

26401 avoid scroll on virtual viewport #35256

Merged
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 */
shouldAwareViewportScroll?: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
shouldAwareViewportScroll?: boolean;
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,
shouldAwareViewportScroll = true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
shouldAwareViewportScroll = true,
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 isAwareViewportScroll = useTackInputFocus(shouldEnableMaxHeight && shouldAwareViewportScroll && Browser.isMobileSafari());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const isAwareViewportScroll = useTackInputFocus(shouldEnableMaxHeight && shouldAwareViewportScroll && Browser.isMobileSafari());
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}, isAwareViewportScroll && [styles.overflowAuto, styles.overscrollBehaviorContain]]}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
style={[styles.w100, styles.h100, {maxHeight}, isAwareViewportScroll && [styles.overflowAuto, styles.overscrollBehaviorContain]]}
style={[styles.w100, styles.h100, {maxHeight}, isAwareViewportScroll ? [styles.overflowAuto, styles.overscrollBehaviorContain] : {}]}

To avoid applying false to style.

behavior={keyboardAvoidingViewBehavior}
enabled={shouldEnableKeyboardAvoidingView}
>
<PickerAvoidingView
style={styles.flex1}
style={isAwareViewportScroll ? [styles.h100, {marginTop: 1}] : styles.flex1}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
style={isAwareViewportScroll ? [styles.h100, {marginTop: 1}] : 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 @@
/**
* This hook to detech input or text area focus on browser on native doesn't support DOM so default return 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';

/**
* This hook to detech input or text area focus on browser, to avoid scroll on vitual viewport
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* This hook to detech input or text area focus on browser, to avoid scroll on vitual viewport
* This hook to detect input or text area focus on browser, to avoid scroll on virtual viewport

little typo.

*/
export default function useTackInputFocus(enable = false): boolean {
const [, isInputFocusDebounced, setIsInputFocus] = useDebouncedState(false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we define the first value? Or does that throw lint errors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi @luacmartins I have updated your suggestions and when we define the first value but not using that will throw eslint error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, let's keep it empty then


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;
}
Loading