Skip to content

Commit 41f662a

Browse files
authored
Merge pull request #21 from sveltejs/v3
update for v3
2 parents d5b72ba + dc8c996 commit 41f662a

10 files changed

+408
-480
lines changed

.eslintrc.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"extends": "eslint:recommended",
3+
"parserOptions": {
4+
"ecmaVersion": 9,
5+
"sourceType": "module"
6+
},
7+
"plugins": ["svelte3"],
8+
"settings": {
9+
"svelte3/extensions": ["html"]
10+
},
11+
"env": {
12+
"browser": true
13+
}
14+
}

README.md

+27-46
Original file line numberDiff line numberDiff line change
@@ -12,40 +12,23 @@ yarn add @sveltejs/svelte-virtual-list
1212
## Usage
1313

1414
```html
15-
<VirtualList items={things} component={RowComponent} />
16-
1715
<script>
1816
import VirtualList from '@sveltejs/svelte-virtual-list';
19-
import RowComponent from './RowComponent.html';
20-
21-
export default {
22-
components: { VirtualList },
23-
24-
data() {
25-
return {
26-
things: [
27-
// these can be any values you like
28-
{ name: 'one', number: 1 },
29-
{ name: 'two', number: 2 },
30-
{ name: 'three', number: 3 },
31-
// ...
32-
{ name: 'six thousand and ninety-two', number: 6092 }
33-
],
34-
RowComponent
35-
};
36-
}
37-
};
38-
</script>
39-
```
4017
41-
The component constructor you supply to `<VirtualList>` will be instantiated for each visible member of `items`:
18+
const things = [
19+
// these can be any values you like
20+
{ name: 'one', number: 1 },
21+
{ name: 'two', number: 2 },
22+
{ name: 'three', number: 3 },
23+
// ...
24+
{ name: 'six thousand and ninety-two', number: 6092 }
25+
];
26+
</script>
4227

43-
```html
44-
<!-- RowComponent.html -->
45-
<div>
46-
<strong>{number}</strong>
47-
<span>{name}</span>
48-
</div>
28+
<VirtualList items={things} let:item>
29+
<!-- this will be rendered for each currently visible item -->
30+
<p>{item.number}: {item.name}</p>
31+
</VirtualList>
4932
```
5033

5134

@@ -54,37 +37,35 @@ The component constructor you supply to `<VirtualList>` will be instantiated for
5437
You can track which rows are visible at any given by binding to the `start` and `end` values:
5538

5639
```html
57-
<VirtualList items={things} component={RowComponent} bind:start bind:end />
40+
<VirtualList items={things} bind:start bind:end>
41+
<p>{item.number}: {item.name}</p>
42+
</VirtualList>
5843

5944
<p>showing {start}-{end} of {things.length} rows</p>
6045
```
6146

62-
You can rename them with e.g. `bind:start=a bind:end=b`.
47+
You can rename them with e.g. `bind:start={a} bind:end={b}`.
6348

6449

65-
## `itemHeight`
50+
## `height`
6651

67-
You can optimize initial display and scrolling when the height of items is known in advance.
52+
By default, the `<VirtualList>` component will fill the vertical space of its container. You can specify a different height by passing any CSS length:
6853

6954
```html
70-
<VirtualList items={things} component={RowComponent} itemHeight={48} />
55+
<VirtualList height="500px" items={things} let:item>
56+
<p>{item.number}: {item.name}</p>
57+
</VirtualList>
7158
```
7259

7360

74-
## Additional properties
75-
76-
You can add arbitrary properties to `<VirtualList>` and they will be forwarded to the rows:
61+
## `itemHeight`
7762

78-
```html
79-
<VirtualList class="funky" answer={42} items={things} component={RowComponent} />
80-
```
63+
You can optimize initial display and scrolling when the height of items is known in advance. This should be a number representing a pixel value.
8164

8265
```html
83-
<!-- RowComponent.html -->
84-
<div class="{number === answer ? 'the-answer' : ''}">
85-
<strong>{number}</strong>
86-
<span>{name}</span>
87-
</div>
66+
<VirtualList itemHeight={48} items={things} let:item>
67+
<p>{item.number}: {item.name}</p>
68+
</VirtualList>
8869
```
8970

9071

VirtualList.html

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<script>
2+
import { onMount, tick } from 'svelte';
3+
4+
// props
5+
export let items;
6+
export let height = '100%';
7+
export let itemHeight;
8+
9+
let foo;
10+
11+
// read-only, but visible to consumers via bind:start
12+
export let start = 0;
13+
export let end = 0;
14+
15+
// local state
16+
let height_map = [];
17+
let rows;
18+
let viewport;
19+
let contents;
20+
let viewport_height = 0;
21+
let visible;
22+
let mounted;
23+
24+
let top = 0;
25+
let bottom = 0;
26+
let average_height;
27+
28+
$: visible = items.slice(start, end).map((data, i) => {
29+
return { index: i + start, data };
30+
});
31+
32+
// whenever `items` changes, invalidate the current heightmap
33+
$: if (mounted) refresh(items, viewport_height, itemHeight);
34+
35+
async function refresh(items, viewport_height, itemHeight) {
36+
const { scrollTop } = viewport;
37+
38+
await tick(); // wait until the DOM is up to date
39+
40+
let content_height = top - scrollTop;
41+
let i = start;
42+
43+
while (content_height < viewport_height && i < items.length) {
44+
let row = rows[i - start];
45+
46+
if (!row) {
47+
end = i + 1;
48+
await tick(); // render the newly visible row
49+
row = rows[i - start];
50+
}
51+
52+
const row_height = height_map[i] = itemHeight || row.offsetHeight;
53+
content_height += row_height;
54+
i += 1;
55+
}
56+
57+
end = i;
58+
59+
const remaining = items.length - end;
60+
average_height = (top + content_height) / end;
61+
62+
bottom = remaining * average_height;
63+
height_map.length = items.length;
64+
65+
}
66+
67+
async function handle_scroll() {
68+
const { scrollTop } = viewport;
69+
70+
const old_start = start;
71+
72+
for (let v = 0; v < rows.length; v += 1) {
73+
height_map[start + v] = itemHeight || rows[v].offsetHeight;
74+
}
75+
76+
let i = 0;
77+
let y = 0;
78+
79+
while (i < items.length) {
80+
const row_height = height_map[i] || average_height;
81+
if (y + row_height > scrollTop) {
82+
start = i;
83+
top = y;
84+
85+
break;
86+
}
87+
88+
y += row_height;
89+
i += 1;
90+
}
91+
92+
while (i < items.length) {
93+
y += height_map[i] || average_height;
94+
i += 1;
95+
96+
if (y > scrollTop + viewport_height) break;
97+
}
98+
99+
end = i;
100+
101+
const remaining = items.length - end;
102+
average_height = y / end;
103+
104+
while (i < items.length) height_map[i++] = average_height;
105+
bottom = remaining * average_height;
106+
107+
// prevent jumping if we scrolled up into unknown territory
108+
if (start < old_start) {
109+
await tick();
110+
111+
let expected_height = 0;
112+
let actual_height = 0;
113+
114+
for (let i = start; i < old_start; i +=1) {
115+
if (rows[i - start]) {
116+
expected_height += height_map[i];
117+
actual_height += itemHeight || rows[i - start].offsetHeight;
118+
}
119+
}
120+
121+
const d = actual_height - expected_height;
122+
viewport.scrollTo(0, scrollTop + d);
123+
}
124+
125+
// TODO if we overestimated the space these
126+
// rows would occupy we may need to add some
127+
// more. maybe we can just call handle_scroll again?
128+
}
129+
130+
// trigger initial refresh
131+
onMount(() => {
132+
rows = contents.getElementsByTagName('svelte-virtual-list-row');
133+
mounted = true;
134+
});
135+
</script>
136+
137+
<style>
138+
svelte-virtual-list-viewport {
139+
position: relative;
140+
overflow-y: auto;
141+
-webkit-overflow-scrolling:touch;
142+
display: block;
143+
}
144+
145+
svelte-virtual-list-contents, svelte-virtual-list-row {
146+
display: block;
147+
}
148+
149+
svelte-virtual-list-row {
150+
overflow: hidden;
151+
}
152+
</style>
153+
154+
<svelte-virtual-list-viewport
155+
bind:this={viewport}
156+
bind:offsetHeight={viewport_height}
157+
on:scroll={handle_scroll}
158+
style="height: {height};"
159+
>
160+
<svelte-virtual-list-contents
161+
bind:this={contents}
162+
style="padding-top: {top}px; padding-bottom: {bottom}px;"
163+
>
164+
{#each visible as row (row.index)}
165+
<svelte-virtual-list-row>
166+
<slot item={row.data}>Missing template</slot>
167+
</svelte-virtual-list-row>
168+
{/each}
169+
</svelte-virtual-list-contents>
170+
</svelte-virtual-list-viewport>

package.json

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
11
{
22
"name": "@sveltejs/svelte-virtual-list",
3-
"version": "2.2.1",
3+
"version": "3.0.0-alpha.1",
44
"description": "A <VirtualList> component for Svelte apps",
5-
"svelte": "src/VirtualList.html",
6-
"module": "index.mjs",
7-
"main": "index.js",
5+
"main": "VirtualList.html",
6+
"svelte": "VirtualList.html",
87
"scripts": {
98
"build": "rollup -c",
109
"dev": "rollup -cw",
1110
"prepublishOnly": "npm test",
1211
"test": "node test/runner.js",
1312
"test:browser": "npm run build && serve test/public",
14-
"pretest": "npm run build"
13+
"pretest": "npm run build",
14+
"lint": "eslint src/VirtualList.html"
1515
},
1616
"devDependencies": {
17+
"eslint": "^5.12.1",
18+
"eslint-plugin-svelte3": "git+https://github.com/sveltejs/eslint-plugin-svelte3.git",
1719
"port-authority": "^1.0.5",
1820
"puppeteer": "^1.9.0",
19-
"rollup": "^0.66.6",
21+
"rollup": "^1.1.2",
2022
"rollup-plugin-commonjs": "^9.2.0",
21-
"rollup-plugin-node-resolve": "^3.4.0",
22-
"rollup-plugin-svelte": "^4.3.2",
23+
"rollup-plugin-node-resolve": "^4.0.0",
24+
"rollup-plugin-svelte": "^5.0.1",
2325
"sirv": "^0.2.2",
24-
"svelte": "^2.13.5",
26+
"svelte": "^3.0.0-beta.2",
2527
"tap-diff": "^0.1.1",
2628
"tap-dot": "^2.0.0",
2729
"tape-modern": "^1.1.1"

rollup.config.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@ import pkg from './package.json';
55

66
export default [
77
{
8-
input: 'src/VirtualList.html',
9-
output: [
10-
{ file: pkg.module, 'format': 'es' },
11-
{ file: pkg.main, 'format': 'umd', name: 'VirtualList' }
12-
],
8+
input: 'test/src/index.js',
9+
output: { file: 'test/public/bundle.js', 'format': 'iife' },
1310
plugins: [
1411
resolve(),
12+
commonjs(),
1513
svelte()
1614
]
1715
},

0 commit comments

Comments
 (0)