UNPKG

react-native-flexible-grid

Version:

React Native Flexible Grid is an advanced grid layout system inspired by CSS Grid, designed to facilitate responsive, customizable, and dynamic grid layouts in React Native applications. It supports both responsive and fixed layouts, enabling the creation

160 lines (152 loc) 5.51 kB
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-native/no-inline-styles */ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { ScrollView, View } from 'react-native'; import { calcResponsiveGrid } from './calc-responsive-grid'; import useThrottle from '../hooks/use-throttle'; import { renderPropComponent } from '../libs/render-prop-component'; export const ResponsiveGrid = ({ data = [], maxItemsPerColumn = 3, virtualizedBufferFactor = 5, renderItem, autoAdjustItemWidth = true, scrollEventInterval = 200, // milliseconds virtualization = true, showScrollIndicator = true, style = {}, itemContainerStyle = {}, itemUnitHeight, onEndReached, onEndReachedThreshold = 0.5, // default to 50% of the container height keyExtractor = (_, index) => String(index), // default to item index if no keyExtractor is provided HeaderComponent = null, FooterComponent = null, direction = 'ltr' }) => { const [visibleItems, setVisibleItems] = useState([]); const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }); const onEndReachedCalled = useRef(false); const scrollYPosition = useRef(0); const [footerComponentHeight, setFooterComponentHeight] = useState(0); const [headerComponentHeight, setHeaderComponentHeight] = useState(0); const { gridViewHeight, gridItems } = useMemo(() => calcResponsiveGrid(data, maxItemsPerColumn, containerSize.width, itemUnitHeight, autoAdjustItemWidth), [data, maxItemsPerColumn, containerSize, autoAdjustItemWidth]); const renderedItems = virtualization ? visibleItems : gridItems; const sumScrollViewHeight = gridViewHeight + headerComponentHeight + footerComponentHeight; const updateVisibleItems = () => { if (!virtualization) return; // Buffer to add outside visible range const buffer = containerSize.height * virtualizedBufferFactor; // Define the range of items that are visible based on scroll position const visibleStart = Math.max(0, scrollYPosition.current - buffer); const visibleEnd = scrollYPosition.current + containerSize.height + buffer; const vItems = gridItems.filter(item => { const itemBottom = item.top + item.height; const itemTop = item.top; // Check if the item is within the adjusted visible range, including the buffer return itemBottom > visibleStart && itemTop < visibleEnd; }); setVisibleItems(vItems); return vItems; }; const throttledUpdateVisibleItems = useThrottle(updateVisibleItems, scrollEventInterval); const onScroll = event => { const currentScrollY = event.nativeEvent.contentOffset.y; scrollYPosition.current = currentScrollY; // Calculate the position to check against the threshold const contentHeight = gridViewHeight; const scrollViewHeight = containerSize.height; const threshold = onEndReachedThreshold * scrollViewHeight; // Check if we've reached the threshold for calling onEndReached if (!onEndReachedCalled.current && currentScrollY + scrollViewHeight + threshold >= contentHeight) { onEndReachedCalled.current = true; // Marked as called to prevent subsequent calls onEndReached === null || onEndReached === void 0 || onEndReached(); // call the onEndReached function if it exists } // Reset the flag when scrolled away from the bottom if (currentScrollY + scrollViewHeight + threshold * 2 < contentHeight) { onEndReachedCalled.current = false; } // Update visible items for virtualization if (virtualization) { throttledUpdateVisibleItems(); } }; useEffect(() => { if (virtualization) { updateVisibleItems(); } // Reset onEndReachedCalled to false when data changes, allowing onEndReached to be called again onEndReachedCalled.current = false; }, [gridItems, containerSize, virtualization]); const getItemPositionStyle = item => { const baseStyle = { position: 'absolute', top: item.top, width: item.width, height: item.height }; return { ...baseStyle, ...(direction === 'rtl' ? { right: item.left } : { left: item.left }) }; }; return /*#__PURE__*/React.createElement(View, { style: [{ flexGrow: 1 }, style], onLayout: event => { const { width, height } = event.nativeEvent.layout; setContainerSize({ width, height }); } }, /*#__PURE__*/React.createElement(ScrollView, { onScroll: onScroll, scrollEventThrottle: 32, contentContainerStyle: { height: sumScrollViewHeight || '100%', width: containerSize.width }, showsVerticalScrollIndicator: showScrollIndicator }, /*#__PURE__*/React.createElement(View, { onLayout: ({ nativeEvent }) => { setHeaderComponentHeight(nativeEvent.layout.height); } }, renderPropComponent(HeaderComponent)), /*#__PURE__*/React.createElement(View, { style: { flex: 1 } }, renderedItems.map((item, index) => /*#__PURE__*/React.createElement(View, { key: keyExtractor(item, index), style: [getItemPositionStyle(item), itemContainerStyle] }, renderItem({ item, index })))), /*#__PURE__*/React.createElement(View, { onLayout: ({ nativeEvent }) => { setFooterComponentHeight(nativeEvent.layout.height); } }, renderPropComponent(FooterComponent)))); }; //# sourceMappingURL=index.js.map