feat: preserve scroll position during note list resets
- Added scroll position preservation when resetting note list (e.g. during searches) - Implemented willReset signal in PaginationService to notify views before data resets - Created preserveOnReset effect to capture scroll offset before list changes - Added restoreAfterReset effect to restore scroll position after data reloads - Updated loadInitial and invalidateCache methods to trigger willReset notifications
This commit is contained in:
@@ -319,6 +319,7 @@ export class PaginatedNotesListComponent implements OnInit, OnDestroy {
|
||||
readonly contextMenu = inject(NoteContextMenuService);
|
||||
private editorState = inject(EditorStateService);
|
||||
private destroy$ = new Subject<void>();
|
||||
private preservedOffset: number | null = null;
|
||||
|
||||
@ViewChild(CdkVirtualScrollViewport) viewport?: CdkVirtualScrollViewport;
|
||||
|
||||
@@ -603,6 +604,38 @@ export class PaginatedNotesListComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
|
||||
// Effects
|
||||
// Capture current scroll anchor BEFORE a data reset (e.g., new search), then restore it after reload
|
||||
private preserveOnReset = effect(() => {
|
||||
// react to reset notifications
|
||||
const _tick = this.paginationService.onWillReset();
|
||||
// when incremented, capture current rendered start index as anchor
|
||||
const vp = this.viewport;
|
||||
if (vp) {
|
||||
try {
|
||||
this.preservedOffset = Math.max(0, vp.measureScrollOffset());
|
||||
} catch {
|
||||
this.preservedOffset = 0;
|
||||
}
|
||||
} else {
|
||||
this.preservedOffset = 0;
|
||||
}
|
||||
});
|
||||
|
||||
// After items recompute following a reset, scroll back to the preserved index once
|
||||
private restoreAfterReset = effect(() => {
|
||||
// Trigger on recompute
|
||||
this.visibleNotes();
|
||||
const offset = this.preservedOffset;
|
||||
if (offset != null) {
|
||||
// Defer to next microtask to ensure viewport has measured
|
||||
queueMicrotask(() => {
|
||||
const vp = this.viewport;
|
||||
if (!vp) return;
|
||||
try { vp.scrollToOffset(offset, 'auto'); } catch {}
|
||||
this.preservedOffset = null;
|
||||
});
|
||||
}
|
||||
});
|
||||
private syncQuery = effect(() => {
|
||||
this.q.set(this.query() || '');
|
||||
// If external query changes (e.g., URL/state), refresh pagination to match
|
||||
|
||||
@@ -28,6 +28,8 @@ export class PaginationService {
|
||||
private isLoading = signal(false);
|
||||
private searchTerm = signal('');
|
||||
private totalItems = signal(0);
|
||||
// Notifier fired BEFORE pages reset, so views can capture scroll anchors
|
||||
private willReset = signal(0);
|
||||
|
||||
// Computed properties
|
||||
readonly allItems = computed(() => {
|
||||
@@ -43,10 +45,14 @@ export class PaginationService {
|
||||
readonly canLoadMore = computed(() => this.hasMorePages() && !this.isLoading());
|
||||
readonly isLoadingMore = this.isLoading;
|
||||
readonly hasMore = this.hasMorePages;
|
||||
// Expose reset notifier so views can preserve scroll position across resets
|
||||
readonly onWillReset = this.willReset;
|
||||
|
||||
// Load initial page
|
||||
async loadInitial(search = ''): Promise<void> {
|
||||
this.searchTerm.set(search);
|
||||
// Notify listeners that a reset is imminent
|
||||
this.willReset.update(v => v + 1);
|
||||
this.pages.set(new Map());
|
||||
this.currentCursor.set(null);
|
||||
this.hasMorePages.set(true);
|
||||
@@ -103,6 +109,8 @@ export class PaginationService {
|
||||
|
||||
// Invalidate cache (after file changes)
|
||||
invalidateCache(): void {
|
||||
// Notify listeners that a reset is imminent
|
||||
this.willReset.update(v => v + 1);
|
||||
this.pages.set(new Map());
|
||||
this.currentCursor.set(null);
|
||||
this.hasMorePages.set(true);
|
||||
|
||||
Reference in New Issue
Block a user