diff --git a/packages/react-native-web/src/exports/FlatList/index.js b/packages/react-native-web/src/exports/FlatList/index.js index b7732a4e7..c8792e073 100644 --- a/packages/react-native-web/src/exports/FlatList/index.js +++ b/packages/react-native-web/src/exports/FlatList/index.js @@ -8,5 +8,19 @@ * @flow */ -import FlatList from '../../vendor/react-native/FlatList'; +import * as React from 'react'; +import useDraggableScroll from '../../modules/useDraggableScroll'; +import OriginFlatList from '../../vendor/react-native/FlatList'; + +const FlatList: React.AbstractComponent< + React.ElementConfig, + React.ElementRef +> = React.forwardRef((props, forwardedRef) => { + const { setRef } = useDraggableScroll(props, forwardedRef); + + return ; +}); + +FlatList.displayName = 'FlatList'; + export default FlatList; diff --git a/packages/react-native-web/src/exports/ScrollView/index.js b/packages/react-native-web/src/exports/ScrollView/index.js index f2ecd1bb6..63c7dd5b7 100644 --- a/packages/react-native-web/src/exports/ScrollView/index.js +++ b/packages/react-native-web/src/exports/ScrollView/index.js @@ -15,6 +15,7 @@ import dismissKeyboard from '../../modules/dismissKeyboard'; import invariant from 'fbjs/lib/invariant'; import mergeRefs from '../../modules/mergeRefs'; import ScrollResponder from '../../modules/ScrollResponder'; +import useDraggableScroll from '../../modules/useDraggableScroll'; import ScrollViewBase from './ScrollViewBase'; import StyleSheet from '../StyleSheet'; import View from '../View'; @@ -356,7 +357,9 @@ const ForwardedScrollView: React.AbstractComponent< React.ElementConfig, React.ElementRef > = React.forwardRef((props, forwardedRef) => { - return ; + const { setRef } = useDraggableScroll(props, forwardedRef); + + return ; }); ForwardedScrollView.displayName = 'ScrollView'; diff --git a/packages/react-native-web/src/exports/VirtualizedList/index.js b/packages/react-native-web/src/exports/VirtualizedList/index.js index e4deb974c..44c97fc32 100644 --- a/packages/react-native-web/src/exports/VirtualizedList/index.js +++ b/packages/react-native-web/src/exports/VirtualizedList/index.js @@ -7,5 +7,19 @@ * @flow */ -import VirtualizedList from '../../vendor/react-native/VirtualizedList'; +import * as React from 'react'; +import useDraggableScroll from '../../modules/useDraggableScroll'; +import OriginVirtualizedList from '../../vendor/react-native/VirtualizedList'; + +const VirtualizedList: React.AbstractComponent< + React.ElementConfig, + React.ElementRef +> = React.forwardRef((props, forwardedRef) => { + const { setRef } = useDraggableScroll(props, forwardedRef); + + return ; +}); + +VirtualizedList.displayName = 'VirtualizedList'; + export default VirtualizedList; diff --git a/packages/react-native-web/src/modules/useDraggableScroll/index.js b/packages/react-native-web/src/modules/useDraggableScroll/index.js new file mode 100644 index 000000000..96737fb68 --- /dev/null +++ b/packages/react-native-web/src/modules/useDraggableScroll/index.js @@ -0,0 +1,87 @@ +import { useEffect, useRef } from 'react'; + +import findNodeHandle from '../../exports/findNodeHandle'; +import useMergeRefs from '../useMergeRefs'; + +export default function useDraggableScroll(props = {}, forwardedRef = null) { + const disabled = !props.horizontal || props.WORKAROUND_disableDrag; + + const hostRef = useRef(null); + + useEffect(() => { + if (disabled) { + return; + } + + const slider = findNodeHandle(hostRef.current); + + if (!slider) { + return; + } + + const state = { + isDragging: false, + shouldPreventClick: false, + startX: 0, + scrollLeft: 0 + }; + + const handleMouseDown = (e) => { + state.isDragging = true; + state.shouldPreventClick = false; + state.startX = e.pageX - slider.offsetLeft; + state.scrollLeft = slider.scrollLeft; + }; + + const handleMouseMove = (e) => { + if (!state.isDragging) { + return; + } + + e.preventDefault(); + + const x = e.pageX - slider.offsetLeft; + const dX = x - state.startX; + + state.shouldPreventClick = Math.abs(dX) > 1; + slider.scrollLeft = state.scrollLeft - dX; + }; + + function preventClick(e) { + e.preventDefault(); + e.stopPropagation(); + slider.removeEventListener('click', preventClick, true); + } + + const handleMouseUp = () => { + if (state.shouldPreventClick) { + slider.addEventListener('click', preventClick, true); + } + + state.isDragging = false; + state.shouldPreventClick = false; + }; + + const handleMouseLeave = () => { + state.isDragging = false; + state.shouldPreventClick = false; + }; + + slider.addEventListener('mousedown', handleMouseDown); + slider.addEventListener('mouseup', handleMouseUp); + slider.addEventListener('mouseleave', handleMouseLeave); + slider.addEventListener('mousemove', handleMouseMove); + + return () => { + slider.removeEventListener('mousedown', handleMouseDown); + slider.removeEventListener('mouseup', handleMouseUp); + slider.removeEventListener('mouseleave', handleMouseLeave); + slider.removeEventListener('mousemove', handleMouseMove); + slider.removeEventListener('click', preventClick, true); + }; + }, [disabled]); + + const setRef = useMergeRefs(hostRef, forwardedRef); + + return { setRef }; +}