react-native-big-list
Version:
High-performance, virtualized list for React Native. Efficiently renders large datasets with recycler API for smooth scrolling and low memory usage. Ideal for fast, scalable, customizable lists on Android, iOS, and web.
405 lines (386 loc) • 11.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _BigListItem = require("./BigListItem");
var _BigListItemRecycler = _interopRequireDefault(require("./BigListItemRecycler"));
var _utils = require("./utils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
class BigListProcessor {
/**
* Constructor.
* @param {ScrollView} scrollView
* @param {array[]|object|null|undefined} sections
* @param {number|function|null|undefined} headerHeight
* @param {number|function|null|undefined} footerHeight
* @param {number|function|null|undefined} sectionHeaderHeight
* @param {number|function|null|undefined} itemHeight
* @param {number|function|null|undefined} sectionFooterHeight
* @param {number|function|null|undefined} insetTop
* @param {number|function|null|undefined} insetBottom
* @param {number|null|undefined} numColumns
*/
constructor(_ref) {
let {
scrollView,
sections,
headerHeight,
footerHeight,
sectionHeaderHeight,
itemHeight,
sectionFooterHeight,
insetTop,
insetBottom,
numColumns
} = _ref;
this.headerHeight = headerHeight;
this.footerHeight = footerHeight;
this.sectionHeaderHeight = sectionHeaderHeight;
this.itemHeight = itemHeight;
this.sectionFooterHeight = sectionFooterHeight;
this.sections = sections;
this.insetTop = insetTop;
this.insetBottom = insetBottom;
this.uniform = (0, _utils.isNumeric)(itemHeight);
this.scrollView = scrollView;
this.numColumns = numColumns;
}
/**
* Get item height.
* @returns {number|*}
*/
getItemHeight(section, index) {
const {
itemHeight
} = this;
return (0, _utils.isNumeric)(itemHeight) ? Number(itemHeight) : itemHeight(section, index);
}
/**
* Get header height.
* @returns {number|*}
*/
getHeaderHeight() {
const {
headerHeight
} = this;
return (0, _utils.isNumeric)(headerHeight) ? Number(headerHeight) : headerHeight();
}
/**
* Get footer height.
* @returns {number|*}
*/
getFooterHeight() {
const {
footerHeight
} = this;
return (0, _utils.isNumeric)(footerHeight) ? Number(footerHeight) : footerHeight();
}
/**
* Get section height.
* @returns {number|*}
*/
getSectionHeaderHeight(section) {
const {
sectionHeaderHeight
} = this;
return (0, _utils.isNumeric)(sectionHeaderHeight) ? Number(sectionHeaderHeight) : sectionHeaderHeight(section);
}
/**
* Get section footer height.
* @returns {number|*}
*/
getSectionFooterHeight(section) {
const {
sectionFooterHeight
} = this;
return (0, _utils.isNumeric)(sectionFooterHeight) ? Number(sectionFooterHeight) : sectionFooterHeight(section);
}
/**
* Process list items.
* @param {number} top
* @param {number} bottom
* @param {array} prevItems
* @returns {{items: [], height: *}}
*/
process(top, bottom, prevItems) {
var _this = this;
const {
sections
} = this;
const items = [];
const recycler = new _BigListItemRecycler.default(prevItems);
let position;
let counter = -1; // Counter of items per row pushed
let height = this.insetTop;
let spacerHeight = height;
/**
* The width of the row is the entire line.
* @param {object} item
* @returns {boolean}
*/
const isFullRow = item => {
// Only items can be rendered with column format, so all others are full row
return item.type !== _BigListItem.BigListItemType.ITEM;
};
/**
* Is visible below.
* @param {object} item
* @returns {boolean}
*/
const isVisibleBelow = item => {
const {
height: itemHeight
} = item;
counter = -1;
if (height > bottom) {
spacerHeight += itemHeight;
return false;
} else {
return true;
}
};
/**
* Is the item visible.
* @param {object} item
* @param {bool} force
* @returns {boolean}
*/
const isVisible = function (item) {
let force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
// Check section headers visibility below
if (item.type === _BigListItem.BigListItemType.SECTION_HEADER) {
return isVisibleBelow(item);
}
// Dimensions
const {
height: itemHeight
} = item;
const fullRow = isFullRow(item);
const prevHeight = height;
// Increase or reset counter
counter = fullRow ? -1 : counter + 1;
if (fullRow || counter % _this.numColumns === 0) {
height += itemHeight;
}
// Check if is visible
if (force || height > top && prevHeight < bottom) {
return true;
} else {
if (fullRow || counter % _this.numColumns === 0) {
spacerHeight += itemHeight;
}
return false;
}
};
/**
* Get recycled views and push items.
* @param {object} itemsArray
*/
const push = function () {
for (var _len = arguments.length, itemsArray = new Array(_len), _key = 0; _key < _len; _key++) {
itemsArray[_key] = arguments[_key];
}
itemsArray.forEach(item => {
items.push(recycler.get(item));
});
};
/**
* Push spacer.
* @param {object} item
*/
const pushSpacer = item => {
if (spacerHeight > 0) {
push({
type: _BigListItem.BigListItemType.SPACER,
position: item.position - spacerHeight,
height: spacerHeight,
section: item.section,
index: item.index
});
spacerHeight = 0;
}
};
/**
* Push the item when is visible.
* @param {object} item
* @param {bool} force
*/
const pushItem = function (item) {
let force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (isVisible(item, force)) {
pushSpacer(item);
push(item);
}
};
/**
* Calculate spacer height.
*/
const getSpacerHeight = () => {
let itemsCounter = -1;
return items.reduce((totalHeight, item, i) => {
if (i !== items.length - 1) {
const fullRow = isFullRow(item);
itemsCounter = fullRow ? 0 : itemsCounter + 1;
if (fullRow || itemsCounter % this.numColumns === 0) {
return totalHeight + item.height;
}
}
return totalHeight;
}, 0);
};
// Header
const headerHeight = this.getHeaderHeight();
if (headerHeight > 0) {
position = height;
pushItem({
type: _BigListItem.BigListItemType.HEADER,
position: position,
height: headerHeight
}, true);
}
// Sections
for (let section = 0; section < sections.length; section++) {
const rows = sections[section];
if (rows === 0) {
continue;
}
// Section Header
const sectionHeaderHeight = this.getSectionHeaderHeight(section);
position = height;
height += sectionHeaderHeight;
if (section > 1 && items.length > 0 && items[items.length - 1].type === _BigListItem.BigListItemType.SECTION_HEADER) {
// Top Spacer
const initialSpacerHeight = getSpacerHeight();
const prevSection = items[items.length - 1];
items.splice(0, items.length);
push({
type: _BigListItem.BigListItemType.HEADER,
position: position,
height: headerHeight
}, {
type: _BigListItem.BigListItemType.SPACER,
position: 0,
height: initialSpacerHeight - headerHeight,
section: prevSection.section,
index: 0
}, prevSection);
}
pushItem({
type: _BigListItem.BigListItemType.SECTION_HEADER,
position: position,
height: sectionHeaderHeight,
section: section
});
// Items
let itemHeight = this.getItemHeight(section);
for (let index = 0; index < rows; index++) {
if (!this.uniform) {
itemHeight = this.getItemHeight(section, index);
}
position = height;
pushItem({
type: _BigListItem.BigListItemType.ITEM,
position: position,
height: itemHeight,
section: section,
index: index
});
}
// Section Footer
const sectionFooterHeight = this.getSectionFooterHeight(section);
if (sectionFooterHeight > 0) {
position = height;
pushItem({
type: _BigListItem.BigListItemType.SECTION_FOOTER,
position: position,
height: sectionFooterHeight,
section: section
});
}
}
// Footer
const footerHeight = this.getFooterHeight();
if (footerHeight > 0) {
position = height;
pushItem({
type: _BigListItem.BigListItemType.FOOTER,
position: position,
height: footerHeight
}, true);
}
// Bottom Spacer
height += this.insetBottom;
spacerHeight += this.insetBottom;
if (spacerHeight > 0) {
push({
type: _BigListItem.BigListItemType.SPACER,
position: height - spacerHeight,
height: spacerHeight,
section: sections.length
});
}
recycler.fill();
return {
height,
items
};
}
/**
* Scroll to position.
* @param {int} targetSection
* @param {int} targetIndex
* @param {boolean} animated
*/
scrollToPosition(targetSection, targetIndex, animated) {
const {
sections,
insetTop
} = this;
// Header + inset
let scrollTop = insetTop + this.getHeaderHeight();
let section = 0;
let foundIndex = false;
while (section <= targetSection) {
const rows = Math.ceil(sections[section] / this.numColumns);
if (rows === 0) {
section += 1;
continue;
}
// Section header
scrollTop += this.getSectionHeaderHeight(section);
// Items
if (this.uniform) {
const uniformHeight = this.getItemHeight(section);
if (section === targetSection) {
scrollTop += uniformHeight * Math.ceil(targetIndex / this.numColumns);
foundIndex = true;
} else {
scrollTop += uniformHeight * rows;
}
} else {
for (let index = 0; index < rows; index++) {
if (section < targetSection || section === targetSection && index < targetIndex) {
scrollTop += this.getItemHeight(section, Math.ceil(index / this.numColumns));
} else if (section === targetSection && index === targetIndex) {
foundIndex = true;
break;
}
}
}
// Section footer
if (!foundIndex) {
scrollTop += this.getSectionFooterHeight(section);
}
section += 1;
}
this.scrollView.scrollTo({
x: 0,
y: Math.max(0, scrollTop - this.getSectionHeaderHeight(targetSection)),
animated
});
return true;
}
}
exports.default = BigListProcessor;
//# sourceMappingURL=BigListProcessor.js.map