Skip to content

Commit

Permalink
feat: mouse down to scroll (#293)
Browse files Browse the repository at this point in the history
* feat: support mouse down scroll

* test: add test case
  • Loading branch information
zombieJ authored Dec 4, 2024
1 parent 776411b commit 4f1221d
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 8 deletions.
6 changes: 6 additions & 0 deletions src/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useGetSize } from './hooks/useGetSize';
import useHeights from './hooks/useHeights';
import useMobileTouchMove from './hooks/useMobileTouchMove';
import useOriginScroll from './hooks/useOriginScroll';
import useScrollDrag from './hooks/useScrollDrag';
import type { ScrollPos, ScrollTarget } from './hooks/useScrollTo';
import useScrollTo from './hooks/useScrollTo';
import type { ExtraRenderInfo, GetKey, RenderFunc, SharedConfig } from './interface';
Expand Down Expand Up @@ -436,6 +437,11 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
return false;
});

// MouseDown drag for scroll
useScrollDrag(inVirtual, componentRef, (offset) => {
syncScrollTop((top) => top + offset);
});

useLayoutEffect(() => {
// Firefox only
function onMozMousePixelScroll(e: WheelEvent) {
Expand Down
9 changes: 1 addition & 8 deletions src/ScrollBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import classNames from 'classnames';
import raf from 'rc-util/lib/raf';
import * as React from 'react';
import { getPageXY } from './hooks/useScrollDrag';

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

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

function getPageXY(
e: React.MouseEvent | React.TouchEvent | MouseEvent | TouchEvent,
horizontal: boolean,
) {
const obj = 'touches' in e ? e.touches[0] : e;
return obj[horizontal ? 'pageX' : 'pageY'];
}

const ScrollBar = React.forwardRef<ScrollBarRef, ScrollBarProps>((props, ref) => {
const {
prefixCls,
Expand Down
86 changes: 86 additions & 0 deletions src/hooks/useScrollDrag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import raf from 'rc-util/lib/raf';
import * as React from 'react';

function smoothScrollOffset(offset: number) {
return Math.floor(offset ** 0.5);
}

export function getPageXY(
e: React.MouseEvent | React.TouchEvent | MouseEvent | TouchEvent,
horizontal: boolean,
) {
const obj = 'touches' in e ? e.touches[0] : e;
return obj[horizontal ? 'pageX' : 'pageY'];
}

export default function useScrollDrag(
inVirtual: boolean,
componentRef: React.RefObject<HTMLElement>,
onScrollOffset: (offset: number) => void,
) {
React.useEffect(() => {
const ele = componentRef.current;
if (inVirtual && ele) {
let mouseDownLock = false;
let rafId: number;
let offset: number;

const stopScroll = () => {
raf.cancel(rafId);
};

const continueScroll = () => {
stopScroll();

rafId = raf(() => {
onScrollOffset(offset);
continueScroll();
});
};

const onMouseDown = (e: MouseEvent) => {
// Skip if nest List has handled this event
const event = e as MouseEvent & {
_virtualHandled?: boolean;
};
if (!event._virtualHandled) {
event._virtualHandled = true;
mouseDownLock = true;
}
};
const onMouseUp = () => {
mouseDownLock = false;
stopScroll();
};
const onMouseMove = (e: MouseEvent) => {
if (mouseDownLock) {
const mouseY = getPageXY(e, false);
const { top, bottom } = ele.getBoundingClientRect();

if (mouseY <= top) {
const diff = top - mouseY;
offset = -smoothScrollOffset(diff);
continueScroll();
} else if (mouseY >= bottom) {
const diff = mouseY - bottom;
offset = smoothScrollOffset(diff);
continueScroll();
} else {
stopScroll();
}
}
};

ele.addEventListener('mousedown', onMouseDown);
ele.ownerDocument.addEventListener('mouseup', onMouseUp);
ele.ownerDocument.addEventListener('mousemove', onMouseMove);

return () => {
ele.removeEventListener('mousedown', onMouseDown);
ele.ownerDocument.removeEventListener('mouseup', onMouseUp);
ele.ownerDocument.removeEventListener('mousemove', onMouseMove);
stopScroll();
};
}
}, [inVirtual]);
}
48 changes: 48 additions & 0 deletions tests/scroll.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jest.mock('../src/ScrollBar', () => {
describe('List.Scroll', () => {
let mockElement;
let boundingRect = {
top: 0,
bottom: 0,
width: 100,
height: 100,
};
Expand All @@ -54,6 +56,8 @@ describe('List.Scroll', () => {

beforeEach(() => {
boundingRect = {
top: 0,
bottom: 0,
width: 100,
height: 100,
};
Expand Down Expand Up @@ -552,4 +556,48 @@ describe('List.Scroll', () => {
'0',
);
});

it('mouse down drag', () => {
const onScroll = jest.fn();
const { container } = render(
<List
component="ul"
itemKey="id"
itemHeight={20}
height={100}
data={genData(100)}
onScroll={onScroll}
>
{({ id }) => <li>{id}</li>}
</List>,
);

function dragDown(mouseY) {
fireEvent.mouseDown(container.querySelector('li'));

let moveEvent = createEvent.mouseMove(container.querySelector('li'));
moveEvent.pageY = mouseY;
fireEvent(container.querySelector('li'), moveEvent);

act(() => {
jest.advanceTimersByTime(100);
});

fireEvent.mouseUp(container.querySelector('li'));
}

function getScrollTop() {
const innerEle = container.querySelector('.rc-virtual-list-holder-inner');
const { transform } = innerEle.style;
return Number(transform.match(/\d+/)[0]);
}

// Drag down
dragDown(100);
expect(getScrollTop()).toBeGreaterThan(0);

// Drag up
dragDown(-100);
expect(getScrollTop()).toBe(0);
});
});

0 comments on commit 4f1221d

Please sign in to comment.