import React, { HTMLAttributes, useLayoutEffect, useRef, useState } from 'react'; import styles from './silke-virtual.scss'; type SilkeVirtualRowProps = { height?: number; items: T[]; indexOffset: number; children: (item: T, index: number) => React.ReactNode; rowAttrs?: (rowIndex: number) => Omit, 'children'>; } & Omit, 'children'>; type ObserverListener = [el: HTMLElement, onVisible: (visible: boolean) => void]; const listeners: ObserverListener[] = []; function findListener(el: HTMLElement): ObserverListener | undefined { for (const pair of listeners) { if (pair[0] === el) return pair; } } let observer: IntersectionObserver; function observe(el: HTMLDivElement, onVisible: (visible: boolean) => void): () => void { if (!observer) { observer = new IntersectionObserver((e) => { for (const t of e) { const listener = findListener(t.target as HTMLElement); if (listener) listener[1](t.isIntersecting); } }); } observer.observe(el); const pair: ObserverListener = [el, onVisible]; listeners.push(pair); return () => { observer.unobserve(el); const index = listeners.indexOf(pair); if (index !== -1) listeners.splice(index, 1); }; } export function SilkeVirtualRow({ children, rowAttrs, items, className, indexOffset, style, height, ...rest }: SilkeVirtualRowProps) { let cl = styles.row; if (className) cl += ' ' + className; const ref = useRef(null); const [visible, setVisible] = useState(false); useLayoutEffect(() => { const el = ref.current; if (el) return observe(el, setVisible); }, []); if (height) style = { ...style, height }; if (rowAttrs) Object.assign(rest, rowAttrs(indexOffset)); return (
{visible && items.map((item, i) => children(item, i + indexOffset))}
); }