Skip to content

Commit 4f1221d

Browse files
authored
feat: mouse down to scroll (#293)
* feat: support mouse down scroll * test: add test case
1 parent 776411b commit 4f1221d

File tree

4 files changed

+141
-8
lines changed

4 files changed

+141
-8
lines changed

src/List.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { useGetSize } from './hooks/useGetSize';
1515
import useHeights from './hooks/useHeights';
1616
import useMobileTouchMove from './hooks/useMobileTouchMove';
1717
import useOriginScroll from './hooks/useOriginScroll';
18+
import useScrollDrag from './hooks/useScrollDrag';
1819
import type { ScrollPos, ScrollTarget } from './hooks/useScrollTo';
1920
import useScrollTo from './hooks/useScrollTo';
2021
import type { ExtraRenderInfo, GetKey, RenderFunc, SharedConfig } from './interface';
@@ -436,6 +437,11 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
436437
return false;
437438
});
438439

440+
// MouseDown drag for scroll
441+
useScrollDrag(inVirtual, componentRef, (offset) => {
442+
syncScrollTop((top) => top + offset);
443+
});
444+
439445
useLayoutEffect(() => {
440446
// Firefox only
441447
function onMozMousePixelScroll(e: WheelEvent) {

src/ScrollBar.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import classNames from 'classnames';
22
import raf from 'rc-util/lib/raf';
33
import * as React from 'react';
4+
import { getPageXY } from './hooks/useScrollDrag';
45

56
export type ScrollBarDirectionType = 'ltr' | 'rtl';
67

@@ -23,14 +24,6 @@ export interface ScrollBarRef {
2324
delayHidden: () => void;
2425
}
2526

26-
function getPageXY(
27-
e: React.MouseEvent | React.TouchEvent | MouseEvent | TouchEvent,
28-
horizontal: boolean,
29-
) {
30-
const obj = 'touches' in e ? e.touches[0] : e;
31-
return obj[horizontal ? 'pageX' : 'pageY'];
32-
}
33-
3427
const ScrollBar = React.forwardRef<ScrollBarRef, ScrollBarProps>((props, ref) => {
3528
const {
3629
prefixCls,

src/hooks/useScrollDrag.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import raf from 'rc-util/lib/raf';
2+
import * as React from 'react';
3+
4+
function smoothScrollOffset(offset: number) {
5+
return Math.floor(offset ** 0.5);
6+
}
7+
8+
export function getPageXY(
9+
e: React.MouseEvent | React.TouchEvent | MouseEvent | TouchEvent,
10+
horizontal: boolean,
11+
) {
12+
const obj = 'touches' in e ? e.touches[0] : e;
13+
return obj[horizontal ? 'pageX' : 'pageY'];
14+
}
15+
16+
export default function useScrollDrag(
17+
inVirtual: boolean,
18+
componentRef: React.RefObject<HTMLElement>,
19+
onScrollOffset: (offset: number) => void,
20+
) {
21+
React.useEffect(() => {
22+
const ele = componentRef.current;
23+
if (inVirtual && ele) {
24+
let mouseDownLock = false;
25+
let rafId: number;
26+
let offset: number;
27+
28+
const stopScroll = () => {
29+
raf.cancel(rafId);
30+
};
31+
32+
const continueScroll = () => {
33+
stopScroll();
34+
35+
rafId = raf(() => {
36+
onScrollOffset(offset);
37+
continueScroll();
38+
});
39+
};
40+
41+
const onMouseDown = (e: MouseEvent) => {
42+
// Skip if nest List has handled this event
43+
const event = e as MouseEvent & {
44+
_virtualHandled?: boolean;
45+
};
46+
if (!event._virtualHandled) {
47+
event._virtualHandled = true;
48+
mouseDownLock = true;
49+
}
50+
};
51+
const onMouseUp = () => {
52+
mouseDownLock = false;
53+
stopScroll();
54+
};
55+
const onMouseMove = (e: MouseEvent) => {
56+
if (mouseDownLock) {
57+
const mouseY = getPageXY(e, false);
58+
const { top, bottom } = ele.getBoundingClientRect();
59+
60+
if (mouseY <= top) {
61+
const diff = top - mouseY;
62+
offset = -smoothScrollOffset(diff);
63+
continueScroll();
64+
} else if (mouseY >= bottom) {
65+
const diff = mouseY - bottom;
66+
offset = smoothScrollOffset(diff);
67+
continueScroll();
68+
} else {
69+
stopScroll();
70+
}
71+
}
72+
};
73+
74+
ele.addEventListener('mousedown', onMouseDown);
75+
ele.ownerDocument.addEventListener('mouseup', onMouseUp);
76+
ele.ownerDocument.addEventListener('mousemove', onMouseMove);
77+
78+
return () => {
79+
ele.removeEventListener('mousedown', onMouseDown);
80+
ele.ownerDocument.removeEventListener('mouseup', onMouseUp);
81+
ele.ownerDocument.removeEventListener('mousemove', onMouseMove);
82+
stopScroll();
83+
};
84+
}
85+
}, [inVirtual]);
86+
}

tests/scroll.test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ jest.mock('../src/ScrollBar', () => {
2929
describe('List.Scroll', () => {
3030
let mockElement;
3131
let boundingRect = {
32+
top: 0,
33+
bottom: 0,
3234
width: 100,
3335
height: 100,
3436
};
@@ -54,6 +56,8 @@ describe('List.Scroll', () => {
5456

5557
beforeEach(() => {
5658
boundingRect = {
59+
top: 0,
60+
bottom: 0,
5761
width: 100,
5862
height: 100,
5963
};
@@ -552,4 +556,48 @@ describe('List.Scroll', () => {
552556
'0',
553557
);
554558
});
559+
560+
it('mouse down drag', () => {
561+
const onScroll = jest.fn();
562+
const { container } = render(
563+
<List
564+
component="ul"
565+
itemKey="id"
566+
itemHeight={20}
567+
height={100}
568+
data={genData(100)}
569+
onScroll={onScroll}
570+
>
571+
{({ id }) => <li>{id}</li>}
572+
</List>,
573+
);
574+
575+
function dragDown(mouseY) {
576+
fireEvent.mouseDown(container.querySelector('li'));
577+
578+
let moveEvent = createEvent.mouseMove(container.querySelector('li'));
579+
moveEvent.pageY = mouseY;
580+
fireEvent(container.querySelector('li'), moveEvent);
581+
582+
act(() => {
583+
jest.advanceTimersByTime(100);
584+
});
585+
586+
fireEvent.mouseUp(container.querySelector('li'));
587+
}
588+
589+
function getScrollTop() {
590+
const innerEle = container.querySelector('.rc-virtual-list-holder-inner');
591+
const { transform } = innerEle.style;
592+
return Number(transform.match(/\d+/)[0]);
593+
}
594+
595+
// Drag down
596+
dragDown(100);
597+
expect(getScrollTop()).toBeGreaterThan(0);
598+
599+
// Drag up
600+
dragDown(-100);
601+
expect(getScrollTop()).toBe(0);
602+
});
555603
});

0 commit comments

Comments
 (0)