2018-01-15 21:23:28 +01:00
|
|
|
import { Store } from 'svelte/store.js'
|
2018-01-17 09:59:15 +01:00
|
|
|
import { mark, stop } from '../_utils/marks'
|
2018-01-15 21:23:28 +01:00
|
|
|
|
2018-01-21 00:37:40 +01:00
|
|
|
const VIEWPORT_RENDER_FACTOR = 4
|
|
|
|
|
2018-01-24 18:47:31 +01:00
|
|
|
const cloneKeys = [
|
|
|
|
'items',
|
|
|
|
'itemHeights',
|
|
|
|
'scrollTop',
|
|
|
|
'scrollHeight',
|
|
|
|
'offsetHeight'
|
|
|
|
]
|
|
|
|
|
2018-01-15 21:23:28 +01:00
|
|
|
class VirtualListStore extends Store {
|
2018-01-18 04:53:12 +01:00
|
|
|
constructor(state) {
|
|
|
|
super(state)
|
2018-01-18 04:41:37 +01:00
|
|
|
this._batches = {}
|
|
|
|
}
|
|
|
|
|
2018-01-24 18:47:31 +01:00
|
|
|
cloneState() {
|
|
|
|
let res = {}
|
|
|
|
for (let key of cloneKeys) {
|
|
|
|
res[key] = this.get(key)
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2018-01-18 04:41:37 +01:00
|
|
|
batchUpdate(key, subKey, value) {
|
|
|
|
let batch = this._batches[key]
|
|
|
|
if (!batch) {
|
|
|
|
batch = this._batches[key] = {}
|
|
|
|
}
|
|
|
|
batch[subKey] = value
|
|
|
|
|
|
|
|
requestAnimationFrame(() => {
|
2018-01-18 04:52:18 +01:00
|
|
|
let batch = this._batches[key]
|
|
|
|
if (!batch) {
|
|
|
|
return
|
|
|
|
}
|
2018-01-18 04:41:37 +01:00
|
|
|
let updatedKeys = Object.keys(batch)
|
|
|
|
if (!updatedKeys.length) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
mark('batchUpdate()')
|
|
|
|
let obj = this.get(key)
|
|
|
|
for (let otherKey of updatedKeys) {
|
|
|
|
obj[otherKey] = batch[otherKey]
|
|
|
|
}
|
|
|
|
delete this._batches[key]
|
|
|
|
let toSet = {}
|
|
|
|
toSet[key] = obj
|
|
|
|
this.set(toSet)
|
|
|
|
stop('batchUpdate()')
|
|
|
|
})
|
|
|
|
}
|
2018-01-15 21:23:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const virtualListStore = new VirtualListStore({
|
|
|
|
items: [],
|
|
|
|
itemHeights: {},
|
2018-01-22 01:07:11 +01:00
|
|
|
showFooter: false,
|
|
|
|
footerHeight: 0
|
2018-01-15 21:23:28 +01:00
|
|
|
})
|
|
|
|
|
2018-01-16 01:12:07 +01:00
|
|
|
virtualListStore.compute('visibleItems',
|
2018-01-17 06:43:31 +01:00
|
|
|
['items', 'scrollTop', 'itemHeights', 'offsetHeight'],
|
|
|
|
(items, scrollTop, itemHeights, offsetHeight) => {
|
2018-01-17 09:59:15 +01:00
|
|
|
mark('compute visibleItems')
|
2018-01-21 00:37:40 +01:00
|
|
|
let renderBuffer = VIEWPORT_RENDER_FACTOR * offsetHeight
|
2018-01-16 01:12:07 +01:00
|
|
|
let visibleItems = []
|
2018-01-16 02:25:32 +01:00
|
|
|
let totalOffset = 0
|
|
|
|
let len = items.length
|
|
|
|
let i = -1
|
|
|
|
while (++i < len) {
|
2018-01-24 03:19:03 +01:00
|
|
|
let key = items[i]
|
2018-01-16 01:35:08 +01:00
|
|
|
let height = itemHeights[key] || 0
|
2018-01-16 02:25:32 +01:00
|
|
|
let currentOffset = totalOffset
|
|
|
|
totalOffset += height
|
|
|
|
let isBelowViewport = (currentOffset < scrollTop)
|
|
|
|
if (isBelowViewport) {
|
2018-01-16 03:29:28 +01:00
|
|
|
if (scrollTop - renderBuffer > currentOffset) {
|
2018-01-16 02:25:32 +01:00
|
|
|
continue // below the area we want to render
|
|
|
|
}
|
2018-01-16 01:12:07 +01:00
|
|
|
} else {
|
2018-01-17 08:16:15 +01:00
|
|
|
if (currentOffset > (scrollTop + height + renderBuffer)) {
|
2018-01-16 02:25:32 +01:00
|
|
|
break // above the area we want to render
|
|
|
|
}
|
2018-01-16 01:12:07 +01:00
|
|
|
}
|
2018-01-16 02:25:32 +01:00
|
|
|
visibleItems.push({
|
|
|
|
offset: currentOffset,
|
|
|
|
key: key,
|
2018-01-17 08:16:15 +01:00
|
|
|
index: i
|
2018-01-16 02:25:32 +01:00
|
|
|
})
|
|
|
|
}
|
2018-01-17 09:59:15 +01:00
|
|
|
stop('compute visibleItems')
|
2018-01-16 01:12:07 +01:00
|
|
|
return visibleItems
|
2018-01-15 21:23:28 +01:00
|
|
|
})
|
|
|
|
|
2018-01-22 01:07:11 +01:00
|
|
|
virtualListStore.compute('heightWithoutFooter',
|
|
|
|
['items', 'itemHeights'],
|
|
|
|
(items, itemHeights) => {
|
2018-01-15 21:23:28 +01:00
|
|
|
let sum = 0
|
2018-01-16 02:25:32 +01:00
|
|
|
let i = -1
|
|
|
|
let len = items.length
|
|
|
|
while (++i < len) {
|
2018-01-24 03:19:03 +01:00
|
|
|
sum += itemHeights[items[i]] || 0
|
2018-01-16 02:25:32 +01:00
|
|
|
}
|
2018-01-15 21:23:28 +01:00
|
|
|
return sum
|
|
|
|
})
|
|
|
|
|
2018-01-22 01:07:11 +01:00
|
|
|
|
|
|
|
virtualListStore.compute('height',
|
|
|
|
['heightWithoutFooter', 'showFooter', 'footerHeight'],
|
|
|
|
(heightWithoutFooter, showFooter, footerHeight) => {
|
|
|
|
return showFooter ? (heightWithoutFooter + footerHeight) : heightWithoutFooter
|
|
|
|
})
|
|
|
|
|
2018-01-18 08:00:33 +01:00
|
|
|
virtualListStore.compute('numItems', ['items'], (items) => items.length)
|
|
|
|
|
2018-01-25 03:04:25 +01:00
|
|
|
virtualListStore.compute('allVisibleItemsHaveHeight',
|
|
|
|
['visibleItems', 'itemHeights'],
|
|
|
|
(visibleItems, itemHeights) => {
|
2018-01-25 03:58:10 +01:00
|
|
|
if (!visibleItems.length) {
|
|
|
|
return false
|
|
|
|
}
|
2018-01-25 03:04:25 +01:00
|
|
|
for (let visibleItem of visibleItems) {
|
|
|
|
if (!itemHeights[visibleItem.key]) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
|
2018-01-15 21:23:28 +01:00
|
|
|
if (process.browser && process.env.NODE_ENV !== 'production') {
|
|
|
|
window.virtualListStore = virtualListStore
|
|
|
|
}
|
|
|
|
|
|
|
|
export {
|
|
|
|
virtualListStore
|
|
|
|
}
|