// Reine Virtualisierungs-Logik (Akzeptanzkriterium 2: Liste bleibt bei // vielen Einträgen flüssig / Pflichtprüfung 1: 10.000 Zeilen ruckelfrei) // — bewusst ohne Drittanbieter-Bibliothek: fixe Zeilenhöhe genügt für eine // tabellarische Dokumentenliste, die Berechnung ist eine reine Funktion // von Scrollposition/Containerhöhe, unabhängig vom DOM testbar. export interface VisibleRange { startIndex: number; endIndex: number; // exklusiv offsetY: number; // Pixel-Versatz des ersten sichtbaren Elements } // computeVisibleRange berechnet, welche Zeilen tatsächlich gerendert // werden müssen — nur der sichtbare Bereich plus overscan-Puffer // (verhindert kurzes Weiß-Aufblitzen bei schnellem Scrollen), NICHT alle // totalRows Zeilen. Das ist der gesamte Trick der Virtualisierung. export function computeVisibleRange( scrollTop: number, containerHeight: number, rowHeight: number, totalRows: number, overscan = 5 ): VisibleRange { if (totalRows <= 0 || rowHeight <= 0) { return { startIndex: 0, endIndex: 0, offsetY: 0 }; } const firstVisible = Math.floor(scrollTop / rowHeight); const visibleCount = Math.ceil(containerHeight / rowHeight); // Sowohl start- als auch endIndex muessen auf totalRows geklemmt werden // - ein (z.B. veralteter) scrollTop jenseits des tatsaechlichen Inhalts // durfte sonst einen startIndex > totalRows liefern. const startIndex = Math.min(Math.max(0, firstVisible - overscan), totalRows); const endIndex = Math.min(totalRows, firstVisible + visibleCount + overscan); return { startIndex, endIndex, offsetY: startIndex * rowHeight }; } export function totalContentHeight(totalRows: number, rowHeight: number): number { return totalRows * rowHeight; }