ShelfItem.svelte
1 <script lang="ts"> 2 import { getRafQueue } from '@amp/web-app-components/src/utils/rafQueue'; 3 import { onDestroy } from 'svelte'; 4 import { get, type Readable } from 'svelte/store'; 5 import type { VisibleIndexData } from '@amp/web-app-components/src/components/Shelf/store/visibleStore'; 6 7 export let index: number; 8 export let visibleStore: Readable<VisibleIndexData>; 9 10 const rafQueue = getRafQueue(); 11 const isBetween = (start: number, end: number, value: number) => { 12 return value >= start && value <= end; 13 }; 14 // get value but dont subscribe to it. 15 let { startIndex, endIndex } = get(visibleStore); 16 $: isRendered = isBetween(startIndex, endIndex, index); 17 $: isSubscribed = true; 18 19 // Elements should only be subscribed 20 // to the store if they are not rendered. 21 const unsubscribe = visibleStore.subscribe((store) => { 22 const { startIndex, endIndex } = store; 23 const currentIsRendered = isBetween(startIndex, endIndex, index); 24 // Manually handling subscription to 25 // update DOM using RAF in browser for smoother scrolling 26 if (currentIsRendered && !isRendered) { 27 rafQueue.add(() => { 28 isRendered = currentIsRendered; 29 }); 30 } 31 }); 32 33 /** 34 * Unsubscribe to the store only if `isSubscribed` is true 35 * 36 * This helps ensure that we do not accidentally call `unsubscribe` twice, 37 * which can cause errors in Svelte. One way that can happen is by unsubscribing 38 * both using `onDestory` and with the callback added to the `rafQueue` 39 * 40 * See https://github.com/sveltejs/svelte/issues/4765#issuecomment-1379243063 41 */ 42 function unsubscribeIfNeeded() { 43 if (isSubscribed) { 44 unsubscribe(); 45 isSubscribed = false; 46 } 47 } 48 49 $: if (isSubscribed && isRendered) { 50 rafQueue.add(() => { 51 unsubscribeIfNeeded(); 52 }); 53 } 54 55 onDestroy(() => { 56 unsubscribeIfNeeded(); 57 }); 58 </script> 59 60 <slot {isRendered} />