MCPcopy Create free account
hub / github.com/callumalpass/tasknotes / VirtualScroller

Class VirtualScroller

src/utils/VirtualScroller.ts:70–892  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

68};
69
70export class VirtualScroller<T> {
71 private container: HTMLElement;
72 private scrollContainer: HTMLElement;
73 private contentContainer: HTMLElement;
74 private spacer: HTMLElement;
75
76 private items: T[] = [];
77 private estimatedHeight: number; // Default estimated height for unmeasured items
78 private overscan: number;
79 private renderItem: (item: T, index: number) => HTMLElement;
80 private getItemKey: (item: T, index: number) => string;
81
82 private state: VirtualScrollState = {
83 startIndex: 0,
84 endIndex: 0,
85 totalItems: 0,
86 offsetY: 0,
87 };
88
89 private renderedElements = new Map<string, HTMLElement>();
90 private scrollRAF: number | null = null;
91
92 // Variable height tracking
93 private itemHeights = new Map<number, number>(); // index -> measured height
94 private positionCache: number[] = []; // Cumulative positions [0, 60, 125, 200...]
95 private totalHeight = 0;
96 private resizeObserver: ResizeObserver | null = null;
97 private measurementRAF: number | null = null;
98 private pendingMeasurements = new Set<number>();
99 private invalidatedKeys = new Set<string>();
100
101 constructor(options: VirtualScrollerOptions<T>) {
102 this.container = options.container;
103 this.items = options.items;
104 this.estimatedHeight = options.itemHeight ?? 0; // Will be calculated if not provided
105 this.overscan = options.overscan ?? 5;
106 this.renderItem = options.renderItem;
107 this.getItemKey = options.getItemKey ?? ((item, index) => String(index));
108
109 this.setupDOM();
110 this.attachScrollListener();
111 this.setupResizeObserver();
112
113 // If no itemHeight provided, calculate from sample
114 if (!options.itemHeight && this.items.length > 0) {
115 this.calculateEstimatedHeight();
116 }
117
118 this.rebuildPositionCache();
119 this.updateVisibleRange();
120 }
121
122 private setupDOM(): void {
123 // Clear existing content
124 this.container.empty();
125
126 // Container should just be relative, parent handles overflow
127 this.container.classList.remove("tn-static-margin-top-12px-91e0f558");

Callers

nothing calls this directly

Calls 1

updateVisibleRangeMethod · 0.95

Tested by

no test coverage detected