import { useEffect, useRef } from 'react'; import { CSS } from '../core/constants'; import { useGriddyStore } from '../core/GriddyStore'; import styles from '../styles/griddy.module.css'; import { TableRow } from './TableRow'; export function VirtualBody() { const table = useGriddyStore((s) => s._table); const virtualizer = useGriddyStore((s) => s._virtualizer); const setTotalRows = useGriddyStore((s) => s.setTotalRows); const infiniteScroll = useGriddyStore((s) => s.infiniteScroll); const rows = table?.getRowModel().rows; const virtualRows = virtualizer?.getVirtualItems(); const totalSize = virtualizer?.getTotalSize() ?? 0; // Track if we're currently loading to prevent multiple simultaneous calls const isLoadingRef = useRef(false); // Sync row count to store for keyboard navigation bounds useEffect(() => { if (rows) { setTotalRows(rows.length); } }, [rows?.length, setTotalRows]); // Infinite scroll: detect when approaching the end useEffect(() => { if (!infiniteScroll?.enabled || !infiniteScroll.onLoadMore || !virtualRows || !rows) { return; } const { hasMore = true, isLoading = false, threshold = 10 } = infiniteScroll; // Don't trigger if already loading or no more data if (isLoading || !hasMore || isLoadingRef.current) { return; } // Check if the last rendered virtual row is within threshold of the end const lastVirtualRow = virtualRows[virtualRows.length - 1]; if (!lastVirtualRow) return; const lastVirtualIndex = lastVirtualRow.index; const totalRows = rows.length; const distanceFromEnd = totalRows - lastVirtualIndex - 1; if (distanceFromEnd <= threshold) { isLoadingRef.current = true; const loadPromise = infiniteScroll.onLoadMore(); if (loadPromise instanceof Promise) { loadPromise.finally(() => { isLoadingRef.current = false; }); } else { isLoadingRef.current = false; } } }, [virtualRows, rows, infiniteScroll]); if (!table || !virtualizer || !rows || !virtualRows) return null; const showLoadingIndicator = infiniteScroll?.enabled && infiniteScroll.isLoading; return (