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
232 lines (218 loc) • 8.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ResponsiveGrid = void 0;
var _react = _interopRequireWildcard(require("react"));
var _reactNative = require("react-native");
var _calcResponsiveGrid = require("./calc-responsive-grid");
var _useThrottle = _interopRequireDefault(require("../hooks/use-throttle"));
var _renderPropComponent = require("../libs/render-prop-component");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable react-native/no-inline-styles */
const ResponsiveGrid = ({
data = [],
maxItemsPerColumn = 3,
virtualizedBufferFactor = 5,
renderItem,
autoAdjustItemWidth = true,
scrollEventInterval = 200,
// milliseconds
virtualization = true,
showScrollIndicator = true,
bounces = true,
style = {},
itemContainerStyle = {},
itemUnitHeight,
onScroll: onScrollProp,
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',
removeClippedSubviews = true
}) => {
const [visibleItems, setVisibleItems] = (0, _react.useState)([]);
const [containerSize, setContainerSize] = (0, _react.useState)({
width: 0,
height: 0
});
// Extract padding values from style prop - renamed for clarity
const [componentPadding, setComponentPadding] = (0, _react.useState)({
horizontal: 0,
vertical: 0
});
const onEndReachedCalled = (0, _react.useRef)(false);
const scrollYPosition = (0, _react.useRef)(0);
const [footerComponentHeight, setFooterComponentHeight] = (0, _react.useState)(0);
const [headerComponentHeight, setHeaderComponentHeight] = (0, _react.useState)(0);
// Get the effective width accounting for padding
const effectiveWidth = containerSize.width - componentPadding.horizontal * 2;
const {
gridViewHeight,
gridItems
} = (0, _react.useMemo)(() => (0, _calcResponsiveGrid.calcResponsiveGrid)(data, maxItemsPerColumn, effectiveWidth > 0 ? effectiveWidth : containerSize.width, itemUnitHeight, autoAdjustItemWidth), [data, maxItemsPerColumn, containerSize, effectiveWidth, autoAdjustItemWidth, itemUnitHeight]);
const renderedItems = virtualization ? visibleItems : gridItems;
const sumScrollViewHeight = gridViewHeight + headerComponentHeight + footerComponentHeight;
// Extract padding from style object
const extractPadding = styleObj => {
if (!styleObj) return {
horizontal: 0,
vertical: 0
};
const flatStyle = _reactNative.StyleSheet.flatten(styleObj);
let horizontal = 0;
let vertical = 0;
if (flatStyle.padding !== undefined) {
horizontal = vertical = Number(flatStyle.padding) || 0;
}
if (flatStyle.paddingHorizontal !== undefined) {
horizontal = Number(flatStyle.paddingHorizontal) || 0;
}
if (flatStyle.paddingVertical !== undefined) {
vertical = Number(flatStyle.paddingVertical) || 0;
}
if (flatStyle.paddingLeft !== undefined || flatStyle.paddingRight !== undefined) {
const left = Number(flatStyle.paddingLeft) || 0;
const right = Number(flatStyle.paddingRight) || 0;
horizontal = Math.max(horizontal, left + right);
}
return {
horizontal,
vertical
};
};
// Update padding values when style changes when component style is changed
(0, _react.useEffect)(() => {
const newPadding = extractPadding(style);
// Only update state if padding values have actually changed
if (newPadding.horizontal !== componentPadding.horizontal || newPadding.vertical !== componentPadding.vertical) {
setComponentPadding(newPadding);
}
}, [style, componentPadding.horizontal, componentPadding.vertical]);
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 = (0, _useThrottle.default)(updateVisibleItems, scrollEventInterval);
const throttledOnScroll = (0, _useThrottle.default)(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();
}
}, 32);
const onScroll = event => {
if (onScrollProp) {
onScrollProp(event);
}
throttledOnScroll(event);
};
(0, _react.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.default.createElement(_reactNative.View, {
style: [{
flexGrow: 1,
overflow: 'hidden'
}, style],
onLayout: event => {
const {
width,
height
} = event.nativeEvent.layout;
setContainerSize({
width,
height
});
}
}, /*#__PURE__*/_react.default.createElement(_reactNative.ScrollView, {
onScroll: onScroll,
scrollEventThrottle: onScrollProp ? 16 : 32,
horizontal: false,
bounces: bounces,
showsHorizontalScrollIndicator: false,
contentContainerStyle: {
height: sumScrollViewHeight || '100%',
width: '100%'
},
showsVerticalScrollIndicator: showScrollIndicator,
removeClippedSubviews: removeClippedSubviews
}, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
onLayout: ({
nativeEvent
}) => {
setHeaderComponentHeight(nativeEvent.layout.height);
}
}, (0, _renderPropComponent.renderPropComponent)(HeaderComponent)), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
style: {
position: 'relative',
width: '100%'
}
}, renderedItems.map((item, index) => /*#__PURE__*/_react.default.createElement(_reactNative.View, {
key: keyExtractor(item, index),
style: [getItemPositionStyle(item), itemContainerStyle]
}, renderItem({
item,
index
})))), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
onLayout: ({
nativeEvent
}) => {
setFooterComponentHeight(nativeEvent.layout.height);
}
}, (0, _renderPropComponent.renderPropComponent)(FooterComponent))));
};
exports.ResponsiveGrid = ResponsiveGrid;
//# sourceMappingURL=index.js.map