diff --git a/src/hooks/use-scroll-handlers.ts b/src/hooks/use-scroll-handlers.ts index 035af64..1e86c2b 100644 --- a/src/hooks/use-scroll-handlers.ts +++ b/src/hooks/use-scroll-handlers.ts @@ -30,6 +30,10 @@ export function resolveScrollRef(ref: any) { if (ref.current?.rlvRef) { return ref.current?.rlvRef?._scrollComponent?._scrollViewRef; } + // SectionList + if (ref.current?._wrapperListRef) { + return ref.current?._wrapperListRef?._listRef?._scrollRef + } // ScrollView return ref.current; } diff --git a/src/react-native-gesture-handler-extend/SectionListGestureComponent.tsx b/src/react-native-gesture-handler-extend/SectionListGestureComponent.tsx new file mode 100644 index 0000000..cf224e5 --- /dev/null +++ b/src/react-native-gesture-handler-extend/SectionListGestureComponent.tsx @@ -0,0 +1,103 @@ +import * as React from 'react' +import { ForwardedRef, PropsWithChildren, ReactElement, RefAttributes } from 'react' +import { SectionList as RNSectionList, SectionListProps as RNSectionListProps } from 'react-native' +import { + NativeViewGestureHandlerProps, + RefreshControl, + ScrollView +} from 'react-native-gesture-handler' + +export const nativeViewGestureHandlerProps = [ + 'shouldActivateOnStart', + 'disallowInterruption' +] as const + +const commonProps = [ + 'id', + 'enabled', + 'shouldCancelWhenOutside', + 'hitSlop', + 'cancelsTouchesInView', + 'userSelect', + 'activeCursor', + 'mouseButton', + 'enableContextMenu', + 'touchAction' +] as const + +const componentInteractionProps = ['waitFor', 'simultaneousHandlers', 'blocksHandlers'] as const + +export const baseGestureHandlerProps = [ + ...commonProps, + ...componentInteractionProps, + 'onBegan', + 'onFailed', + 'onCancelled', + 'onActivated', + 'onEnded', + 'onGestureEvent', + 'onHandlerStateChange' +] as const + +export const nativeViewProps = [ + ...baseGestureHandlerProps, + ...nativeViewGestureHandlerProps +] as const + +export const SectionList = React.forwardRef((props, ref) => { + const refreshControlGestureRef = React.useRef(null) + const { waitFor, refreshControl, ...rest } = props + const sectionListProps = {} + const scrollViewProps = {} + for (const [propName, value] of Object.entries(rest)) { + if ((nativeViewProps as readonly string[]).includes(propName)) { + // @ts-ignore - this function cannot have generic type so we have to ignore this error + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + scrollViewProps[propName] = value + } else { + // @ts-ignore - this function cannot have generic type so we have to ignore this error + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + sectionListProps[propName] = value + } + } + + return ( + // @ts-ignore - this function cannot have generic type so we have to ignore this error + ( + + )} + // @ts-ignore we don't pass `refreshing` prop as we only want to override the ref + refreshControl={ + refreshControl + ? React.cloneElement(refreshControl, { + // @ts-ignore for reasons unknown to me, `ref` doesn't exist on the type inferred by TS + ref: refreshControlGestureRef + }) + : undefined + } + /> + ) +}) as ( + props: PropsWithChildren< + RNSectionListProps & RefAttributes> & NativeViewGestureHandlerProps + >, + ref: ForwardedRef> +) => ReactElement | null +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type SectionList = typeof RNSectionList + +export function toArray(object: T | T[]): T[] { + if (!Array.isArray(object)) { + return [object] + } + return object +} diff --git a/src/views/SectionList.tsx b/src/views/SectionList.tsx new file mode 100644 index 0000000..0f2f986 --- /dev/null +++ b/src/views/SectionList.tsx @@ -0,0 +1,48 @@ +/* eslint-disable curly */ +import React, { RefObject, useImperativeHandle } from 'react' +import { Platform, SectionList as RNSectionList, SectionListProps } from 'react-native' +import { NativeViewGestureHandlerProps } from 'react-native-gesture-handler' +import { SectionList as RNGHSectionList } from '../react-native-gesture-handler-extend/SectionListGestureComponent' +import { useScrollHandlers } from '../hooks/use-scroll-handlers' + +type Props = SectionListProps & + Partial & + React.RefAttributes & { + /** + * By default refresh control gesture will work in top 15% area of the ScrollView. You can set a different value here. + * + * Accepts a value between 0-1. + */ + refreshControlGestureArea?: number + } + +function $SectionList(props: Props, ref: React.ForwardedRef>) { + const handlers = useScrollHandlers({ + hasRefreshControl: !!props.refreshControl, + refreshControlBoundary: props.refreshControlGestureArea || 0.15 + }) + useImperativeHandle(ref, () => handlers.ref) + const ScrollComponent = Platform.OS === 'web' ? RNSectionList : RNGHSectionList + + return ( + //@ts-ignore + { + handlers.onScroll(event) + props.onScroll?.(event) + }} + bounces={false} + //@ts-ignore + onLayout={(event) => { + handlers.onLayout() + props.onLayout?.(event) + }} + scrollEventThrottle={1} + /> + ) +} + +export const SectionList = React.forwardRef($SectionList) as unknown as typeof RNSectionList