hooks for jellyfin data, playback, tmdb, player chrome
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
/**
|
||||
* Detects whether a sentinel element has scrolled above the top of the
|
||||
* nearest scroll container (the AppShell's `main.content-scroll`).
|
||||
* Returns `true` when the user has moved past the sentinel, `false`
|
||||
* while the sentinel is still in or below the viewport.
|
||||
*
|
||||
* Uses a ref-callback API rather than a `useRef` so the listener
|
||||
* actually attaches when the sentinel mounts. With a plain useRef the
|
||||
* setup effect runs once on first commit - which on pages that show a
|
||||
* skeleton until data loads is BEFORE the real sentinel div renders,
|
||||
* leaving `ref.current` null and the listener never wired up.
|
||||
*
|
||||
* Usage:
|
||||
* const { sentinelRef, past } = usePastSentinel()
|
||||
* return <div ref={sentinelRef} />
|
||||
*/
|
||||
export function usePastSentinel(): {
|
||||
sentinelRef: (el: HTMLElement | null) => void
|
||||
past: boolean
|
||||
} {
|
||||
const [el, setEl] = useState<HTMLElement | null>(null)
|
||||
const [past, setPast] = useState(false)
|
||||
|
||||
// Wrap the state setter so React doesn't change the callback identity
|
||||
// every render - the `ref` prop sees a stable function.
|
||||
const sentinelRef = useCallback((next: HTMLElement | null) => {
|
||||
setEl(next)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!el) return
|
||||
const root = (document.querySelector('main.content-scroll') as HTMLElement | null) || null
|
||||
let raf = 0
|
||||
const update = () => {
|
||||
raf = 0
|
||||
const rect = el.getBoundingClientRect()
|
||||
const rootTop = root ? root.getBoundingClientRect().top : 0
|
||||
setPast(rect.top <= rootTop + 1)
|
||||
}
|
||||
const schedule = () => {
|
||||
if (raf) return
|
||||
raf = requestAnimationFrame(update)
|
||||
}
|
||||
schedule()
|
||||
const target: EventTarget = root || window
|
||||
target.addEventListener('scroll', schedule, { passive: true })
|
||||
const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(schedule) : null
|
||||
ro?.observe(el)
|
||||
return () => {
|
||||
if (raf) cancelAnimationFrame(raf)
|
||||
target.removeEventListener('scroll', schedule)
|
||||
ro?.disconnect()
|
||||
}
|
||||
}, [el])
|
||||
|
||||
return { sentinelRef, past }
|
||||
}
|
||||
Reference in New Issue
Block a user