Skip to content

Commit d1226b7

Browse files
committed
fix: addressed an edge case with scrollview content sizing on initial rendering on safari
1 parent b6e405b commit d1226b7

File tree

3 files changed

+114
-9
lines changed

3 files changed

+114
-9
lines changed

src/components/bottomSheetScrollable/ScrollableContainer.web.tsx

+57-2
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,87 @@
1-
import React, { type ComponentProps, forwardRef, useCallback } from 'react';
1+
import React, {
2+
type ComponentProps,
3+
forwardRef,
4+
useCallback,
5+
useRef,
6+
} from 'react';
7+
import type { LayoutChangeEvent, ViewProps } from 'react-native';
28
import type { SimultaneousGesture } from 'react-native-gesture-handler';
39
import Animated from 'react-native-reanimated';
410
import { BottomSheetDraggableScrollable } from './BottomSheetDraggableScrollable';
511

612
interface ScrollableContainerProps {
713
nativeGesture: SimultaneousGesture;
14+
setContentSize: (contentHeight: number) => void;
815
// biome-ignore lint/suspicious/noExplicitAny: 🤷‍♂️
916
ScrollableComponent: any;
17+
onLayout: ViewProps['onLayout'];
1018
}
1119

20+
/**
21+
* Detect if the current browser is Safari or not.
22+
*/
23+
const isWebkit = () => {
24+
// @ts-ignore
25+
return navigator.userAgent.indexOf('Safari') > -1;
26+
};
27+
1228
export const ScrollableContainer = forwardRef<
1329
never,
1430
ScrollableContainerProps & { animatedProps: never }
1531
>(function ScrollableContainer(
16-
{ nativeGesture, ScrollableComponent, animatedProps, ...rest },
32+
{
33+
nativeGesture,
34+
ScrollableComponent,
35+
animatedProps,
36+
setContentSize,
37+
onLayout,
38+
...rest
39+
},
1740
ref
1841
) {
42+
//#region refs
43+
const isInitialContentHeightCaptured = useRef(false);
44+
//#endregion
45+
46+
//#region callbacks
1947
const renderScrollComponent = useCallback(
2048
(props: ComponentProps<typeof Animated.ScrollView>) => (
2149
<Animated.ScrollView {...props} animatedProps={animatedProps} />
2250
),
2351
[animatedProps]
2452
);
53+
54+
/**
55+
* A workaround a bug in React Native Web [#1502](https://github.com/necolas/react-native-web/issues/1502),
56+
* where the `onContentSizeChange` won't be call on initial render.
57+
*/
58+
const handleOnLayout = useCallback(
59+
(event: LayoutChangeEvent) => {
60+
if (onLayout) {
61+
onLayout(event);
62+
}
63+
64+
if (!isInitialContentHeightCaptured.current) {
65+
isInitialContentHeightCaptured.current = true;
66+
if (!isWebkit()) {
67+
return;
68+
}
69+
// @ts-ignore
70+
window.requestAnimationFrame(() => {
71+
// @ts-ignore
72+
setContentSize(event.nativeEvent.target.clientHeight);
73+
});
74+
}
75+
},
76+
[onLayout, setContentSize]
77+
);
78+
//#endregion
2579
return (
2680
<BottomSheetDraggableScrollable scrollableGesture={nativeGesture}>
2781
<ScrollableComponent
2882
ref={ref}
2983
{...rest}
84+
onLayout={handleOnLayout}
3085
renderScrollComponent={renderScrollComponent}
3186
/>
3287
</BottomSheetDraggableScrollable>

src/components/bottomSheetScrollable/createBottomSheetScrollableComponent.tsx

+17-7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
useStableCallback,
2020
} from '../../hooks';
2121
import { ScrollableContainer } from './ScrollableContainer';
22+
import { useBottomSheetContentSizeSetter } from './useBottomSheetContentSizeSetter';
2223

2324
export function createBottomSheetScrollableComponent<T, P>(
2425
type: SCROLLABLE_TYPE,
@@ -61,11 +62,13 @@ export function createBottomSheetScrollableComponent<T, P>(
6162
);
6263
const {
6364
animatedFooterHeight,
64-
animatedContentHeight,
6565
animatedScrollableState,
66-
enableDynamicSizing,
6766
enableContentPanningGesture,
6867
} = useBottomSheetInternal();
68+
69+
const { setContentSize } = useBottomSheetContentSizeSetter(
70+
enableFooterMarginAdjustment
71+
);
6972
//#endregion
7073

7174
if (!draggableGesture && enableContentPanningGesture) {
@@ -99,11 +102,7 @@ export function createBottomSheetScrollableComponent<T, P>(
99102
//#region callbacks
100103
const handleContentSizeChange = useStableCallback(
101104
(contentWidth: number, contentHeight: number) => {
102-
if (enableDynamicSizing) {
103-
animatedContentHeight.value =
104-
contentHeight +
105-
(enableFooterMarginAdjustment ? animatedFooterHeight.value : 0);
106-
}
105+
setContentSize(contentHeight);
107106

108107
if (onContentSizeChange) {
109108
onContentSizeChange(contentWidth, contentHeight);
@@ -158,6 +157,17 @@ export function createBottomSheetScrollableComponent<T, P>(
158157
onRefresh={onRefresh}
159158
onScroll={scrollHandler}
160159
onContentSizeChange={handleContentSizeChange}
160+
// onLayout={e => {
161+
// window.requestAnimationFrame(() => {
162+
// console.log(
163+
// 'XX test',
164+
// e.nativeEvent.target.clientHeight,
165+
// e.nativeEvent.target.scrollHeight
166+
// );
167+
// window.dispatchEvent(new Event('resize'));
168+
// });
169+
// }}
170+
setContentSize={setContentSize}
161171
ScrollableComponent={ScrollableComponent}
162172
refreshControl={refreshControl}
163173
{...rest}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useCallback } from 'react';
2+
import { useBottomSheetInternal } from '../../hooks';
3+
4+
/**
5+
* A hook to set the content size properly into the bottom sheet,
6+
* internals.
7+
*
8+
* @param enableFooterMarginAdjustment Adjust the scrollable bottom margin to avoid the animated footer.
9+
* @returns
10+
*/
11+
export function useBottomSheetContentSizeSetter(
12+
enableFooterMarginAdjustment: boolean
13+
) {
14+
//#region hooks
15+
const { enableDynamicSizing, animatedContentHeight, animatedFooterHeight } =
16+
useBottomSheetInternal();
17+
//#endregion
18+
19+
//#region methods
20+
const setContentSize = useCallback(
21+
(contentHeight: number) => {
22+
if (enableDynamicSizing) {
23+
animatedContentHeight.value =
24+
contentHeight +
25+
(enableFooterMarginAdjustment ? animatedFooterHeight.value : 0);
26+
}
27+
},
28+
[
29+
enableDynamicSizing,
30+
animatedContentHeight,
31+
animatedFooterHeight,
32+
enableFooterMarginAdjustment,
33+
]
34+
);
35+
//#endregion
36+
37+
return {
38+
setContentSize,
39+
};
40+
}

0 commit comments

Comments
 (0)