UNPKG

@uppy/utils

Version:

Shared utility functions for Uppy Core and plugins maintained by the Uppy team.

160 lines (152 loc) 5.36 kB
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } /** * Adapted from preact-virtual-list: https://github.com/developit/preact-virtual-list * * © 2016 Jason Miller * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Adaptations: * - Added role=presentation to helper elements * - Tweaked styles for Uppy's Dashboard use case */ import { h, Component } from 'preact'; const STYLE_INNER = { position: 'relative', // Disabled for our use case: the wrapper elements around FileList already deal with overflow, // and this additional property would hide things that we want to show. // // overflow: 'hidden', width: '100%', minHeight: '100%' }; const STYLE_CONTENT = { position: 'absolute', top: 0, left: 0, // Because the `top` value gets set to some offset, this `height` being 100% would make the scrollbar // stretch far beyond the content. For our use case, the content div actually can get its height from // the elements inside it, so we don't need to specify a `height` property at all. // // height: '100%', width: '100%', overflow: 'visible' }; class VirtualList extends Component { constructor(props) { super(props); // The currently focused node, used to retain focus when the visible rows change. // To avoid update loops, this should not cause state updates, so it's kept as a plain property. this.handleScroll = () => { this.setState({ offset: this.base.scrollTop }); }; this.handleResize = () => { this.resize(); }; this.focusElement = null; this.state = { offset: 0, height: 0 }; } componentDidMount() { this.resize(); window.addEventListener('resize', this.handleResize); } // TODO: refactor to stable lifecycle method // eslint-disable-next-line componentWillUpdate() { if (this.base.contains(document.activeElement)) { this.focusElement = document.activeElement; } } componentDidUpdate() { // Maintain focus when rows are added and removed. if (this.focusElement && this.focusElement.parentNode && document.activeElement !== this.focusElement) { this.focusElement.focus(); } this.focusElement = null; this.resize(); } componentWillUnmount() { window.removeEventListener('resize', this.handleResize); } resize() { const { height } = this.state; if (height !== this.base.offsetHeight) { this.setState({ height: this.base.offsetHeight }); } } render(_ref) { let { data, rowHeight, renderRow, overscanCount = 10, ...props } = _ref; const { offset, height } = this.state; // first visible row index let start = Math.floor(offset / rowHeight); // actual number of visible rows (without overscan) let visibleRowCount = Math.floor(height / rowHeight); // 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 + padding to allow keyboard focus to travel past the visible area const end = start + visibleRowCount + 4; // data slice currently in viewport plus overscan items const selection = data.slice(start, end); const styleInner = { ...STYLE_INNER, height: data.length * rowHeight }; const styleContent = { ...STYLE_CONTENT, top: start * rowHeight }; // The `role="presentation"` attributes ensure that these wrapper elements are not treated as list // items by accessibility and outline tools. return ( // eslint-disable-next-line react/jsx-props-no-spreading h("div", _extends({ onScroll: this.handleScroll }, props), h("div", { role: "presentation", style: styleInner }, h("div", { role: "presentation", style: styleContent }, selection.map(renderRow)))) ); } } export default VirtualList;