Skip to content

Commit

Permalink
refactor: items-\>model itemCount-\>modelCount
Browse files Browse the repository at this point in the history
  • Loading branch information
orefalo committed Jun 21, 2024
1 parent a2d4b66 commit 562fd5e
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 73 deletions.
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ This component can be used two different ways:
const data = ['A', 'B', 'C', 'D', 'E', 'F' /* ... */];
</script>
<VirtualList width="100%" height={600} items={data} itemCount={data.length} itemSize={50}>
<VirtualList width="100%" height={600} model={data} modelCount={data.length} itemSize={50}>
{#snippet slot({ item, style, index })}
<div class="row" {style}>
Item: {item}, Row: #{index}
Expand All @@ -84,10 +84,10 @@ This component can be used two different ways:
</VirtualList>
```

You can also perform dynamic loading
You can also perform dynamic loading with a PartialLoader.

```svelte
TODO
TODO: explain that model vs views, which translates into model and items
```

### Props
Expand All @@ -98,8 +98,8 @@ The component accepts the following properties
| ----------------- | ----------- | :-------: | ------------ |
| width | `number` or `string`\* || Width of List. This property will determine the number of rendered items when scrollDirection is `'horizontal'`. |
| height | `number` or `string`\* || Height of List. This property will determine the number of rendered items when scrollDirection is `'vertical'`. |
| items | any[] || the model, the data for the items to display in the list. |
| itemCount | `number` || The number of items you want to render. |
| model | any[] || the model, the data for the items to display in the list. |
| modelCount | `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` |
| row | (r:RowAttributes) => SnippetResult || Snippet called to render every item, see description below. |
| scrollDirection | `string` | | Whether the list should scroll vertically or horizontally. One of `'vertical'` (default) or `'horizontal'`. |
Expand All @@ -112,16 +112,17 @@ The component accepts the following properties
| getKey | `(index: number) => any` | | Function that returns the key of an item in the list, which is used to uniquely identify an item. This is useful for dynamic data coming from a database or similar. By default, it's using the item's index. |

_\* `height` must be a number when `scrollDirection` is `'vertical'`. Similarly, `width` must be a number if `scrollDirection` is `'horizontal'`_
_\** `model` is stored, `items` are rendered

### Snippets

- `header` - a snippet for the elements that should appear at the top of the list
- `footer` - a snippet for the elements that should appear at the bottom of the list (e.g. `InfiniteLoading` component from `svelte-infinite-loading`)
- `slot` - a required snipper property called to render each row of the list with the signature `slot(r:RowAttributes)`
- `footer` - a snippet for the elements that should appear at the bottom of the list
- `slot` - a required snipper property called to render each row of the list with the signature `slot(r:RowAttributes<T>)`

```typescript
export interface RowAttributes {
item: any // the element from the items array at index
export interface RowAttributes<T> {
item: T // the element from the model array to be rendered
index: number; // Item index
style: string; // Item style, must be applied to the slot (look above for example)
}
Expand Down
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
[x] Rename the row snippet to something else -> slot
[ ] add disabled
[ ] move the css style logic out of the component row({ item, style, index })
[ ] considering, items={data} itemCount={data.length} itemSize={50}; model is data, items is the view should be transformed into model={data} modelCount={data.length} itemSize={50}, {@render item(...)}
[x] considering, items={data} itemCount={data.length} itemSize={50}; model is data, items is the view should be transformed into model={data} modelCount={data.length} itemSize={50}, {@render item(...)}
[x] implement minimization for the npm package
32 changes: 16 additions & 16 deletions src/lib/SizeAndPositionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
import { ALIGNMENT, type VirtualItemSize, type VirtualRange, type VirtualPosition } from '.';

export default class SizeAndPositionManager {
private items: Array<any>;
private model: Array<any>;
private modelCount: number;
private itemSize: VirtualItemSize;
private itemCount: number;
private estimatedItemSize?: number;
private itemSizeAndPositionData: Record<number, VirtualPosition>;
private lastMeasuredIndex: number;
private totalSize?: number;

constructor(items: Array<any>, itemSize: VirtualItemSize, itemCount: number, estimatedItemSize?: number) {
this.items = items;
constructor(model: Array<any>, modelCount: number, itemSize: VirtualItemSize, estimatedItemSize?: number) {
this.model = model;
this.itemSize = itemSize;
this.itemCount = itemCount;
this.modelCount = modelCount;
this.estimatedItemSize = estimatedItemSize;
this.itemSizeAndPositionData = {};
this.lastMeasuredIndex = -1;
Expand All @@ -35,7 +35,7 @@ export default class SizeAndPositionManager {

updateConfig(itemSize: VirtualItemSize, itemCount: number, estimatedItemSize?: number) {
if (itemCount !== undefined) {
this.itemCount = itemCount;
this.modelCount = itemCount;
}

if (estimatedItemSize !== undefined) {
Expand All @@ -56,7 +56,7 @@ export default class SizeAndPositionManager {
}

checkForMismatchItemSizeAndItemCount() {
if (Array.isArray(this.itemSize) && this.itemSize.length < this.itemCount) {
if (Array.isArray(this.itemSize) && this.itemSize.length < this.modelCount) {
throw Error(`When itemSize is an array, itemSize.length can't be smaller than itemCount`);
}
}
Expand All @@ -65,7 +65,7 @@ export default class SizeAndPositionManager {
const { itemSize } = this;

if (typeof itemSize === 'function') {
return itemSize(this.items[index], index);
return itemSize(this.model[index], index);
}

return Array.isArray(itemSize) ? itemSize[index] : itemSize;
Expand All @@ -77,7 +77,7 @@ export default class SizeAndPositionManager {
*/
computeTotalSizeAndPositionData() {
let totalSize = 0;
for (let i = 0; i < this.itemCount; i++) {
for (let i = 0; i < this.modelCount; i++) {
const size = this.getSize(i);
const offset = totalSize;
totalSize += size;
Expand All @@ -100,8 +100,8 @@ export default class SizeAndPositionManager {
*
*/
getSizeAndPositionForIndex(index: number) {
if (index < 0 || index >= this.itemCount) {
throw Error(`Requested index ${index} is outside of range 0..${this.itemCount}`);
if (index < 0 || index >= this.modelCount) {
throw Error(`Requested index ${index} is outside of range 0..${this.modelCount}`);
}

return this.justInTime ? this.getJustInTimeSizeAndPositionForIndex(index) : this.itemSizeAndPositionData[index];
Expand Down Expand Up @@ -162,7 +162,7 @@ export default class SizeAndPositionManager {
return (
lastMeasuredSizeAndPosition.offset +
lastMeasuredSizeAndPosition.size +
(this.itemCount - this.lastMeasuredIndex - 1) * this.getEstimatedItemSize()
(this.modelCount - this.lastMeasuredIndex - 1) * this.getEstimatedItemSize()
);
}

Expand Down Expand Up @@ -227,14 +227,14 @@ export default class SizeAndPositionManager {

let end = start;

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

if (windowOverPadding) {
start = Math.max(0, start - windowOverPadding);
end = Math.min(end + windowOverPadding, this.itemCount - 1);
end = Math.min(end + windowOverPadding, this.modelCount - 1);
}

return {
Expand Down Expand Up @@ -310,11 +310,11 @@ export default class SizeAndPositionManager {
private exponentialSearch(index: number, offset: number): number {
let interval = 1;

while (index < this.itemCount && this.getSizeAndPositionForIndex(index).offset < offset) {
while (index < this.modelCount && this.getSizeAndPositionForIndex(index).offset < offset) {
index += interval;
interval *= 2;
}

return this.binarySearch(Math.floor(index / 2), Math.min(index, this.itemCount - 1), offset);
return this.binarySearch(Math.floor(index / 2), Math.min(index, this.modelCount - 1), offset);
}
}
44 changes: 21 additions & 23 deletions src/lib/VirtualList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@
const {
height,
width = '100%',
items = [],
// total item count, regardless of progressive loading. helps define the viewport size
itemCount,
// provided or calculated size of item n
model = [],
// total model count, typically model.length unless a partial loader is used
modelCount,
// items are the view, size of item n, can be a function
itemSize,
// usefull when using a partial loader
estimatedItemSize,
Expand All @@ -71,13 +71,12 @@
onVisibleRangeUpdate,
onAfterScroll,
...attributes
...props
}: {
height: number | string;
width: number | string;
items: Array<any>;
itemCount: number;
model: Array<any>;
modelCount: number;
itemSize: VirtualItemSize;
estimatedItemSize?: number;
getKey?: (i: number | string) => string;
Expand All @@ -95,7 +94,6 @@
// events
onVisibleRangeUpdate?: (range: VirtualRangeEvent) => void;
onAfterScroll?: (event: AfterScrollEvent) => void;
} = $props();
const SCROLL_PROP = {
Expand All @@ -117,19 +115,19 @@
scrollToIndex?: number;
scrollToAlignment?: string;
scrollOffset?: number;
itemCount?: number;
modelCount?: number;
itemSize?: VirtualItemSize;
estimatedItemSize?: number;
}
const sizeAndPositionManager = new SizeAndPositionManager(items, itemSize, itemCount, estimatedItemSize);
const sizeAndPositionManager = new SizeAndPositionManager(model, modelCount, itemSize, estimatedItemSize);
let mounted: boolean = false;
let container: HTMLDivElement;
let visibleItems: Array<SlotAttributes<any>> = $state([]);
let curState: VState = $state({
offset: scrollOffset || (scrollToIndex !== undefined && itemCount && getOffsetForIndex(scrollToIndex)) || 0,
offset: scrollOffset || (scrollToIndex !== undefined && modelCount && getOffsetForIndex(scrollToIndex)) || 0,
scrollChangeReason: SCROLL_CHANGE_REASON.REQUESTED
});
Expand All @@ -139,7 +137,7 @@
scrollToIndex,
scrollToAlignment,
scrollOffset,
itemCount,
modelCount,
itemSize,
estimatedItemSize
};
Expand All @@ -151,7 +149,7 @@
$effect(() => {
// listen to updates:
//@ts-expect-error unused no side effect
scrollToIndex, scrollToAlignment, scrollOffset, itemCount, itemSize, estimatedItemSize;
scrollToIndex, scrollToAlignment, scrollOffset, modelCount, itemSize, estimatedItemSize;
// on update:
propsUpdated();
Expand Down Expand Up @@ -210,12 +208,12 @@
const scrollPropsHaveChanged =
prevProps.scrollToIndex !== scrollToIndex || prevProps.scrollToAlignment !== scrollToAlignment;
const itemPropsHaveChanged =
prevProps.itemCount !== itemCount ||
prevProps.modelCount !== modelCount ||
prevProps.itemSize !== itemSize ||
prevProps.estimatedItemSize !== estimatedItemSize;
if (itemPropsHaveChanged) {
sizeAndPositionManager.updateConfig(itemSize, itemCount, estimatedItemSize);
sizeAndPositionManager.updateConfig(itemSize, modelCount, estimatedItemSize);
recomputeSizes();
}
Expand All @@ -228,7 +226,7 @@
};
} else if (typeof scrollToIndex === 'number' && (scrollPropsHaveChanged || itemPropsHaveChanged)) {
curState = {
offset: getOffsetForIndex(scrollToIndex, scrollToAlignment, itemCount),
offset: getOffsetForIndex(scrollToIndex, scrollToAlignment, modelCount),
scrollChangeReason: SCROLL_CHANGE_REASON.REQUESTED
};
Expand All @@ -238,7 +236,7 @@
scrollToIndex,
scrollToAlignment,
scrollOffset,
itemCount,
modelCount,
itemSize,
estimatedItemSize
};
Expand Down Expand Up @@ -286,7 +284,7 @@
if (start !== undefined && end !== undefined) {
for (let index = start; index <= end; index++) {
updatedItems.push({
item: items[index],
item: model[index],
index,
style: getStyle(index)
});
Expand Down Expand Up @@ -333,12 +331,12 @@
function getOffsetForIndex(
index: number,
align: ALIGNMENT = scrollToAlignment,
_itemCount: number = itemCount
_modelCount: number = modelCount
): number {
if (index < 0) {
index = 0;
} else if (index >= _itemCount) {
index = _itemCount - 1;
} else if (index >= _modelCount) {
index = _modelCount - 1;
}
return sizeAndPositionManager.getUpdatedOffsetForIndex(
Expand Down Expand Up @@ -383,7 +381,7 @@
}
</script>

<div bind:this={container} class="virtual-list-wrapper" style={wrapperStyle} {...attributes}>
<div bind:this={container} class="virtual-list-wrapper" style={wrapperStyle} {...props}>
{#if header}
{@render header()}
{/if}
Expand Down
10 changes: 5 additions & 5 deletions src/routes/examples/events/code.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { VirtualList } from 'svelte-virtuallists';
import { VirtualList, type SlotAttributes } from 'svelte-virtuallists';
import TextArea from '$comp/TextAreaAutosize.svelte';
let val = $state('// Event name: Event params (Last event at the top)');
Expand All @@ -17,7 +17,7 @@
}
// that's the model, which we don't use for this example
const myItems: Array<number> = new Array(10000).fill(1).map((v, i) => i);
const myModel: Array<number> = new Array(10000).fill(1).map((v, i) => i);
let virtualList: VirtualList;
Expand Down Expand Up @@ -69,15 +69,15 @@
bind:this={virtualList}
height={500}
width="auto"
items={myItems}
itemCount={10000}
model={myModel}
modelCount={myModel.length}
itemSize={40}
{scrollToIndex}
scrollOffset={scrollOffet}
onAfterScroll={handleMessage}
onVisibleRangeUpdate={handleMessage}
>
{#snippet slot({ item, style, index })}
{#snippet slot({ item, style, index }: SlotAttributes<any>)}
<div class="row" {style} class:highlighted={index === scrollToIndex}>
Item #{item}
</div>
Expand Down
10 changes: 5 additions & 5 deletions src/routes/examples/horizontal/code.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script lang="ts">
import { DIRECTION, VirtualList } from 'svelte-virtuallists';
import { DIRECTION, VirtualList, type SlotAttributes } from 'svelte-virtuallists';
interface MyItemsData {
text: string;
lineHeight: string;
width: string;
}
const myItems: Array<MyItemsData> = new Array(10000).fill(1).map((v, i) => ({
const myModel: Array<MyItemsData> = new Array(10000).fill(1).map((v, i) => ({
text: 'Item ' + i,
lineHeight: 20 + (i % 20) + 'px',
width: 100 + (i % 30) + 'px'
Expand All @@ -19,11 +19,11 @@
height="200px"
width={680}
scrollDirection={DIRECTION.HORIZONTAL}
items={myItems}
itemCount={10000}
model={myModel}
modelCount={myModel.length}
itemSize={150}
>
{#snippet slot({ item, style, index })}
{#snippet slot({ item, style, index }: SlotAttributes<any>)}
<div class="col" {style}>
Item #{index}
</div>
Expand Down
Loading

0 comments on commit 562fd5e

Please sign in to comment.