Skip to content

feat: mouse down to scroll #293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
Comment on lines +4 to +6
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

处理 smoothScrollOffset 函数中的负偏移值

offset 为负数时,smoothScrollOffset 函数将返回 NaN,因为在 JavaScript 中,负数的 0.5 次幂结果为 NaN。这可能导致当 offset 为负数时出现问题。建议在计算前对 offset 取绝对值,并根据原始符号返回结果。

建议修改如下:

 function smoothScrollOffset(offset: number) {
-  return Math.floor(offset ** 0.5);
+  const result = Math.floor(Math.sqrt(Math.abs(offset)));
+  return offset >= 0 ? result : -result;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function smoothScrollOffset(offset: number) {
return Math.floor(offset ** 0.5);
}
function smoothScrollOffset(offset: number) {
const result = Math.floor(Math.sqrt(Math.abs(offset)));
return offset >= 0 ? result : -result;
}


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();

Check warning on line 69 in src/hooks/useScrollDrag.ts

View check run for this annotation

Codecov / codecov/patch

src/hooks/useScrollDrag.ts#L68-L69

Added lines #L68 - L69 were not covered by tests
}
}
};

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);
});
});
Loading