diff --git a/package.json b/package.json index b11edc6..4bbd2fb 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "main": "index.js", "scripts": { "build": "rollup -c", + "dev": "rollup -cw", "prepublishOnly": "npm test", "test": "node test/runner.js", "test:browser": "npm run build && serve test/public", diff --git a/src/VirtualList.html b/src/VirtualList.html index 5fcba6d..6048bd2 100644 --- a/src/VirtualList.html +++ b/src/VirtualList.html @@ -1,4 +1,4 @@ -
+
{#each visible as item (item.index)}
@@ -38,23 +38,13 @@ const { viewport, container } = this.refs; const viewportHeight = viewport.offsetHeight; - const keys = Object.keys(this.options.data).filter(key => key !== 'items' && key !== 'component' && key !== 'itemHeight'); + const keys = Object.keys(this.options.data).filter(key => key !== 'items' && key !== 'component' && key !== 'height' && key !== 'itemHeight'); if (keys.length) { const state = this.get(); keys.forEach(key => { _props[key] = state[key]; }); this.set({ _props }); - - this.on('state', ({ changed, current }) => { - if (!keys.some(key => changed[key])) return; - - const _props = {}; - keys.forEach(key => { - _props[key] = current[key]; - }); - this.set({ _props }); - }); } this.rows = container.getElementsByClassName('row'); @@ -89,10 +79,28 @@ bottom: (items.length - end) * avg }); } + + this.on('state', ({ changed, previous, current }) => { + if (changed.items || changed.height || changed.itemHeight) { + if (current.itemHeight && (changed.itemHeight || current.items.length !== this.heightMap.length)) { + this.heightMap = current.items.map(() => current.itemHeight); + } + + this.refresh(); + } + + if (keys.some(key => changed[key])) { + const _props = {}; + keys.forEach(key => { + _props[key] = current[key]; + }); + this.set({ _props }); + } + }); }, methods: { - handleScroll() { + refresh() { const { items, start, end, itemHeight } = this.get(); const { offsetHeight, scrollTop } = this.refs.viewport; @@ -100,17 +108,15 @@ let offset = 0; let i = 0; - if (itemHeight) { - if (this.heightMap.length !== items.length) { - this.heightMap = items.map(item => itemHeight); - } - } else { + if (!itemHeight) { for (let v = 0; v < this.rows.length; v += 1) { this.heightMap[start + v] = this.rows[v].offsetHeight; } } for (; i < items.length; i += 1) { + if (!(i in this.heightMap)) break; + offset += this.heightMap[i]; if (offset > scrollTop) break; @@ -120,7 +126,7 @@ const newStart = i++; for (; i < items.length; i += 1) { - if (offset > scrollTop + offsetHeight) break; + if (offset >= scrollTop + offsetHeight) break; offset += this.heightMap[i]; } diff --git a/test/src/index.js b/test/src/index.js index 4aeaf1c..cb093d7 100644 --- a/test/src/index.js +++ b/test/src/index.js @@ -83,8 +83,7 @@ test('allows item height to be specified', t => { list.set({ itemHeight: 50 }); - // TODO, run handleScroll when items or itemHeight is updated? Probably not needed. - // t.equal(div.getElementsByClassName('row').length, 3); + t.equal(div.getElementsByClassName('row').length, 3); list.destroy(); }); @@ -134,5 +133,53 @@ test('props are passed to child component', t => { list.destroy(); }); +test('updates when items change', t => { + const Row = svelte.create(` + {foo} + `); + + const list = new VirtualList({ + target, + data: { + items: [{ foo: 'bar'}], + component: Row + } + }); + + t.htmlEqual(target.innerHTML, ` +
+
+
+ bar +
+
+
+ `); + + list.set({ + items: [{ foo: 'bar'}, { foo: 'baz'}, { foo: 'qux'}] + }); + + t.htmlEqual(target.innerHTML, ` +
+
+
+ bar +
+ +
+ baz +
+ +
+ qux +
+
+
+ `); + + list.destroy(); +}); + // this allows us to close puppeteer once tests have completed window.done = done; \ No newline at end of file