Fiks ScrollFade: sprettende scroll-hint overlappet en ekte pil-knapp
Bruker (skjermbilde): "Den sprettende ned-pilen fungerer ikke. Den ligger OPPÅ en annen nedpil." -- gjelder Retning-steget (Utslag/ Innspill) i score-registreringen, hvor "Kort"-knappen har sitt eget ArrowDown-ikon. To reelle feil i ScrollFade (round-detail.tsx): 1. ResizeObserver observerte scroll-BEHOLDEREN, ikke innholdet. Beholderens boks-størrelse er fast (flex-1), så den endrer seg aldri når man bytter steg i veiviseren -- observeren fyrte derfor ALDRI når et steg med reelt overflow-innhold ble vist. Hintet dukket rett og slett ikke opp der det trengtes (f.eks. nettopp Retning-steget). Rettet ved å observere innholds-diven i stedet. 2. Fade-/pil-overlayet er absolutt posisjonert over de siste 64px av scroll-området, uten noen garanti om at ekte innhold ikke havner der. På Retning-steget landet "Kort"-knappens egen pil nøyaktig i den sonen -- to piler oppå hverandre. Rettet med en usynlig 64px-buffer etter innholdet (samme høyde som overlayet), slik at overlayet aldri kan dekke et ekte element. hasMore-utregningen trekker fra bufferhøyden så den ikke gir falske positiver på kort innhold som uansett får plass. Scratch-verifisert (egen DB/rolle/MinIO/API/frontend-container, ekte bruker med fullt kølle-utvalg for å reprodusere nøyaktig samme rutenett som i brukerens skjermbilde): hintet vises korrekt når Retning-steget faktisk overflower, forsvinner korrekt ved reell bunn, "Kort"-knappen fullt synlig og utenfor overlay-sonen ved skrolling, lys+mørk, ingen konsollfeil. tsc --noEmit rent, 45/45 vitest grønt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
b704a9c378
commit
ee91d03514
1 changed files with 30 additions and 11 deletions
|
|
@ -6577,30 +6577,49 @@ function WizardSection({ title, children }: { title: string; children: React.Rea
|
||||||
// finnes mer innhold under fold-linjen -- og skjuler seg av seg selv idet man
|
// finnes mer innhold under fold-linjen -- og skjuler seg av seg selv idet man
|
||||||
// har scrollet helt ned. Fanger både resize og innholdsendringer (ulikt
|
// har scrollet helt ned. Fanger både resize og innholdsendringer (ulikt
|
||||||
// stat_level/par gir ulik høyde) via en ResizeObserver på selve scrollboksen.
|
// stat_level/par gir ulik høyde) via en ResizeObserver på selve scrollboksen.
|
||||||
|
// Høyden (px) på fade-/piloverlayet under -- MÅ matche spacer-diven sin
|
||||||
|
// h-16 og trekkes fra i hasMore-utregningen, se begrunnelse der.
|
||||||
|
const SCROLL_FADE_HEIGHT_PX = 64
|
||||||
|
|
||||||
function ScrollFade({ children, className }: { children: React.ReactNode; className?: string }) {
|
function ScrollFade({ children, className }: { children: React.ReactNode; className?: string }) {
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const scrollRef = useRef<HTMLDivElement>(null)
|
||||||
|
const contentRef = useRef<HTMLDivElement>(null)
|
||||||
const [hasMore, setHasMore] = useState(false)
|
const [hasMore, setHasMore] = useState(false)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = ref.current
|
const scrollEl = scrollRef.current
|
||||||
if (!el) return
|
const contentEl = contentRef.current
|
||||||
|
if (!scrollEl || !contentEl) return
|
||||||
const update = () => {
|
const update = () => {
|
||||||
// 4px slingring: unngår at en sub-piksel-rest holder hintet i live
|
// Trekker fra spacer-divens egen høyde (samme SCROLL_FADE_HEIGHT_PX)
|
||||||
// når man i praksis ER nederst.
|
// i tillegg til 4px slingring -- uten dette ville "hasMore" blitt
|
||||||
setHasMore(el.scrollTop + el.clientHeight < el.scrollHeight - 4)
|
// sant utelukkende fordi spaceren selv gjør scrollHeight større, selv
|
||||||
|
// når ALT ekte innhold allerede er synlig (falskt positivt hint).
|
||||||
|
setHasMore(scrollEl.scrollTop + scrollEl.clientHeight < scrollEl.scrollHeight - SCROLL_FADE_HEIGHT_PX - 4)
|
||||||
}
|
}
|
||||||
update()
|
update()
|
||||||
el.addEventListener("scroll", update, { passive: true })
|
scrollEl.addEventListener("scroll", update, { passive: true })
|
||||||
|
// Observerer INNHOLDS-diven, IKKE scroll-beholderen selv --
|
||||||
|
// beholderens egen boks-størrelse er fast (flex-1, bundet av
|
||||||
|
// veiviserens layout), den endrer seg ALDRI når man bytter steg. Uten
|
||||||
|
// dette ble "hasMore" aldri regnet på nytt ved stegbytte -- den
|
||||||
|
// sprettende pilen dukket rett og slett ikke opp på steg som faktisk
|
||||||
|
// trengte scrolling (f.eks. "Retning"-steget med kølle-rutenett).
|
||||||
const ro = new ResizeObserver(update)
|
const ro = new ResizeObserver(update)
|
||||||
ro.observe(el)
|
ro.observe(contentEl)
|
||||||
return () => {
|
return () => {
|
||||||
el.removeEventListener("scroll", update)
|
scrollEl.removeEventListener("scroll", update)
|
||||||
ro.disconnect()
|
ro.disconnect()
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
return (
|
return (
|
||||||
<div className="relative flex min-h-0 flex-1 flex-col">
|
<div className="relative flex min-h-0 flex-1 flex-col">
|
||||||
<div ref={ref} className={className}>
|
<div ref={scrollRef} className={className}>
|
||||||
{children}
|
<div ref={contentRef}>{children}</div>
|
||||||
|
{/* Usynlig buffer, SAMME høyde som fade-/piloverlayet -- garanterer at
|
||||||
|
overlayet aldri dekker et ekte, interaktivt element (f.eks. en
|
||||||
|
retningsknapp med sin egen pil-ikon nederst i steget). Uten denne
|
||||||
|
kunne den sprettende pilen havne rett oppå en annen pil. */}
|
||||||
|
<div aria-hidden="true" className="h-16 shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
{/* Rent dekorativt -- selve scrollingen er fullt mulig uten dette. */}
|
{/* Rent dekorativt -- selve scrollingen er fullt mulig uten dette. */}
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue