/**************************************************************************** * Copyright 2017 EPAM Systems * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ***************************************************************************/ import { h, Component } from 'preact'; /** @jsx h */ const STYLE_INNER = 'position:relative; overflow:hidden; width:100%; min-height:100%;'; const STYLE_CONTENT = 'position:absolute; top:0; left:0; height:100%; width:100%; overflow:visible;'; export default class VirtualList extends Component { constructor(props) { super(props); this.state = { offset: 0, height: 0 }; } resize = (ev, reset) => { const height = this.base.offsetHeight; if (this.state.height !== height) { this.setState({ height }); } if (reset) { this.setState({offset: 0}); this.base.scrollTop = 0; } }; handleScroll = () => { this.setState({ offset: this.base.scrollTop }); if (this.props.sync) this.forceUpdate(); }; componentDidUpdate({data}) { const equal = (data.length === this.props.data.length && this.props.data.every((v, i)=> v === data[i])); this.resize(null, !equal); } componentDidMount() { this.resize(); addEventListener('resize', this.resize); } componentWillUnmount() { removeEventListener('resize', this.resize); } render() { const { data, rowHeight, children, Tag="div", overscanCount=1, sync, ...props } = this.props; const { offset, height } = this.state; // first visible row index let start = (offset / rowHeight) || 0; const renderRow = children[0]; // actual number of visible rows (without overscan) let visibleRowCount = (height / rowHeight) || 0; // Overscan: render blocks of rows modulo an overscan row count // This dramatically reduces DOM writes during scrolling if (overscanCount) { start = Math.max(0, start - (start % overscanCount)); visibleRowCount += overscanCount; } // last visible + overscan row index const end = start + 1 + visibleRowCount; // data slice currently in viewport plus overscan items let selection = data.slice(start, end); return (