Fix tab scroll arrows not updating at scroll end

- Add debounced scroll-end detection (150ms) for trackpad/touch scrolling
- Add scrollend event listener for native browser support
- Add 350ms timeout fallback after arrow button clicks
- Arrows now update immediately when reaching scroll boundaries

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-13 01:24:14 +01:00
parent 42d27bb9b4
commit 005526d5af

View File

@@ -40,6 +40,7 @@ const TabsScroller = forwardRef<HTMLDivElement, TabsScrollerProps>(
const { tabBarPosition } = useTheme(); const { tabBarPosition } = useTheme();
const sliderRef = useRef<HTMLDivElement | null>(null); const sliderRef = useRef<HTMLDivElement | null>(null);
const rafRef = useRef(0); const rafRef = useRef(0);
const scrollEndTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const [showLeft, setShowLeft] = useState(false); const [showLeft, setShowLeft] = useState(false);
const [showRight, setShowRight] = useState(false); const [showRight, setShowRight] = useState(false);
const heightRef = useRef(0); const heightRef = useRef(0);
@@ -156,8 +157,19 @@ const TabsScroller = forwardRef<HTMLDivElement, TabsScrollerProps>(
if (!node) return undefined; if (!node) return undefined;
const container = node.closest('.page-tabs-container, .admin-tabs-container'); const container = node.closest('.page-tabs-container, .admin-tabs-container');
const handleScroll = () => scheduleOverflowUpdate(); const handleScroll = () => {
scheduleOverflowUpdate();
// Debounced scroll-end detection for trackpad/touch scrolling
if (scrollEndTimeoutRef.current) {
clearTimeout(scrollEndTimeoutRef.current);
}
scrollEndTimeoutRef.current = setTimeout(() => {
updateOverflow();
}, 150);
};
const handleScrollEnd = () => updateOverflow();
node.addEventListener('scroll', handleScroll, { passive: true }); node.addEventListener('scroll', handleScroll, { passive: true });
node.addEventListener('scrollend', handleScrollEnd, { passive: true });
let resizeObserver: ResizeObserver | null = null; let resizeObserver: ResizeObserver | null = null;
if (typeof ResizeObserver !== 'undefined') { if (typeof ResizeObserver !== 'undefined') {
resizeObserver = new ResizeObserver(() => scheduleOverflowUpdate()); resizeObserver = new ResizeObserver(() => scheduleOverflowUpdate());
@@ -176,6 +188,10 @@ const TabsScroller = forwardRef<HTMLDivElement, TabsScrollerProps>(
} }
return () => { return () => {
node.removeEventListener('scroll', handleScroll); node.removeEventListener('scroll', handleScroll);
node.removeEventListener('scrollend', handleScrollEnd);
if (scrollEndTimeoutRef.current) {
clearTimeout(scrollEndTimeoutRef.current);
}
resizeObserver?.disconnect(); resizeObserver?.disconnect();
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
window.removeEventListener('resize', scheduleOverflowUpdate); window.removeEventListener('resize', scheduleOverflowUpdate);
@@ -222,8 +238,12 @@ const TabsScroller = forwardRef<HTMLDivElement, TabsScrollerProps>(
left: direction === 'left' ? -step : step, left: direction === 'left' ? -step : step,
behavior: 'smooth' behavior: 'smooth'
}); });
// Force update after smooth scroll completes (fallback for browsers without scrollend)
setTimeout(() => {
updateOverflow();
}, 350);
}, },
[scrollStep] [scrollStep, updateOverflow]
); );
const handlePointerDown = useCallback((event: PointerEvent<HTMLDivElement>) => { const handlePointerDown = useCallback((event: PointerEvent<HTMLDivElement>) => {