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
195 lines (186 loc) • 7.99 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FlexGrid = void 0;
var _reactNative = require("react-native");
var _react = _interopRequireWildcard(require("react"));
var _calcFlexGrid = require("./calc-flex-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-native/no-inline-styles */
/* eslint-disable react-hooks/exhaustive-deps */
const FlexGrid = ({
data = [],
virtualization = true,
maxColumnRatioUnits = 1,
itemSizeUnit = 50,
scrollEventInterval = 200,
// milliseconds
virtualizedBufferFactor = 2,
showScrollIndicator = true,
renderItem = () => null,
autoAdjustItemWidth = true,
style = {},
itemContainerStyle = {},
keyExtractor = (_, index) => String(index),
// default to item index if no keyExtractor is provided
onHorizontalEndReachedThreshold = 0.5,
// default to 50% of the container width
onHorizontalEndReached,
onVerticalEndReachedThreshold = 0.5,
// default to 50% of the container height
onVerticalEndReached,
HeaderComponent = null,
FooterComponent = null
}) => {
const [visibleItems, setVisibleItems] = (0, _react.useState)([]);
const [containerSize, setContainerSize] = (0, _react.useState)({
width: 0,
height: 0
});
const scrollPosition = (0, _react.useRef)({
x: 0,
y: 0
});
const onEndReachedCalled = (0, _react.useRef)({
x: false,
y: false
});
const {
totalHeight,
totalWidth,
gridItems
} = (0, _react.useMemo)(() => {
return (0, _calcFlexGrid.calcFlexGrid)(data, maxColumnRatioUnits, itemSizeUnit, autoAdjustItemWidth);
}, [data, maxColumnRatioUnits, itemSizeUnit, autoAdjustItemWidth]);
const renderedList = virtualization ? visibleItems : gridItems;
const updateVisibleItems = () => {
if (!virtualization) return;
const bufferX = containerSize.width * virtualizedBufferFactor;
const bufferY = containerSize.height * virtualizedBufferFactor;
const visibleStartX = Math.max(0, scrollPosition.current.x - bufferX);
const visibleEndX = scrollPosition.current.x + containerSize.width + bufferX;
const visibleStartY = Math.max(0, scrollPosition.current.y - bufferY);
const visibleEndY = scrollPosition.current.y + containerSize.height + bufferY;
const vItems = gridItems.filter(item => {
const itemRight = item.left + (item.widthRatio || 1) * itemSizeUnit;
const itemBottom = item.top + (item.heightRatio || 1) * itemSizeUnit;
return item.left < visibleEndX && itemRight > visibleStartX && item.top < visibleEndY && itemBottom > visibleStartY;
});
setVisibleItems(vItems);
};
const throttledUpdateVisibleItems = (0, _useThrottle.default)(updateVisibleItems, scrollEventInterval);
const onHorizontalScroll = event => {
const {
nativeEvent
} = event;
const currentScrollX = nativeEvent.contentOffset.x;
scrollPosition.current = {
...scrollPosition.current,
x: nativeEvent.contentOffset.x
};
// Calculate the position to check against the horizontal threshold
const contentWidth = totalWidth;
const scrollViewWidth = containerSize.width;
const threshold = onHorizontalEndReachedThreshold * scrollViewWidth;
// Check if we've reached the horizontal threshold for calling onHorizontalEndReached
if (!onEndReachedCalled.current.x && currentScrollX + scrollViewWidth + threshold >= contentWidth) {
onEndReachedCalled.current.x = true; // Marked as called to prevent subsequent calls
onHorizontalEndReached === null || onHorizontalEndReached === void 0 || onHorizontalEndReached(); // call the onHorizontalEndReached function if it exists
}
// Reset the flag when scrolled away from the bottom
if (currentScrollX + scrollViewWidth + threshold * 2 < contentWidth) {
onEndReachedCalled.current.x = false;
}
if (virtualization) {
throttledUpdateVisibleItems();
}
};
const onVerticalScroll = event => {
const {
nativeEvent
} = event;
const currentScrollY = nativeEvent.contentOffset.y;
scrollPosition.current = {
...scrollPosition.current,
y: nativeEvent.contentOffset.y
};
// Calculate the position to check against the vertical threshold
const contentHeight = totalHeight;
const scrollViewHeight = containerSize.height;
const threshold = onVerticalEndReachedThreshold * scrollViewHeight;
// Check if we've reached the horizontal threshold for calling onVerticalEndReached
if (!onEndReachedCalled.current.y && currentScrollY + scrollViewHeight + threshold >= contentHeight) {
onEndReachedCalled.current.y = true; // Marked as called to prevent subsequent calls
onVerticalEndReached === null || onVerticalEndReached === void 0 || onVerticalEndReached(); // call the onVerticalEndReached function if it exists
}
// Reset the flag when scrolled away from the bottom
if (currentScrollY + scrollViewHeight + threshold * 2 < contentHeight) {
onEndReachedCalled.current.y = false;
}
if (virtualization) {
throttledUpdateVisibleItems();
}
};
(0, _react.useEffect)(() => {
//recalculate visible items when itemSizeUnit or maxColumnRatioUnits or data changes
if (virtualization) {
updateVisibleItems();
}
// Reset onEndReachedCalled to false when data changes, allowing onEndReached functions to be called again
onEndReachedCalled.current = {
x: false,
y: false
};
}, [itemSizeUnit, maxColumnRatioUnits, data, virtualization, containerSize]);
if (!renderedList) {
return null;
}
return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
style: [{
flexGrow: 1
}, style],
onLayout: event => {
const {
width,
height
} = event.nativeEvent.layout;
setContainerSize({
width,
height
});
}
}, /*#__PURE__*/_react.default.createElement(_reactNative.ScrollView, {
horizontal: true,
onScroll: onHorizontalScroll,
scrollEventThrottle: 32,
showsHorizontalScrollIndicator: showScrollIndicator
}, /*#__PURE__*/_react.default.createElement(_reactNative.ScrollView, {
onScroll: onVerticalScroll,
scrollEventThrottle: 32,
showsVerticalScrollIndicator: showScrollIndicator
}, /*#__PURE__*/_react.default.createElement(_reactNative.View, null, (0, _renderPropComponent.renderPropComponent)(HeaderComponent)), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
style: {
flex: 1,
height: totalHeight,
width: totalWidth
}
}, renderedList.map((item, index) => /*#__PURE__*/_react.default.createElement(_reactNative.View, {
key: keyExtractor(item, index),
style: [{
position: 'absolute',
top: item.top,
left: item.left,
width: (item.widthRatio || 1) * itemSizeUnit,
height: (item.heightRatio || 1) * itemSizeUnit
}, itemContainerStyle]
}, renderItem({
item,
index
})))), /*#__PURE__*/_react.default.createElement(_reactNative.View, null, (0, _renderPropComponent.renderPropComponent)(FooterComponent)))));
};
exports.FlexGrid = FlexGrid;
//# sourceMappingURL=index.js.map