UNPKG

@mhmdaljefri/revogrid

Version:

Virtual reactive data grid component - RevoGrid.

181 lines (180 loc) 5.92 kB
import { getItemByPosition } from '../dimension/dimension.helpers'; /** * Update items based on new scroll position * If viewport wasn't changed fully simple recombination of positions * Otherwise rebuild viewport items */ export function getUpdatedItemsByPosition(pos, items, realCount, virtualSize, dimension) { const activeItem = getItemByPosition(dimension, pos); const firstItem = getFirstItem(items); let toUpdate; // do simple position replacement if items already present in viewport if (firstItem) { let changedOffsetStart = activeItem.itemIndex - (firstItem.itemIndex || 0); if (changedOffsetStart) { // simple recombination const newData = recombineByOffset(Math.abs(changedOffsetStart), Object.assign(Object.assign({ positiveDirection: changedOffsetStart > -1 }, dimension), items)); if (newData) { toUpdate = newData; } // if partial replacement add items if revo-viewport has some space left if (toUpdate) { const extra = addMissingItems(activeItem, realCount, virtualSize, toUpdate, dimension); if (extra.length) { updateMissingAndRange(toUpdate.items, extra, toUpdate); } } } } // new collection if no items after replacement full replacement if (!toUpdate) { const items = getItems({ start: activeItem.start, startIndex: activeItem.itemIndex, origSize: dimension.originItemSize, maxSize: virtualSize, maxCount: realCount, sizes: dimension.sizes, }); // range now comes from 0 to length - 1 toUpdate = { items, start: 0, end: items.length - 1, }; } return toUpdate; } export function updateMissingAndRange(items, missing, range) { items.splice(range.end + 1, 0, ...missing); // update range if start larger after recombination if (range.start >= range.end && !(range.start === range.end && range.start === 0)) { range.start += missing.length; } range.end += missing.length; } // if partial replacement add items if revo-viewport has some space left export function addMissingItems(firstItem, realCount, virtualSize, existingCollection, dimension) { const lastItem = getLastItem(existingCollection); const items = getItems({ sizes: dimension.sizes, start: lastItem.end, startIndex: lastItem.itemIndex + 1, origSize: dimension.originItemSize, maxSize: virtualSize - (lastItem.end - firstItem.start), maxCount: realCount, }); return items; } // get revo-viewport items parameters, caching position and calculating items count in revo-viewport export function getItems(opt, currentSize = 0) { const items = []; let index = opt.startIndex; let size = currentSize; while (size <= opt.maxSize && index < opt.maxCount) { const newSize = getItemSize(index, opt.sizes, opt.origSize); items.push({ start: opt.start + size, end: opt.start + size + newSize, itemIndex: index, size: newSize, }); size += newSize; index++; } return items; } /** * Do batch items recombination * If items not overlapped with existing viewport returns null */ export function recombineByOffset(offset, data) { const newItems = [...data.items]; const itemsCount = newItems.length; let newRange = { start: data.start, end: data.end, }; // if offset out of revo-viewport, makes sense whole redraw if (offset > itemsCount) { return null; } // is direction of scroll positive if (data.positiveDirection) { // push item to the end let lastItem = getLastItem(data); let i = newRange.start; const length = i + offset; for (; i < length; i++) { const newIndex = lastItem.itemIndex + 1; const size = getItemSize(newIndex, data.sizes, data.originItemSize); // if item overlapped limit break a loop if (lastItem.end + size > data.realSize) { break; } // new item index to recombine let newEnd = i % itemsCount; // item should always present, we do not create new item, we recombine them if (!newItems[newEnd]) { throw new Error('incorrect index'); } // do recombination newItems[newEnd] = lastItem = { start: lastItem.end, end: lastItem.end + size, itemIndex: newIndex, size: size, }; // update range newRange.start++; newRange.end = newEnd; } // direction is negative } else { // push item to the start let firstItem = getFirstItem(data); const end = newRange.end; for (let i = 0; i < offset; i++) { const newIndex = firstItem.itemIndex - 1; const size = getItemSize(newIndex, data.sizes, data.originItemSize); // new item index to recombine let newStart = end - i; newStart = (newStart < 0 ? itemsCount + newStart : newStart) % itemsCount; // item should always present, we do not create new item, we recombine them if (!newItems[newStart]) { throw new Error('incorrect index'); } // do recombination newItems[newStart] = firstItem = { start: firstItem.start - size, end: firstItem.start, itemIndex: newIndex, size: size, }; // update range newRange.start = newStart; newRange.end--; } } const range = { start: (newRange.start < 0 ? itemsCount + newRange.start : newRange.start) % itemsCount, end: (newRange.end < 0 ? itemsCount + newRange.end : newRange.end) % itemsCount, }; return Object.assign({ items: newItems }, range); } function getItemSize(index, sizes, origSize = 0) { if (sizes && sizes[index]) { return sizes[index]; } return origSize; } export function isActiveRange(pos, item) { return item && pos >= item.start && pos <= item.end; } export function getFirstItem(s) { return s.items[s.start]; } export function getLastItem(s) { return s.items[s.end]; }