fix(home): Sort-Chip-Wechsel behält Scroll-Position

Beim Klick auf eine andere Sort-Pille wurde allRecipes=[] eager
gesetzt; dadurch kollabierte der Block unter den Chips und der
Browser snapte nach oben. Jetzt laden wir erst die neuen Treffer,
tauschen dann atomar. Sollte sich die Block-Höhe trotzdem ändern
(z.B. von 50 geladenen Items zurück auf 10), korrigieren wir per
scrollBy-Delta, damit die Chips visuell an Ort bleiben.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-18 14:34:08 +02:00
parent 8c93099d91
commit e56c1543d8

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { onMount } from 'svelte';
import { onMount, tick } from 'svelte';
import { page } from '$app/stores';
import { CookingPot, X } from 'lucide-svelte';
import type { Snapshot } from './$types';
@@ -43,6 +43,7 @@
let allExhausted = $state(false);
let allLoading = $state(false);
let allSentinel: HTMLElement | undefined = $state();
let allChips: HTMLElement | undefined = $state();
let allObserver: IntersectionObserver | null = null;
type SearchSnapshot = {
@@ -105,10 +106,34 @@
}
}
function resetAllRecipes() {
allRecipes = [];
allExhausted = false;
allLoading = false;
async function setAllSort(next: AllSort) {
if (next === allSort) return;
allSort = next;
if (typeof window !== 'undefined') localStorage.setItem('kochwas.allSort', next);
if (allLoading) return;
// Position der Sort-Chips vor dem Swap merken — wenn der Rezept-Block
// beim Tausch kürzer wird, hält der Browser sonst nicht Schritt und
// snapt nach oben. Wir korrigieren nach dem Render per scrollBy.
const chipsBefore = allChips?.getBoundingClientRect().top ?? 0;
allLoading = true;
try {
const res = await fetch(
`/api/recipes/all?sort=${next}&limit=${ALL_PAGE}&offset=0`
);
if (!res.ok) return;
const body = await res.json();
const hits = body.hits as SearchHit[];
allRecipes = hits;
allExhausted = hits.length < ALL_PAGE;
await tick();
const chipsAfter = allChips?.getBoundingClientRect().top ?? 0;
const delta = chipsAfter - chipsBefore;
if (typeof window !== 'undefined' && Math.abs(delta) > 1) {
window.scrollBy({ top: delta, left: 0, behavior: 'instant' });
}
} finally {
allLoading = false;
}
}
async function loadFavorites(profileId: number) {
@@ -136,14 +161,6 @@
void loadAllMore();
});
function setAllSort(next: AllSort) {
if (next === allSort) return;
allSort = next;
if (typeof window !== 'undefined') localStorage.setItem('kochwas.allSort', next);
resetAllRecipes();
void loadAllMore();
}
// IntersectionObserver an den Sentinel hängen — wenn sichtbar, nachladen.
$effect(() => {
if (typeof window === 'undefined') return;
@@ -500,7 +517,12 @@
<div class="listing-head">
<h2>Alle Rezepte</h2>
</div>
<div class="sort-chips" role="tablist" aria-label="Sortierung">
<div
class="sort-chips"
role="tablist"
aria-label="Sortierung"
bind:this={allChips}
>
{#each ALL_SORTS as s (s.value)}
<button
type="button"