From 8629b4e771b7441ad7a7a399b64862fdb488c606 Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Tue, 25 Aug 2026 23:58:10 +0800 Subject: [PATCH] fix(usage): prevent table clipping after column changes - recalculate the top scrollbar from the actual table overflow - clamp and synchronize scroll positions when visible columns change - preserve horizontal scrolling while hiding the bottom scrollbar - remove forced table width expansion for smaller column sets - improve toolbar and display settings responsiveness --- src/pages/UsageRecordsPage.tsx | 58 ++++++-- src/styles.css | 233 +++++++++++++++++++++------------ 2 files changed, 193 insertions(+), 98 deletions(-) diff --git a/src/pages/UsageRecordsPage.tsx b/src/pages/UsageRecordsPage.tsx index d9f8b40..8e649c0 100644 --- a/src/pages/UsageRecordsPage.tsx +++ b/src/pages/UsageRecordsPage.tsx @@ -1092,12 +1092,15 @@ const getInitialColumnWidths = (): Record => { function TableTopScrollbar({ tableWrapRef, totalWidth, + visibleColumnKeys, }: { tableWrapRef: React.RefObject; totalWidth: number; + visibleColumnKeys: EventColumnKey[]; }) { const scrollbarRef = useRef(null); const [hasOverflow, setHasOverflow] = useState(false); + const [scrollWidth, setScrollWidth] = useState(totalWidth); useEffect(() => { const scrollbar = scrollbarRef.current; @@ -1105,6 +1108,7 @@ function TableTopScrollbar({ if (!scrollbar || !tableWrap) return; let syncing = false; + const syncTable = () => { if (syncing) return; syncing = true; @@ -1113,6 +1117,7 @@ function TableTopScrollbar({ syncing = false; }); }; + const syncScrollbar = () => { if (syncing) return; syncing = true; @@ -1121,22 +1126,50 @@ function TableTopScrollbar({ syncing = false; }); }; - const updateOverflow = () => { - setHasOverflow(tableWrap.scrollWidth > tableWrap.clientWidth + 1); - scrollbar.scrollLeft = tableWrap.scrollLeft; + + const updateLayout = () => { + const clientWidth = tableWrap.clientWidth; + const wrapScrollWidth = tableWrap.scrollWidth; + const maxScroll = Math.max(0, wrapScrollWidth - clientWidth); + const isOverflowing = maxScroll > 1; + + setHasOverflow(isOverflowing); + + if (isOverflowing) { + const scrollbarClientWidth = scrollbar.clientWidth || clientWidth; + const targetInnerWidth = scrollbarClientWidth + maxScroll; + setScrollWidth(targetInnerWidth); + + if (tableWrap.scrollLeft > maxScroll) { + tableWrap.scrollLeft = maxScroll; + } + scrollbar.scrollLeft = tableWrap.scrollLeft; + } else { + tableWrap.scrollLeft = 0; + scrollbar.scrollLeft = 0; + setScrollWidth(clientWidth); + } }; - updateOverflow(); + updateLayout(); + const frameId = window.requestAnimationFrame(updateLayout); + scrollbar.addEventListener('scroll', syncTable, { passive: true }); tableWrap.addEventListener('scroll', syncScrollbar, { passive: true }); - const resizeObserver = new ResizeObserver(updateOverflow); + + const resizeObserver = new ResizeObserver(() => { + updateLayout(); + }); resizeObserver.observe(tableWrap); + resizeObserver.observe(scrollbar); + return () => { + window.cancelAnimationFrame(frameId); scrollbar.removeEventListener('scroll', syncTable); tableWrap.removeEventListener('scroll', syncScrollbar); resizeObserver.disconnect(); }; - }, [tableWrapRef, totalWidth]); + }, [tableWrapRef, totalWidth, visibleColumnKeys]); return (