Skip to content

Commit e76ab00

Browse files
committed
feat: draggable scroll list for desktop
- horizontal only for now
1 parent dee258a commit e76ab00

File tree

5 files changed

+114
-5
lines changed

5 files changed

+114
-5
lines changed

packages/react-native-web/src/exports/FlatList/index.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,19 @@
88
* @flow
99
*/
1010

11-
import FlatList from '../../vendor/react-native/FlatList';
11+
import * as React from 'react';
12+
import useDraggableScroll from '../../modules/useDraggableScroll';
13+
import OriginFlatList from '../../vendor/react-native/FlatList';
14+
15+
const FlatList: React.AbstractComponent<
16+
React.ElementConfig<typeof OriginFlatList>,
17+
React.ElementRef<typeof OriginFlatList>
18+
> = React.forwardRef((props, forwardedRef) => {
19+
const { setRef } = useDraggableScroll(props, forwardedRef);
20+
21+
return <OriginFlatList ref={setRef} {...props} />;
22+
});
23+
24+
FlatList.displayName = 'FlatList';
25+
1226
export default FlatList;

packages/react-native-web/src/exports/ScrollView/__tests__/index-test.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ describe('components/ScrollView', () => {
8888
});
8989
const node = ref.current;
9090

91-
// Did we get an HTMLElement?
92-
expect(node.tagName === 'DIV').toBe(true);
9391
// Does it have the "platform" methods?
9492
expect(typeof node.measure === 'function').toBe(true);
9593
expect(typeof node.measureLayout === 'function').toBe(true);

packages/react-native-web/src/exports/ScrollView/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import dismissKeyboard from '../../modules/dismissKeyboard';
1515
import invariant from 'fbjs/lib/invariant';
1616
import mergeRefs from '../../modules/mergeRefs';
1717
import ScrollResponder from '../../modules/ScrollResponder';
18+
import useDraggableScroll from '../../modules/useDraggableScroll';
1819
import ScrollViewBase from './ScrollViewBase';
1920
import StyleSheet from '../StyleSheet';
2021
import View from '../View';
@@ -356,7 +357,9 @@ const ForwardedScrollView: React.AbstractComponent<
356357
React.ElementConfig<typeof ScrollView>,
357358
React.ElementRef<typeof ScrollView>
358359
> = React.forwardRef((props, forwardedRef) => {
359-
return <ScrollView {...props} forwardedRef={forwardedRef} />;
360+
const { setRef } = useDraggableScroll(props, forwardedRef);
361+
362+
return <ScrollView ref={setRef} {...props} />;
360363
});
361364

362365
ForwardedScrollView.displayName = 'ScrollView';

packages/react-native-web/src/exports/VirtualizedList/index.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,19 @@
77
* @flow
88
*/
99

10-
import VirtualizedList from '../../vendor/react-native/VirtualizedList';
10+
import * as React from 'react';
11+
import useDraggableScroll from '../../modules/useDraggableScroll';
12+
import OriginVirtualizedList from '../../vendor/react-native/VirtualizedList';
13+
14+
const VirtualizedList: React.AbstractComponent<
15+
React.ElementConfig<typeof OriginVirtualizedList>,
16+
React.ElementRef<typeof OriginVirtualizedList>
17+
> = React.forwardRef((props, forwardedRef) => {
18+
const { setRef } = useDraggableScroll(props, forwardedRef);
19+
20+
return <OriginVirtualizedList ref={setRef} {...props} />;
21+
});
22+
23+
VirtualizedList.displayName = 'VirtualizedList';
24+
1125
export default VirtualizedList;
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
import findNodeHandle from '../../exports/findNodeHandle';
4+
import useMergeRefs from '../useMergeRefs';
5+
6+
export default function useDraggableScroll(props = {}, forwardedRef = null) {
7+
const disabled = !props.horizontal || props.WORKAROUND_disableDrag;
8+
9+
const hostRef = useRef(null);
10+
11+
useEffect(() => {
12+
if (disabled) {
13+
return;
14+
}
15+
16+
const slider = findNodeHandle(hostRef.current);
17+
18+
if (!slider) {
19+
return;
20+
}
21+
22+
const state = {
23+
isDragging: false,
24+
shouldPreventClick: false,
25+
startX: 0,
26+
scrollLeft: 0
27+
};
28+
29+
const handleMouseDown = (e) => {
30+
state.isDragging = true;
31+
state.shouldPreventClick = false;
32+
state.startX = e.pageX - slider.offsetLeft;
33+
state.scrollLeft = slider.scrollLeft;
34+
};
35+
36+
const handleMouseMove = (e) => {
37+
if (!state.isDragging) {
38+
return;
39+
}
40+
41+
e.preventDefault();
42+
43+
const x = e.pageX - slider.offsetLeft;
44+
const dX = x - state.startX;
45+
46+
state.shouldPreventClick = Math.abs(dX) > 1;
47+
slider.scrollLeft = state.scrollLeft - dX;
48+
};
49+
50+
function preventClick(e) {
51+
e.preventDefault();
52+
e.stopPropagation();
53+
slider.removeEventListener('click', preventClick, true);
54+
}
55+
56+
const handleMouseUp = () => {
57+
if (state.shouldPreventClick) {
58+
slider.addEventListener('click', preventClick, true);
59+
}
60+
61+
state.isDragging = false;
62+
state.shouldPreventClick = false;
63+
};
64+
65+
slider.addEventListener('mousedown', handleMouseDown);
66+
slider.addEventListener('mouseup', handleMouseUp);
67+
slider.addEventListener('mousemove', handleMouseMove);
68+
69+
return () => {
70+
slider.removeEventListener('mousedown', handleMouseDown);
71+
slider.removeEventListener('mouseup', handleMouseUp);
72+
slider.removeEventListener('mousemove', handleMouseMove);
73+
slider.removeEventListener('click', preventClick, true);
74+
};
75+
}, [disabled]);
76+
77+
const setRef = useMergeRefs(hostRef, forwardedRef);
78+
79+
return { setRef };
80+
}

0 commit comments

Comments
 (0)