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:
2025-11-01 10:10:45 -04:00
parent b985a7cdbb
commit 70ca76835e
1003 changed files with 51521 additions and 36 deletions
@@ -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
+8
View File
@@ -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);