Enhancement: Add wheel scrolling support for TV guide and synchronize scrolling with timeline

This commit is contained in:
SergeantPanda 2025-11-13 19:47:39 -06:00
parent 6e79b37a66
commit 4b5d3047bb
2 changed files with 73 additions and 14 deletions

View file

@ -275,6 +275,7 @@ export default function TVChannelGuide({ startDate, endDate }) {
const guideRef = useRef(null);
const timelineRef = useRef(null); // New ref for timeline scrolling
const listRef = useRef(null);
const tvGuideRef = useRef(null); // Ref for the main tv-guide wrapper
const isSyncingScroll = useRef(false);
const guideScrollLeftRef = useRef(0);
const {
@ -506,6 +507,10 @@ export default function TVChannelGuide({ startDate, endDate }) {
if (!node) return undefined;
const handleScroll = () => {
if (isSyncingScroll.current) {
return;
}
const { scrollLeft } = node;
if (scrollLeft === guideScrollLeftRef.current) {
return;
@ -514,10 +519,6 @@ export default function TVChannelGuide({ startDate, endDate }) {
guideScrollLeftRef.current = scrollLeft;
setGuideScrollLeft(scrollLeft);
if (isSyncingScroll.current) {
return;
}
if (
timelineRef.current &&
timelineRef.current.scrollLeft !== scrollLeft
@ -531,12 +532,13 @@ export default function TVChannelGuide({ startDate, endDate }) {
};
node.addEventListener('scroll', handleScroll, { passive: true });
return () => {
node.removeEventListener('scroll', handleScroll);
};
}, []);
// Update now every second
// Update "now" every second
useEffect(() => {
const interval = setInterval(() => {
setNow(dayjs());
@ -544,13 +546,67 @@ export default function TVChannelGuide({ startDate, endDate }) {
return () => clearInterval(interval);
}, []);
// Pixel offset for the now vertical line
// Pixel offset for the "now" vertical line
const nowPosition = useMemo(() => {
if (now.isBefore(start) || now.isAfter(end)) return -1;
const minutesSinceStart = now.diff(start, 'minute');
return (minutesSinceStart / MINUTE_INCREMENT) * MINUTE_BLOCK_WIDTH;
}, [now, start, end]);
useEffect(() => {
const tvGuide = tvGuideRef.current;
if (!tvGuide) return undefined;
const handleContainerWheel = (event) => {
const guide = guideRef.current;
const timeline = timelineRef.current;
if (!guide) {
return;
}
if (event.deltaX !== 0 || (event.shiftKey && event.deltaY !== 0)) {
event.preventDefault();
event.stopPropagation();
const delta = event.deltaX !== 0 ? event.deltaX : event.deltaY;
const newScrollLeft = guide.scrollLeft + delta;
// Set both guide and timeline scroll positions
if (typeof guide.scrollTo === 'function') {
guide.scrollTo({ left: newScrollLeft, behavior: 'auto' });
} else {
guide.scrollLeft = newScrollLeft;
}
// Also sync timeline immediately
if (timeline) {
if (typeof timeline.scrollTo === 'function') {
timeline.scrollTo({ left: newScrollLeft, behavior: 'auto' });
} else {
timeline.scrollLeft = newScrollLeft;
}
}
// Update the ref to keep state in sync
guideScrollLeftRef.current = newScrollLeft;
setGuideScrollLeft(newScrollLeft);
}
};
tvGuide.addEventListener('wheel', handleContainerWheel, {
passive: false,
capture: true,
});
return () => {
tvGuide.removeEventListener('wheel', handleContainerWheel, {
capture: true,
});
};
}, []);
const syncScrollLeft = useCallback((nextLeft, behavior = 'auto') => {
const guideNode = guideRef.current;
const timelineNode = timelineRef.current;
@ -780,18 +836,18 @@ export default function TVChannelGuide({ startDate, endDate }) {
}, [now, nowPosition, start, syncScrollLeft]);
const handleTimelineScroll = useCallback(() => {
if (!timelineRef.current) {
if (!timelineRef.current || isSyncingScroll.current) {
return;
}
const nextLeft = timelineRef.current.scrollLeft;
guideScrollLeftRef.current = nextLeft;
setGuideScrollLeft(nextLeft);
if (isSyncingScroll.current) {
if (nextLeft === guideScrollLeftRef.current) {
return;
}
guideScrollLeftRef.current = nextLeft;
setGuideScrollLeft(nextLeft);
isSyncingScroll.current = true;
if (guideRef.current) {
if (typeof guideRef.current.scrollTo === 'function') {
@ -1178,6 +1234,7 @@ export default function TVChannelGuide({ startDate, endDate }) {
return (
<Box
ref={tvGuideRef}
className="tv-guide"
style={{
overflow: 'hidden',

View file

@ -70,11 +70,13 @@
/* Hide bottom horizontal scrollbar for the guide's virtualized list only */
.tv-guide .guide-list-outer {
/* Prevent horizontal page scrollbar while preserving internal scroll behavior */
overflow-x: hidden !important;
/* Allow horizontal scrolling but hide the scrollbar visually */
overflow-x: auto !important;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
/* Also hide scrollbars visually across browsers for the outer container */
.tv-guide .guide-list-outer::-webkit-scrollbar {
height: 0px;
display: none; /* Chrome, Safari, Opera */
}