Skip to content

Commit

Permalink
fix: addressed an edge case with scrollview content sizing on initial…
Browse files Browse the repository at this point in the history
… rendering on safari
  • Loading branch information
gorhom committed Oct 5, 2024
1 parent b6e405b commit d1226b7
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 9 deletions.
59 changes: 57 additions & 2 deletions src/components/bottomSheetScrollable/ScrollableContainer.web.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,87 @@
import React, { type ComponentProps, forwardRef, useCallback } from 'react';
import React, {
type ComponentProps,
forwardRef,
useCallback,
useRef,
} from 'react';
import type { LayoutChangeEvent, ViewProps } from 'react-native';
import type { SimultaneousGesture } from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
import { BottomSheetDraggableScrollable } from './BottomSheetDraggableScrollable';

interface ScrollableContainerProps {
nativeGesture: SimultaneousGesture;
setContentSize: (contentHeight: number) => void;
// biome-ignore lint/suspicious/noExplicitAny: 🤷‍♂️
ScrollableComponent: any;
onLayout: ViewProps['onLayout'];
}

/**
* Detect if the current browser is Safari or not.
*/
const isWebkit = () => {
// @ts-ignore
return navigator.userAgent.indexOf('Safari') > -1;
};

export const ScrollableContainer = forwardRef<
never,
ScrollableContainerProps & { animatedProps: never }
>(function ScrollableContainer(
{ nativeGesture, ScrollableComponent, animatedProps, ...rest },
{
nativeGesture,
ScrollableComponent,
animatedProps,
setContentSize,
onLayout,
...rest
},
ref
) {
//#region refs
const isInitialContentHeightCaptured = useRef(false);
//#endregion

//#region callbacks
const renderScrollComponent = useCallback(
(props: ComponentProps<typeof Animated.ScrollView>) => (
<Animated.ScrollView {...props} animatedProps={animatedProps} />
),
[animatedProps]
);

/**
* A workaround a bug in React Native Web [#1502](https://github.com/necolas/react-native-web/issues/1502),
* where the `onContentSizeChange` won't be call on initial render.
*/
const handleOnLayout = useCallback(
(event: LayoutChangeEvent) => {
if (onLayout) {
onLayout(event);
}

if (!isInitialContentHeightCaptured.current) {
isInitialContentHeightCaptured.current = true;
if (!isWebkit()) {
return;
}
// @ts-ignore
window.requestAnimationFrame(() => {
// @ts-ignore
setContentSize(event.nativeEvent.target.clientHeight);
});
}
},
[onLayout, setContentSize]
);
//#endregion
return (
<BottomSheetDraggableScrollable scrollableGesture={nativeGesture}>
<ScrollableComponent
ref={ref}
{...rest}
onLayout={handleOnLayout}
renderScrollComponent={renderScrollComponent}
/>
</BottomSheetDraggableScrollable>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
useStableCallback,
} from '../../hooks';
import { ScrollableContainer } from './ScrollableContainer';
import { useBottomSheetContentSizeSetter } from './useBottomSheetContentSizeSetter';

export function createBottomSheetScrollableComponent<T, P>(
type: SCROLLABLE_TYPE,
Expand Down Expand Up @@ -61,11 +62,13 @@ export function createBottomSheetScrollableComponent<T, P>(
);
const {
animatedFooterHeight,
animatedContentHeight,
animatedScrollableState,
enableDynamicSizing,
enableContentPanningGesture,
} = useBottomSheetInternal();

const { setContentSize } = useBottomSheetContentSizeSetter(
enableFooterMarginAdjustment
);
//#endregion

if (!draggableGesture && enableContentPanningGesture) {
Expand Down Expand Up @@ -99,11 +102,7 @@ export function createBottomSheetScrollableComponent<T, P>(
//#region callbacks
const handleContentSizeChange = useStableCallback(
(contentWidth: number, contentHeight: number) => {
if (enableDynamicSizing) {
animatedContentHeight.value =
contentHeight +
(enableFooterMarginAdjustment ? animatedFooterHeight.value : 0);
}
setContentSize(contentHeight);

if (onContentSizeChange) {
onContentSizeChange(contentWidth, contentHeight);
Expand Down Expand Up @@ -158,6 +157,17 @@ export function createBottomSheetScrollableComponent<T, P>(
onRefresh={onRefresh}
onScroll={scrollHandler}
onContentSizeChange={handleContentSizeChange}
// onLayout={e => {
// window.requestAnimationFrame(() => {
// console.log(
// 'XX test',
// e.nativeEvent.target.clientHeight,
// e.nativeEvent.target.scrollHeight
// );
// window.dispatchEvent(new Event('resize'));
// });
// }}
setContentSize={setContentSize}
ScrollableComponent={ScrollableComponent}
refreshControl={refreshControl}
{...rest}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useCallback } from 'react';
import { useBottomSheetInternal } from '../../hooks';

/**
* A hook to set the content size properly into the bottom sheet,
* internals.
*
* @param enableFooterMarginAdjustment Adjust the scrollable bottom margin to avoid the animated footer.
* @returns
*/
export function useBottomSheetContentSizeSetter(
enableFooterMarginAdjustment: boolean
) {
//#region hooks
const { enableDynamicSizing, animatedContentHeight, animatedFooterHeight } =
useBottomSheetInternal();
//#endregion

//#region methods
const setContentSize = useCallback(
(contentHeight: number) => {
if (enableDynamicSizing) {
animatedContentHeight.value =
contentHeight +
(enableFooterMarginAdjustment ? animatedFooterHeight.value : 0);
}
},
[
enableDynamicSizing,
animatedContentHeight,
animatedFooterHeight,
enableFooterMarginAdjustment,
]
);
//#endregion

return {
setContentSize,
};
}

0 comments on commit d1226b7

Please sign in to comment.