Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ Also works pretty well with [`svelte-infinite-loading`](https://github.com/Skayo
| :---------------- | :------------------------------------------------ | :-------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| width | `number \| string`\* | ✓ | Width of List. This property will determine the number of rendered items when scrollDirection is `'horizontal'`. |
| height | `number \| string`\* | ✓ | Height of List. This property will determine the number of rendered items when scrollDirection is `'vertical'`. |
| itemCount | `number` | ✓ | The number of items you want to render |
| items | `any[]` | | The items you want to render |
| itemCount | `number` | | The number of items you want to render |
| itemSize | `number \| number[] \| (index: number) => number` | ✓ | Either a fixed height/width (depending on the scrollDirection), an array containing the heights of all the items in your list, or a function that returns the height of an item given its index: `(index: number): number` |
| scrollDirection | `string` | | Whether the list should scroll vertically or horizontally. One of `'vertical'` (default) or `'horizontal'`. |
| scrollOffset | `number` | | Can be used to control the scroll offset; Also useful for setting an initial scroll offset |
Expand Down
9 changes: 3 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
"name": "svelte-tiny-virtual-list",
"version": "2.0.5",
"description": "A tiny but mighty list virtualization component for svelte, with zero dependencies 💪",
"svelte": "src/index.js",
"main": "dist/svelte-tiny-virtual-list.js",
"module": "dist/svelte-tiny-virtual-list.mjs",
"types": "types/index.d.ts",
"svelte": "src/VirtualList.svelte",
"main": "src/VirtualList.svelte",
"scripts": {
"build": "rollup -c",
"lint": "eslint src/** test/**",
Expand All @@ -29,8 +27,7 @@
},
"files": [
"src",
"dist",
"types"
"dist"
],
"keywords": [
"svelte",
Expand Down
99 changes: 79 additions & 20 deletions src/SizeAndPositionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { ALIGNMENT } from './constants';
* @type {object}
* @property {number} size
* @property {number} offset
* @property {number} expandSize
* @property {number} expandOffset
*/

/**
Expand All @@ -29,15 +31,24 @@ import { ALIGNMENT } from './constants';
* @type {object}
* @property {number} itemCount
* @property {ItemSize} itemSize
* @property {Array} expandItems
* @property {ItemSize} expandItemSize
* @property {number} estimatedItemSize
* @property {number} estimatedExpandItemSize
*/

export default class SizeAndPositionManager {

/**
* @param {Options} options
*/
constructor({ itemSize, itemCount, estimatedItemSize }) {
constructor({ itemCount, itemSize, expandItems, expandItemSize, estimatedItemSize, estimatedExpandItemSize }) {
/**
* @private
* @type {number}
*/
this.itemCount = itemCount;

/**
* @private
* @type {ItemSize}
Expand All @@ -46,16 +57,28 @@ export default class SizeAndPositionManager {

/**
* @private
* @type {number}
* @type {Array}
*/
this.itemCount = itemCount;
this.expandItems = expandItems;

/**
* @private
* @type {ItemSize}
*/
this.expandItemSize = expandItemSize;

/**
* @private
* @type {number}
*/
this.estimatedItemSize = estimatedItemSize;

/**
* @private
* @type {number}
*/
this.estimatedExpandItemSize = estimatedExpandItemSize;

/**
* Cache of size and position data for items, mapped by item index.
*
Expand Down Expand Up @@ -84,17 +107,29 @@ export default class SizeAndPositionManager {
/**
* @param {Options} options
*/
updateConfig({ itemSize, itemCount, estimatedItemSize }) {
updateConfig({ itemCount, itemSize, expandItems, expandItemSize, estimatedItemSize, estimatedExpandItemSize }) {
if (itemCount != null) {
this.itemCount = itemCount;
}

if (itemSize != null) {
this.itemSize = itemSize;
}

if (expandItems != null) {
this.expandItems = expandItems;
}

if (expandItemSize != null) {
this.expandItemSize = expandItemSize;
}

if (estimatedItemSize != null) {
this.estimatedItemSize = estimatedItemSize;
}

if (itemSize != null) {
this.itemSize = itemSize;
if (estimatedExpandItemSize != null) {
this.estimatedExpandItemSize = estimatedExpandItemSize;
}

this.checkForMismatchItemSizeAndItemCount();
Expand Down Expand Up @@ -127,6 +162,21 @@ export default class SizeAndPositionManager {
return Array.isArray(itemSize) ? itemSize[index] : itemSize;
}

/**
* @param {number} index
*/
getExpandSize(index) {
if (!this.expandItems[index]) return 0;

const { expandItemSize } = this;

if (typeof expandItemSize === 'function') {
return expandItemSize(index);
}

return Array.isArray(expandItemSize) ? expandItemSize[index] : expandItemSize;
}

/**
* Compute the totalSize and itemSizeAndPositionData at the start,
* only when itemSize is a number or an array.
Expand All @@ -135,12 +185,16 @@ export default class SizeAndPositionManager {
let totalSize = 0;
for (let i = 0; i < this.itemCount; i++) {
const size = this.getSize(i);
const expandSize = this.getExpandSize(i);
const offset = totalSize;
totalSize += size;
const expandOffset = totalSize + size;
totalSize += size + expandSize;

this.itemSizeAndPositionData[i] = {
offset,
size,
offset,
expandSize,
expandOffset,
};
}

Expand Down Expand Up @@ -180,17 +234,23 @@ export default class SizeAndPositionManager {
const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem();
let offset =
lastMeasuredSizeAndPosition.offset + lastMeasuredSizeAndPosition.size;
let expandOffset = lastMeasuredSizeAndPosition.expandOffset + lastMeasuredSizeAndPosition.expandSize;

for (let i = this.lastMeasuredIndex + 1; i <= index; i++) {
const size = this.getSize(i);

const expandSize = this.getExpandSize(i);
if (size == null || isNaN(size)) {
throw Error(`Invalid size returned for index ${i} of value ${size}`);
}
if (expandSize == null || isNaN(expandSize)) {
throw Error(`Invalid expandSize returned for index ${i} of value ${expandSize}`);
}

this.itemSizeAndPositionData[i] = {
offset,
size,
expandOffset,
expandSize,
};

offset += size;
Expand All @@ -205,7 +265,7 @@ export default class SizeAndPositionManager {
getSizeAndPositionOfLastMeasuredItem() {
return this.lastMeasuredIndex >= 0
? this.itemSizeAndPositionData[this.lastMeasuredIndex]
: { offset: 0, size: 0 };
: { offset: 0, size: 0, expandOffset: 0, expandSize: 0 };
}

/**
Expand All @@ -216,7 +276,6 @@ export default class SizeAndPositionManager {
getTotalSize() {
// Return the pre computed totalSize when itemSize is number or array.
if (this.totalSize) return this.totalSize;

/**
* When itemSize is a function,
* This value will be completedly estimated initially.
Expand All @@ -227,7 +286,7 @@ export default class SizeAndPositionManager {
return (
lastMeasuredSizeAndPosition.offset +
lastMeasuredSizeAndPosition.size +
(this.itemCount - this.lastMeasuredIndex - 1) * this.estimatedItemSize
(this.itemCount - this.lastMeasuredIndex - 1) * (this.estimatedItemSize + this.estimatedExpandItemSize)
);
}

Expand All @@ -240,7 +299,7 @@ export default class SizeAndPositionManager {
* @param {number | undefined} targetIndex
* @return {number} Offset to use to ensure the specified item is visible
*/
getUpdatedOffsetForIndex({ align = ALIGNMENT.START, containerSize, currentOffset, targetIndex }) {
getUpdatedOffsetForIndex(align = ALIGNMENT.START, containerSize, currentOffset, targetIndex) {
if (containerSize <= 0) {
return 0;
}
Expand Down Expand Up @@ -276,30 +335,30 @@ export default class SizeAndPositionManager {
* @param {number} overscanCount
* @return {{stop: number|undefined, start: number|undefined}}
*/
getVisibleRange({ containerSize = 0, offset, overscanCount }) {
getVisibleRange(containerSize = 0, offset, overscanCount) {
const totalSize = this.getTotalSize();

if (totalSize === 0) {
return {};
}

const maxOffset = offset + containerSize;
const maxOffset = Math.max(0, offset || 0) + containerSize;
let start = this.findNearestItem(offset);

if (start === undefined) {
throw Error(`Invalid offset ${offset} specified`);
}

const datum = this.getSizeAndPositionForIndex(start);
offset = datum.offset + datum.size;
offset = datum.offset + datum.size + datum.expandSize;

let stop = start;

while (offset < maxOffset && stop < this.itemCount - 1) {
stop++;
offset += this.getSizeAndPositionForIndex(stop).size;
offset += this.getSizeAndPositionForIndex(stop).size + this.getSizeAndPositionForIndex(stop).expandSize;
}

if (overscanCount) {
start = Math.max(0, start - overscanCount);
stop = Math.min(stop + overscanCount, this.itemCount - 1);
Expand Down
Loading