diff --git a/src/Wishlist.tsx b/src/Wishlist.tsx index 1a6e931..3c8b574 100644 --- a/src/Wishlist.tsx +++ b/src/Wishlist.tsx @@ -79,10 +79,36 @@ type Props = ViewProps & { onEndReached?: () => void; initialIndex?: number; contentContainerStyle?: StyleProp | undefined; + /** + * Rendered at the bottom of all the items. Can be a React Component Class, a render function, or + * a rendered element. + */ + ListFooterComponent?: + | React.ComponentType + | React.ReactElement + | null + | undefined; + /** + * Rendered at the top of all the items. Can be a React Component Class, a render function, or + * a rendered element. + */ + ListHeaderComponent?: + | React.ComponentType + | React.ReactElement + | null + | undefined; }; function ComponentBase( - { children, style, data, contentContainerStyle, ...rest }: Props, + { + children, + style, + data, + contentContainerStyle, + ListFooterComponent, + ListHeaderComponent, + ...rest + }: Props, ref: React.Ref, ) { const nativeWishlist = useRef | null>( @@ -123,6 +149,28 @@ function ComponentBase( [children, width], ); + if (ListHeaderComponent) { + childrenTemplates.__wishlistHeader = React.isValidElement( + ListHeaderComponent, + ) ? ( + ListHeaderComponent + ) : ( + // @ts-expect-error + + ); + } + + if (ListFooterComponent) { + childrenTemplates.__wishlistFooter = React.isValidElement( + ListFooterComponent, + ) ? ( + ListFooterComponent + ) : ( + // @ts-expect-error + + ); + } + const templatesRegistry = useMemo( () => ({ templates: {}, @@ -137,6 +185,9 @@ function ComponentBase( [], ); + const hasHeader = !!ListHeaderComponent; + const hasFooter = !!ListFooterComponent; + // Resolve inflator - either use the provided callback or use the mapping const resolvedInflater: InflateMethod = useMemo(() => { return ( @@ -145,7 +196,17 @@ function ComponentBase( previousItem: TemplateItem | null, ) => { 'worklet'; - const value = (data as WishlistDataInternal).__at(index); + const internalData = data as WishlistDataInternal; + + let value: T | undefined; + if (hasHeader && index === internalData.__firstIndex() - 1) { + value = { key: '__wishlistHeader', type: '__wishlistHeader' } as T; + } else if (hasFooter && index === internalData.__lastIndex()) { + value = { key: '__wishlistFooter', type: '__wishlistFooter' } as T; + } else { + value = internalData.__at(index); + } + if (!value) { return undefined; } @@ -170,7 +231,7 @@ function ComponentBase( return [item, value]; }; - }, [data]); + }, [data, hasFooter, hasHeader]); const inflatorIdRef = useRef(null); const prevInflatorRef = useRef(); @@ -222,7 +283,6 @@ function ComponentBase( ))} - ( templates={childrenTemplates} nestedTemplates={templatesRegistry.templates} contentContainerStyle={contentContainerStyle} + initialIndex={rest.initialIndex ?? (hasHeader ? -1 : 0)} /> @@ -251,6 +312,7 @@ type InnerComponentProps = ViewProps & { templates: { [key: string]: any }; nestedTemplates: { [key: string]: any }; contentContainerStyle?: StyleProp | undefined; + initialIndex: number; }; function InnerComponent({ @@ -261,8 +323,12 @@ function InnerComponent({ templates, nestedTemplates, contentContainerStyle, + initialIndex, }: InnerComponentProps) { - const combinedTemplates = { ...templates, ...nestedTemplates }; + const combinedTemplates: { [key: string]: React.ReactElement } = { + ...templates, + ...nestedTemplates, + }; const { id } = useWishlistContext(); @@ -281,14 +347,13 @@ function InnerComponent({ inflatorId={inflatorId} onEndReached={rest?.onEndReached} onStartReached={rest?.onStartReached} - initialIndex={rest.initialIndex ?? 0} + initialIndex={initialIndex} > - extends WishlistData { __attach: (wishlistId: string) => void; __detach: (wishlistId: string) => void; __at: (index: number) => T | undefined; + __firstIndex: () => number; + __lastIndex: () => number; } /** @@ -64,6 +66,17 @@ export function useWishlistData( return currentlyRenderedCopy.at(index); } + function __firstIndex() { + return 0 - currentlyRenderedCopy.__numberOfNegative; + } + + function __lastIndex() { + return ( + currentlyRenderedCopy.length - + currentlyRenderedCopy.__numberOfNegative + ); + } + function __attach(wishlistId: string) { attachedListIds.add(wishlistId); @@ -92,6 +105,8 @@ export function useWishlistData( __at, __attach, __detach, + __firstIndex, + __lastIndex, }; global.dataCtx[dataId] = internalData; @@ -102,7 +117,7 @@ export function useWishlistData( }, []); return useMemo( - () => ({ + (): WishlistDataInternal => ({ update: (updateJob: UpdateJob) => { 'worklet'; @@ -138,6 +153,14 @@ export function useWishlistData( getWishlistData().__detach(wishlistId); })(); }, + __firstIndex: () => { + 'worklet'; + return getWishlistData().__firstIndex(); + }, + __lastIndex: () => { + 'worklet'; + return getWishlistData().__lastIndex(); + }, }), [getWishlistData], );