<VirtualListContainer :realm :containerQuery > <div class="virtual-list {{shown ? '' : 'hidden'}}" style="height: {{$height}}px;" ref:node > <VirtualListHeader component="{{headerComponent}}" virtualProps="{{headerProps}}" shown="{{$showHeader}}"/> {{#if $visibleItems}} {{#each $visibleItems as visibleItem @key}} <VirtualListLazyItem :component offset="{{visibleItem.offset}}" makeProps="{{makeProps}}" key="{{visibleItem.key}}" index="{{visibleItem.index}}" /> {{/each}} {{/if}} {{#if $showFooter}} <VirtualListFooter component="{{footerComponent}}" /> {{/if}} </div> </VirtualListContainer> <style> .virtual-list { position: relative; transition: opacity 0.25s linear; } </style> <script> import VirtualListContainer from './VirtualListContainer.html' import VirtualListLazyItem from './VirtualListLazyItem' import VirtualListFooter from './VirtualListFooter.html' import VirtualListHeader from './VirtualListHeader.html' import { virtualListStore } from './virtualListStore' import throttle from 'lodash/throttle' import { mark, stop } from '../../_utils/marks' import isEqual from 'lodash/isEqual' const DISTANCE_FROM_BOTTOM_TO_FIRE = 800 const SCROLL_EVENT_THROTTLE = 1000 export default { oncreate () { this.fireScrollToBottom = throttle(() => { this.fire('scrollToBottom') }, SCROLL_EVENT_THROTTLE) this.fireScrollToTop = throttle(() => { this.fire('scrollToTop') }, SCROLL_EVENT_THROTTLE) this.observe('showFooter', showFooter => { mark('set showFooter') this.store.setForRealm({showFooter: showFooter}) mark('set showFooter') }) this.observe('showHeader', showHeader => { mark('set showHeader') this.store.setForRealm({showHeader: showHeader}) stop('set showHeader') }) this.observe('items', (newItems, oldItems) => { if (!newItems || isEqual(newItems, oldItems)) { return } mark('set items') this.store.setForRealm({items: newItems}) stop('set items') }) this.observe('allVisibleItemsHaveHeight', allVisibleItemsHaveHeight => { if (allVisibleItemsHaveHeight) { this.fire('initializedVisibleItems') } }) let observedOnce = false this.observe('distanceFromBottom', (distanceFromBottom) => { if (!observedOnce) { observedOnce = true // TODO: the first time is always 0... need better way to handle this return } if (distanceFromBottom >= 0 && distanceFromBottom <= DISTANCE_FROM_BOTTOM_TO_FIRE) { this.fireScrollToBottom() } }) this.observe('scrollTop', (scrollTop) => { this.fire('scrollTopChanged', scrollTop) if (scrollTop === 0) { this.fireScrollToTop() } this.calculateListOffset() }) }, data: () => ({ component: null }), store: () => virtualListStore, components: { VirtualListContainer, VirtualListLazyItem, VirtualListFooter, VirtualListHeader }, computed: { distanceFromBottom: ($scrollHeight, $scrollTop, $offsetHeight) => { return $scrollHeight - $scrollTop - $offsetHeight }, scrollTop: ($scrollTop) => $scrollTop, // TODO: bug in svelte store, shouldn't need to do this allVisibleItemsHaveHeight: ($allVisibleItemsHaveHeight) => $allVisibleItemsHaveHeight, }, methods: { calculateListOffset() { // TODO: better way to get the offset top? let node = this.refs.node if (!node) { return } mark('calculateListOffset') let listOffset = node.offsetParent.offsetTop this.store.setForRealm({listOffset}) stop('calculateListOffset') } } } </script>