UNPKG

@shopify/flash-list

Version:

FlashList is a more performant FlatList replacement

221 lines 8.04 kB
import { RVLayoutManager, } from "./LayoutManager"; /** * GridLayoutManager implementation that arranges items in a grid pattern. * Items are placed in rows and columns, with support for items spanning multiple columns. */ export class RVGridLayoutManagerImpl extends RVLayoutManager { constructor(params, previousLayoutManager) { super(params, previousLayoutManager); /** If there's a span change for grid layout, we need to recompute all the widths */ this.fullRelayoutRequired = false; this.boundedSize = params.windowSize.width; } /** * Updates layout parameters and triggers recomputation if necessary. * @param params New layout parameters */ updateLayoutParams(params) { const prevNumColumns = this.maxColumns; super.updateLayoutParams(params); if (this.boundedSize !== params.windowSize.width || prevNumColumns !== params.maxColumns) { this.boundedSize = params.windowSize.width; if (this.layouts.length > 0) { // update all widths this.updateAllWidths(); this.recomputeLayouts(0, this.layouts.length - 1); this.requiresRepaint = true; } } } /** * Processes layout information for items, updating their dimensions. * @param layoutInfo Array of layout information for items * @param itemCount Total number of items in the list */ processLayoutInfo(layoutInfo, itemCount) { for (const info of layoutInfo) { const { index, dimensions } = info; const layout = this.layouts[index]; layout.height = dimensions.height; layout.isHeightMeasured = true; layout.isWidthMeasured = true; } // TODO: Can be optimized if (this.fullRelayoutRequired) { this.updateAllWidths(); this.fullRelayoutRequired = false; return 0; } } /** * Estimates layout dimensions for an item at the given index. * @param index Index of the item to estimate layout for */ estimateLayout(index) { const layout = this.layouts[index]; layout.width = this.getWidth(index); layout.height = this.getEstimatedHeight(index); layout.isWidthMeasured = true; layout.enforcedWidth = true; } /** * Handles span change for an item. * @param index Index of the item */ handleSpanChange(index) { this.fullRelayoutRequired = true; } /** * Returns the total size of the layout area. * @returns RVDimension containing width and height of the layout */ getLayoutSize() { if (this.layouts.length === 0) return { width: 0, height: 0 }; const totalHeight = this.computeTotalHeightTillRow(this.layouts.length - 1); return { width: this.boundedSize, height: totalHeight, }; } /** * Recomputes layouts for items in the given range. * @param startIndex Starting index of items to recompute * @param endIndex Ending index of items to recompute */ recomputeLayouts(startIndex, endIndex) { const newStartIndex = this.locateFirstIndexInRow(Math.max(0, startIndex - 1)); const startVal = this.getLayout(newStartIndex); let startX = startVal.x; let startY = startVal.y; for (let i = newStartIndex; i <= endIndex; i++) { const layout = this.getLayout(i); if (!this.checkBounds(startX, layout.width)) { const tallestItem = this.processAndReturnTallestItemInRow(i - 1); startY = tallestItem.y + tallestItem.height; startX = 0; } layout.x = startX; layout.y = startY; startX += layout.width; } if (endIndex === this.layouts.length - 1) { this.processAndReturnTallestItemInRow(endIndex); } } /** * Calculates the width of an item based on its span. * @param index Index of the item * @returns Width of the item */ getWidth(index) { return (this.boundedSize / this.maxColumns) * this.getSpan(index); } /** * Processes items in a row and returns the tallest item. * Also handles height normalization for items in the same row. * Tallest item per row helps in forcing tallest items height on neighbouring items. * @param endIndex Index of the last item in the row * @returns The tallest item in the row */ processAndReturnTallestItemInRow(endIndex) { var _a, _b; const startIndex = this.locateFirstIndexInRow(endIndex); let tallestItem; let maxHeight = 0; let i = startIndex; let isMeasured = false; while (i <= endIndex) { const layout = this.layouts[i]; isMeasured = isMeasured || Boolean(layout.isHeightMeasured); maxHeight = Math.max(maxHeight, layout.height); if (layout.height > ((_a = layout.minHeight) !== null && _a !== void 0 ? _a : 0) && layout.height > ((_b = tallestItem === null || tallestItem === void 0 ? void 0 : tallestItem.height) !== null && _b !== void 0 ? _b : 0)) { tallestItem = layout; } i++; if (i >= this.layouts.length) { break; } } if (!tallestItem && maxHeight > 0) { maxHeight = Number.MAX_SAFE_INTEGER; } tallestItem = tallestItem !== null && tallestItem !== void 0 ? tallestItem : this.layouts[startIndex]; if (!isMeasured) { return tallestItem; } if (tallestItem) { let targetHeight = tallestItem.height; if (maxHeight - tallestItem.height > 1) { targetHeight = 0; this.requiresRepaint = true; } i = startIndex; while (i <= endIndex) { this.layouts[i].minHeight = targetHeight; if (targetHeight > 0) { this.layouts[i].height = targetHeight; } i++; if (i >= this.layouts.length) { break; } } tallestItem.minHeight = 0; } return tallestItem; } /** * Computes the total height of the layout. * @param endIndex Index of the last item in the row * @returns Total height of the layout */ computeTotalHeightTillRow(endIndex) { const startIndex = this.locateFirstIndexInRow(endIndex); const y = this.layouts[startIndex].y; let maxHeight = 0; let i = startIndex; while (i <= endIndex) { maxHeight = Math.max(maxHeight, this.layouts[i].height); i++; if (i >= this.layouts.length) { break; } } return y + maxHeight; } updateAllWidths() { for (let i = 0; i < this.layouts.length; i++) { this.layouts[i].width = this.getWidth(i); } } /** * Checks if an item can fit within the bounded width. * @param itemX Starting X position of the item * @param width Width of the item * @returns True if the item fits within bounds */ checkBounds(itemX, width) { return itemX + width <= this.boundedSize + 0.9; } /** * Locates the index of the first item in the current row. * @param itemIndex Index to start searching from * @returns Index of the first item in the row */ locateFirstIndexInRow(itemIndex) { if (itemIndex === 0) { return 0; } let i = itemIndex; for (; i >= 0; i--) { if (this.layouts[i].x === 0) { break; } } return Math.max(i, 0); } } //# sourceMappingURL=GridLayoutManager.js.map