|
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'; |
2 | 8 | import type { SimultaneousGesture } from 'react-native-gesture-handler';
|
3 | 9 | import Animated from 'react-native-reanimated';
|
4 | 10 | import { BottomSheetDraggableScrollable } from './BottomSheetDraggableScrollable';
|
5 | 11 |
|
6 | 12 | interface ScrollableContainerProps {
|
7 | 13 | nativeGesture: SimultaneousGesture;
|
| 14 | + setContentSize: (contentHeight: number) => void; |
8 | 15 | // biome-ignore lint/suspicious/noExplicitAny: 🤷♂️
|
9 | 16 | ScrollableComponent: any;
|
| 17 | + onLayout: ViewProps['onLayout']; |
10 | 18 | }
|
11 | 19 |
|
| 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 | + |
12 | 28 | export const ScrollableContainer = forwardRef<
|
13 | 29 | never,
|
14 | 30 | ScrollableContainerProps & { animatedProps: never }
|
15 | 31 | >(function ScrollableContainer(
|
16 |
| - { nativeGesture, ScrollableComponent, animatedProps, ...rest }, |
| 32 | + { |
| 33 | + nativeGesture, |
| 34 | + ScrollableComponent, |
| 35 | + animatedProps, |
| 36 | + setContentSize, |
| 37 | + onLayout, |
| 38 | + ...rest |
| 39 | + }, |
17 | 40 | ref
|
18 | 41 | ) {
|
| 42 | + //#region refs |
| 43 | + const isInitialContentHeightCaptured = useRef(false); |
| 44 | + //#endregion |
| 45 | + |
| 46 | + //#region callbacks |
19 | 47 | const renderScrollComponent = useCallback(
|
20 | 48 | (props: ComponentProps<typeof Animated.ScrollView>) => (
|
21 | 49 | <Animated.ScrollView {...props} animatedProps={animatedProps} />
|
22 | 50 | ),
|
23 | 51 | [animatedProps]
|
24 | 52 | );
|
| 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 |
25 | 79 | return (
|
26 | 80 | <BottomSheetDraggableScrollable scrollableGesture={nativeGesture}>
|
27 | 81 | <ScrollableComponent
|
28 | 82 | ref={ref}
|
29 | 83 | {...rest}
|
| 84 | + onLayout={handleOnLayout} |
30 | 85 | renderScrollComponent={renderScrollComponent}
|
31 | 86 | />
|
32 | 87 | </BottomSheetDraggableScrollable>
|
|
0 commit comments