Skip to content

Commit

Permalink
Merge pull request #13 from sveltejs/zero-items
Browse files Browse the repository at this point in the history
Handle changes from zero to some items
  • Loading branch information
Rich-Harris authored May 7, 2018
2 parents 5233b4f + 62bc6ac commit 342f007
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 43 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"pretest": "npm run build"
},
"devDependencies": {
"faucet": "^0.0.1",
"port-authority": "^1.0.3",
"puppeteer": "^1.2.0",
"rollup": "^0.58.2",
Expand All @@ -23,6 +22,7 @@
"rollup-plugin-svelte": "^4.1.0",
"serve": "^6.5.3",
"svelte": "^2.1.0",
"tap-diff": "^0.1.1",
"tap-dot": "^1.0.5",
"tape-modern": "^1.0.0"
},
Expand Down
86 changes: 53 additions & 33 deletions src/VirtualList.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@
},

oncreate() {
const { items, _props, itemHeight } = this.get();
const { viewport, container } = this.refs;
const viewportHeight = viewport.offsetHeight;
const { items, _props } = this.get();
const { container } = this.refs;

const keys = Object.keys(this.options.data).filter(key => key !== 'items' && key !== 'component' && key !== 'height' && key !== 'itemHeight');
if (keys.length) {
Expand All @@ -47,45 +46,31 @@
this.set({ _props });
}

this.heightMap = [];
this.rows = container.getElementsByClassName('row');

if (itemHeight) {
this.heightMap = items.map(item => itemHeight);
this.set({
end: Math.min(items.length, Math.ceil(viewportHeight / itemHeight)),
bottom: items.length * itemHeight
});
} else {
this.heightMap = [];

let height = 0;
let i = 0;

while (height < viewportHeight && i < items.length) {
this.set({ end: i + 1 });

const rowHeight = this.heightMap[i] = this.rows[i].offsetHeight;
height += rowHeight;

i += 1;
}

const end = i;
const avg = Math.round(height / i);

for (; i < items.length; i += 1) this.heightMap[i] = avg;

this.set({
bottom: (items.length - end) * avg
});
if (items.length > 0) {
this.initialise();
}

this.on('state', ({ changed, previous, current }) => {
if (changed.items || changed.height || changed.itemHeight) {
if (current.itemHeight && (changed.itemHeight || current.items.length !== this.heightMap.length)) {
if (current.itemHeight && (changed.itemHeight || current.items.length > this.heightMap.length)) {
this.heightMap = current.items.map(() => current.itemHeight);
}

else if (current.items.length > this.heightMap.length) {
if (this.heightMap.length === 0) {
this.initialise();
} else {
let height = 0;
let i = 0;
for (; i < this.heightMap.length; i += 1) height += this.heightMap[i];
const avg = height / this.heightMap.length;
for (; i < current.items.length; i += 1) this.heightMap[i] = avg;
}
}

this.refresh();
}

Expand All @@ -100,6 +85,41 @@
},

methods: {
initialise() {
const { items, itemHeight } = this.get();
const { viewport } = this.refs;
const viewportHeight = viewport.offsetHeight;

if (itemHeight) {
this.heightMap = items.map(item => itemHeight);
this.set({
end: Math.min(items.length, Math.ceil(viewportHeight / itemHeight)),
bottom: items.length * itemHeight
});
} else {
let height = 0;
let i = 0;

while (height < viewportHeight && i < items.length) {
this.set({ end: i + 1 });

const rowHeight = this.heightMap[i] = this.rows[i].offsetHeight;
height += rowHeight;

i += 1;
}

const end = i;
const avg = Math.round(height / i);

for (; i < items.length; i += 1) this.heightMap[i] = avg;

this.set({
bottom: (items.length - end) * avg
});
}
},

refresh() {
const { items, start, end, itemHeight } = this.get();
const { offsetHeight, scrollTop } = this.refs.viewport;
Expand Down
97 changes: 88 additions & 9 deletions test/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,45 @@ import { assert, test, done } from 'tape-modern';
// setup
const target = document.querySelector('main');

function indent(node, spaces) {
if (node.childNodes.length === 0) return;

if (node.childNodes.length > 1 || node.childNodes[0].nodeType !== 3) {
const first = node.childNodes[0];
const last = node.childNodes[node.childNodes.length - 1];

const head = `\n${spaces} `;
const tail = `\n${spaces}`;

if (first.nodeType === 3) {
first.data = `${head}${first.data}`;
} else {
node.insertBefore(document.createTextNode(head), first);
}

if (last.nodeType === 3) {
last.data = `${last.data}${tail}`;
} else {
node.appendChild(document.createTextNode(tail));
}

let lastType = null;
for (let i = 0; i < node.childNodes.length; i += 1) {
const child = node.childNodes[i];
if (child.nodeType === 1) {
indent(node.childNodes[i], `${spaces} `);

if (lastType === 1) {
node.insertBefore(document.createTextNode(head), child);
i += 1;
}
}

lastType = child.nodeType;
}
}
}

function normalize(html) {
const div = document.createElement('div');
div.innerHTML = html
Expand All @@ -15,6 +54,8 @@ function normalize(html) {
.replace(/>\s+/g, '>')
.replace(/\s+</g, '<');

indent(div, '');

div.normalize();
return div.innerHTML;
}
Expand Down Expand Up @@ -135,22 +176,23 @@ test('props are passed to child component', t => {

test('updates when items change', t => {
const Row = svelte.create(`
<span>{foo}</span>
<div style="height: 80px;">{foo}</div>
`);

const list = new VirtualList({
target,
data: {
items: [{ foo: 'bar'}],
component: Row
component: Row,
height: '100px'
}
});

t.htmlEqual(target.innerHTML, `
<div style='height: 100%;'>
<div style='height: 100px;'>
<div style="padding-top: 0px; padding-bottom: 0px;">
<div class="row">
<span>bar</span>
<div style="height: 80px;">bar</div>
</div>
</div>
</div>
Expand All @@ -161,18 +203,55 @@ test('updates when items change', t => {
});

t.htmlEqual(target.innerHTML, `
<div style='height: 100%;'>
<div style="padding-top: 0px; padding-bottom: 0px;">
<div style='height: 100px;'>
<div style="padding-top: 0px; padding-bottom: 80px;">
<div class="row">
<span>bar</span>
<div style="height: 80px;">bar</div>
</div>
<div class="row">
<span>baz</span>
<div style="height: 80px;">baz</div>
</div>
</div>
</div>
`);

list.destroy();
});

test('updates when items change from an empty list', t => {
const Row = svelte.create(`
<div style="height: 80px;">{foo}</div>
`);

const list = new VirtualList({
target,
data: {
items: [],
component: Row,
height: '100px'
}
});

t.htmlEqual(target.innerHTML, `
<div style='height: 100px;'>
<div style="padding-top: 0px; padding-bottom: 0px;"></div>
</div>
`);

list.set({
items: [{ foo: 'bar'}, { foo: 'baz'}, { foo: 'qux'}]
});

t.htmlEqual(target.innerHTML, `
<div style='height: 100px;'>
<div style="padding-top: 0px; padding-bottom: 80px;">
<div class="row">
<span>qux</span>
<div style="height: 80px;">bar</div>
</div>
<div class="row">
<div style="height: 80px;">baz</div>
</div>
</div>
</div>
Expand Down

0 comments on commit 342f007

Please sign in to comment.