124 lines
		
	
	
		
			No EOL
		
	
	
		
			3.9 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			124 lines
		
	
	
		
			No EOL
		
	
	
		
			3.9 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <VirtualListContainer :realm :containerQuery on:initialized on:noNeedToScroll >
 | |
|   <div class="virtual-list"
 | |
|        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;
 | |
|   }
 | |
| </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-es/throttle'
 | |
|   import { mark, stop } from '../../_utils/marks'
 | |
|   import isEqual from 'lodash-es/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> |