feat(VirtualList): VirtualList now supports pagination, it loads batches when user scrolls near the end of current batch

This commit is contained in:
Ilia Mashkov
2026-01-31 11:48:14 +03:00
parent 3add50a190
commit b1ce734f19
3 changed files with 85 additions and 23 deletions

View File

@@ -59,6 +59,11 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
} | null
>(null);
/**
* Accumulated fonts from all pages (for infinite scroll)
*/
#accumulatedFonts = $state<UnifiedFont[]>([]);
/**
* Pagination metadata (derived from proxy API response)
*/
@@ -84,8 +89,53 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
};
});
/**
* Track previous filter params to detect changes and reset pagination
*/
#previousFilterParams = $state<string>('');
/**
* Cleanup function for the filter tracking effect
*/
#filterCleanup: (() => void) | null = null;
constructor(initialParams: ProxyFontsParams = {}) {
super(initialParams);
// Track filter params (excluding pagination params)
// Wrapped in $effect.root() to prevent effect_orphan error
this.#filterCleanup = $effect.root(() => {
$effect(() => {
const filterParams = JSON.stringify({
provider: this.params.provider,
category: this.params.category,
subset: this.params.subset,
q: this.params.q,
});
// If filters changed, reset offset to 0
if (filterParams !== this.#previousFilterParams) {
if (this.#previousFilterParams && this.params.offset !== 0) {
this.setParams({ offset: 0 });
}
this.#previousFilterParams = filterParams;
}
});
});
}
/**
* Clean up both parent and child effects
*/
destroy() {
// Call parent cleanup (TanStack observer effect)
super.destroy();
// Call filter tracking effect cleanup
if (this.#filterCleanup) {
this.#filterCleanup();
this.#filterCleanup = null;
}
}
/**
@@ -136,17 +186,25 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
offset: response.offset ?? this.params.offset ?? 0,
};
// Accumulate fonts for infinite scroll
if (params.offset === 0) {
// Reset when starting from beginning (new search/filter)
this.#accumulatedFonts = response.fonts;
} else {
// Append new fonts to existing ones
this.#accumulatedFonts = [...this.#accumulatedFonts, ...response.fonts];
}
return response.fonts;
}
// --- Getters (proxied from BaseFontStore) ---
/**
* Get all fonts from current query result
* Get all accumulated fonts (for infinite scroll)
*/
get fonts(): UnifiedFont[] {
// The result.data is UnifiedFont[] (from TanStack Query)
return (this.result.data as UnifiedFont[] | undefined) ?? [];
return this.#accumulatedFonts;
}
/**
@@ -288,5 +346,9 @@ export function createUnifiedFontStore(params: ProxyFontsParams = {}) {
/**
* Singleton instance for global use
* Initialized with a default limit to prevent fetching all fonts at once
*/
export const unifiedFontStore = new UnifiedFontStore();
export const unifiedFontStore = new UnifiedFontStore({
limit: 50,
offset: 0,
});