feat: Add grace period for user activity handling in IdleMainPagesSlideshow to prevent premature slideshow reset after navigation

This commit is contained in:
sebseb7
2026-03-26 21:24:46 +01:00
parent defe3c9521
commit ba66b82b2b

View File

@@ -13,6 +13,13 @@ const SLIDESHOW_STEP_MS = 14_000;
/** Ignore duplicate events (mousemove etc.) within this window. */ /** Ignore duplicate events (mousemove etc.) within this window. */
const ACTIVITY_THROTTLE_MS = 400; const ACTIVITY_THROTTLE_MS = 400;
/**
* After auto-navigation, ignore user-activity handlers briefly — route changes
* often emit scroll / mousemove / focus events that would call resetIdle() and
* clear the slideshow interval (only one slide before stopping).
*/
const POST_NAV_GRACE_MS = 3_000;
/** /**
* After idle on /, /aktionen, or /filiale, cycles those routes slowly. * After idle on /, /aktionen, or /filiale, cycles those routes slowly.
* Lives outside MainPageLayout so it is not reset when the route changes. * Lives outside MainPageLayout so it is not reset when the route changes.
@@ -25,6 +32,10 @@ export default function IdleMainPagesSlideshow() {
const pathRef = useRef(location.pathname); const pathRef = useRef(location.pathname);
const wasOnMainPageRef = useRef(false); const wasOnMainPageRef = useRef(false);
const lastActivityRef = useRef(0); const lastActivityRef = useRef(0);
const ignoreActivityUntilRef = useRef(0);
const resetIdleRef = useRef(() => {});
const clearTimersRef = useRef(() => {});
pathRef.current = location.pathname; pathRef.current = location.pathname;
@@ -39,11 +50,14 @@ export default function IdleMainPagesSlideshow() {
} }
}, []); }, []);
clearTimersRef.current = clearTimers;
const startSlideshow = useCallback(() => { const startSlideshow = useCallback(() => {
let idx = MAIN_PAGE_PATHS.indexOf(pathRef.current); let idx = MAIN_PAGE_PATHS.indexOf(pathRef.current);
if (idx < 0) idx = 0; if (idx < 0) idx = 0;
const advance = () => { const advance = () => {
idx = (idx + 1) % MAIN_PAGE_PATHS.length; idx = (idx + 1) % MAIN_PAGE_PATHS.length;
ignoreActivityUntilRef.current = Date.now() + POST_NAV_GRACE_MS;
navigate(MAIN_PAGE_PATHS[idx], { replace: true }); navigate(MAIN_PAGE_PATHS[idx], { replace: true });
}; };
slideTimerRef.current = setInterval(advance, SLIDESHOW_STEP_MS); slideTimerRef.current = setInterval(advance, SLIDESHOW_STEP_MS);
@@ -58,6 +72,8 @@ export default function IdleMainPagesSlideshow() {
}, IDLE_MS); }, IDLE_MS);
}, [clearTimers, startSlideshow]); }, [clearTimers, startSlideshow]);
resetIdleRef.current = resetIdle;
useEffect(() => { useEffect(() => {
const nowMain = MAIN_PAGE_PATHS.includes(location.pathname); const nowMain = MAIN_PAGE_PATHS.includes(location.pathname);
if (!nowMain) { if (!nowMain) {
@@ -73,10 +89,11 @@ export default function IdleMainPagesSlideshow() {
useEffect(() => { useEffect(() => {
const onActivity = () => { const onActivity = () => {
const t = Date.now(); const now = Date.now();
if (t - lastActivityRef.current < ACTIVITY_THROTTLE_MS) return; if (now < ignoreActivityUntilRef.current) return;
lastActivityRef.current = t; if (now - lastActivityRef.current < ACTIVITY_THROTTLE_MS) return;
resetIdle(); lastActivityRef.current = now;
resetIdleRef.current();
}; };
const events = [ const events = [
@@ -96,9 +113,9 @@ export default function IdleMainPagesSlideshow() {
return () => { return () => {
events.forEach((ev) => window.removeEventListener(ev, onActivity)); events.forEach((ev) => window.removeEventListener(ev, onActivity));
window.removeEventListener("mousemove", onActivity); window.removeEventListener("mousemove", onActivity);
clearTimers(); clearTimersRef.current();
}; };
}, [resetIdle, clearTimers]); }, []);
return null; return null;
} }