diff --git a/src/ScrollBar.tsx b/src/ScrollBar.tsx index ce4ff6f..e633c86 100644 --- a/src/ScrollBar.tsx +++ b/src/ScrollBar.tsx @@ -25,6 +25,25 @@ export interface ScrollBarRef { delayHidden: () => void; } +function getScrollOffsetByThumbTop( + thumbTop: number, + enabledScrollRange: number, + enabledOffsetRange: number, +) { + if (enabledScrollRange <= 0 || enabledOffsetRange <= 0) { + return 0; + } + + const mergedThumbTop = Math.max(Math.min(thumbTop, enabledOffsetRange), 0); + const ptg: number = mergedThumbTop / enabledOffsetRange; + + let nextScrollOffset = Math.ceil(ptg * enabledScrollRange); + nextScrollOffset = Math.max(nextScrollOffset, 0); + nextScrollOffset = Math.min(nextScrollOffset, enabledScrollRange); + + return nextScrollOffset; +} + const ScrollBar = React.forwardRef((props, ref) => { const { prefixCls, @@ -79,9 +98,57 @@ const ScrollBar = React.forwardRef((props, ref) => }, [scrollOffset, enableScrollRange, enableOffsetRange]); // ====================== Container ======================= + const isThumbTarget = (target: EventTarget | null) => { + return !!target && thumbRef.current?.contains(target as Node); + }; + + const scrollToTrackPosition = (e: React.MouseEvent | MouseEvent) => { + const scrollbarEle = scrollbarRef.current; + + if (!scrollbarEle) { + return; + } + + const rect = scrollbarEle.getBoundingClientRect(); + const pagePosition = getPageXY(e, horizontal); + let nextTop: number; + + if (!Number.isFinite(pagePosition)) { + return; + } + + if (horizontal) { + const horizontalStart = isLTR ? rect.left : rect.right; + + if (!Number.isFinite(horizontalStart)) { + return; + } + + nextTop = + (isLTR ? pagePosition - horizontalStart : horizontalStart - pagePosition) - spinSize / 2; + } else { + if (!Number.isFinite(rect.top)) { + return; + } + + nextTop = pagePosition - rect.top - spinSize / 2; + } + + onScroll( + getScrollOffsetByThumbTop(nextTop, enableScrollRange, enableOffsetRange), + horizontal, + ); + }; + const onContainerMouseDown: React.MouseEventHandler = (e) => { e.stopPropagation(); e.preventDefault(); + + if (e.button !== 0 || isThumbTarget(e.target)) { + return; + } + + scrollToTrackPosition(e); }; // ======================== Thumb ========================= @@ -153,11 +220,11 @@ const ScrollBar = React.forwardRef((props, ref) => const tmpEnableScrollRange = enableScrollRangeRef.current; const tmpEnableOffsetRange = enableOffsetRangeRef.current; - const ptg: number = tmpEnableOffsetRange ? newTop / tmpEnableOffsetRange : 0; - - let newScrollTop = Math.ceil(ptg * tmpEnableScrollRange); - newScrollTop = Math.max(newScrollTop, 0); - newScrollTop = Math.min(newScrollTop, tmpEnableScrollRange); + const newScrollTop = getScrollOffsetByThumbTop( + newTop, + tmpEnableScrollRange, + tmpEnableOffsetRange, + ); moveRafId = raf(() => { onScroll(newScrollTop, horizontal); diff --git a/tests/scroll.test.js b/tests/scroll.test.js index a7bb170..7cd8d2f 100644 --- a/tests/scroll.test.js +++ b/tests/scroll.test.js @@ -308,6 +308,23 @@ describe('List.Scroll', () => { expect(container.querySelector('ul').scrollTop > 0).toBeTruthy(); }); + it('click track to scroll', () => { + const { container } = genList({ + itemHeight: 20, + height: 100, + data: genData(100), + }); + + act(() => { + const scrollbar = container.querySelector('.rc-virtual-list-scrollbar-vertical'); + const mouseDownEvent = createEvent.mouseDown(scrollbar); + Object.defineProperty(mouseDownEvent, 'pageY', { value: 50 }); + fireEvent(scrollbar, mouseDownEvent); + }); + + expect(container.querySelector('ul').scrollTop).toEqual(950); + }); + it('should show scrollbar when element has showScrollBar prop set to true', () => { jest.useFakeTimers(); const listRef = React.createRef(); diff --git a/tests/scrollWidth.test.tsx b/tests/scrollWidth.test.tsx index 2232632..3e94ccb 100644 --- a/tests/scrollWidth.test.tsx +++ b/tests/scrollWidth.test.tsx @@ -42,6 +42,10 @@ describe('List.scrollWidth', () => { }, getBoundingClientRect() { return { + top: 0, + bottom: holderHeight, + left: 0, + right: holderWidth, width: holderWidth, height: holderHeight, }; @@ -102,6 +106,41 @@ describe('List.scrollWidth', () => { }); describe('trigger offset', () => { + it('click horizontal track to scroll', async () => { + const listRef = React.createRef(); + + const { container } = await genList({ + itemHeight: ITEM_HEIGHT, + height: 100, + data: genData(100), + scrollWidth: 1000, + ref: listRef, + }); + + pageX = 30; + fireEvent.mouseDown(container.querySelector('.rc-virtual-list-scrollbar-horizontal')!); + + expect(listRef.current.getScrollInfo()).toEqual({ x: 225, y: 0 }); + }); + + it('click horizontal track to scroll in rtl', async () => { + const listRef = React.createRef(); + + const { container } = await genList({ + itemHeight: ITEM_HEIGHT, + height: 100, + data: genData(100), + scrollWidth: 1000, + direction: 'rtl', + ref: listRef, + }); + + pageX = 30; + fireEvent.mouseDown(container.querySelector('.rc-virtual-list-scrollbar-horizontal')!); + + expect(listRef.current.getScrollInfo()).toEqual({ x: -675, y: 0 }); + }); + it('drag scrollbar', async () => { const onVirtualScroll = jest.fn(); const listRef = React.createRef();