mirror of
https://github.com/transloadit/uppy.git
synced 2026-07-25 11:14:05 +00:00
* Add VirtualList to ProviderView * Import correctly from lib * fixup! Import correctly from lib * fixup! fixup! Import correctly from lib * Really do it this time * Only virtual list for lists * fixup! Only virtual list for lists * Fix scroll * Set hardcoded width/height for img, as recommened --------- Co-authored-by: Artur Paikin <artur@arturpaikin.com>
158 lines
4.8 KiB
JavaScript
158 lines
4.8 KiB
JavaScript
/**
|
|
* 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.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)
|
|
}
|
|
|
|
handleScroll = () => {
|
|
this.setState({ offset: this.base.scrollTop })
|
|
}
|
|
|
|
handleResize = () => {
|
|
this.resize()
|
|
}
|
|
|
|
resize () {
|
|
const { height } = this.state
|
|
|
|
if (height !== this.base.offsetHeight) {
|
|
this.setState({
|
|
height: this.base.offsetHeight,
|
|
})
|
|
}
|
|
}
|
|
|
|
render ({
|
|
data,
|
|
rowHeight,
|
|
renderRow,
|
|
overscanCount = 10,
|
|
...props
|
|
}) {
|
|
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
|
|
<div onScroll={this.handleScroll} {...props}>
|
|
<div role="presentation" style={styleInner}>
|
|
<div role="presentation" style={styleContent}>
|
|
{selection.map(renderRow)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
}
|
|
|
|
export default VirtualList
|