mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-13 19:35:41 +00:00
[fix] ScrollView doesn't respond to descendant scroll events
Workaround for React DOM's non-standard bubbling of scroll events. Fix #1800
This commit is contained in:
@@ -13,6 +13,7 @@ import * as React from 'react';
|
||||
import { forwardRef, useRef } from 'react';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import View from '../View';
|
||||
import useMergeRefs from '../../modules/useMergeRefs';
|
||||
|
||||
type Props = {
|
||||
...ViewProps,
|
||||
@@ -93,6 +94,7 @@ const ScrollViewBase = forwardRef<Props, *>((props, forwardedRef) => {
|
||||
|
||||
const scrollState = useRef({ isScrolling: false, scrollLastTick: 0 });
|
||||
const scrollTimeout = useRef(null);
|
||||
const scrollRef = useRef(null);
|
||||
|
||||
function createPreventableScrollHandler(handler: Function) {
|
||||
return (e: Object) => {
|
||||
@@ -105,29 +107,31 @@ const ScrollViewBase = forwardRef<Props, *>((props, forwardedRef) => {
|
||||
}
|
||||
|
||||
function handleScroll(e: Object) {
|
||||
e.persist();
|
||||
e.stopPropagation();
|
||||
// A scroll happened, so the scroll resets the scrollend timeout.
|
||||
if (scrollTimeout.current != null) {
|
||||
clearTimeout(scrollTimeout.current);
|
||||
}
|
||||
scrollTimeout.current = setTimeout(() => {
|
||||
handleScrollEnd(e);
|
||||
}, 100);
|
||||
if (scrollState.current.isScrolling) {
|
||||
// Scroll last tick may have changed, check if we need to notify
|
||||
if (shouldEmitScrollEvent(scrollState.current.scrollLastTick, scrollEventThrottle)) {
|
||||
handleScrollTick(e);
|
||||
if (e.target === scrollRef.current) {
|
||||
e.persist();
|
||||
// A scroll happened, so the scroll resets the scrollend timeout.
|
||||
if (scrollTimeout.current != null) {
|
||||
clearTimeout(scrollTimeout.current);
|
||||
}
|
||||
scrollTimeout.current = setTimeout(() => {
|
||||
handleScrollEnd(e);
|
||||
}, 100);
|
||||
if (scrollState.current.isScrolling) {
|
||||
// Scroll last tick may have changed, check if we need to notify
|
||||
if (shouldEmitScrollEvent(scrollState.current.scrollLastTick, scrollEventThrottle)) {
|
||||
handleScrollTick(e);
|
||||
}
|
||||
} else {
|
||||
// Weren't scrolling, so we must have just started
|
||||
handleScrollStart(e);
|
||||
}
|
||||
} else {
|
||||
// Weren't scrolling, so we must have just started
|
||||
handleScrollStart(e);
|
||||
}
|
||||
}
|
||||
|
||||
function handleScrollStart(e: Object) {
|
||||
scrollState.current.isScrolling = true;
|
||||
scrollState.current.scrollLastTick = Date.now();
|
||||
handleScrollTick(e);
|
||||
}
|
||||
|
||||
function handleScrollTick(e: Object) {
|
||||
@@ -161,7 +165,7 @@ const ScrollViewBase = forwardRef<Props, *>((props, forwardedRef) => {
|
||||
onTouchMove={createPreventableScrollHandler(onTouchMove)}
|
||||
onWheel={createPreventableScrollHandler(onWheel)}
|
||||
pointerEvents={pointerEvents}
|
||||
ref={forwardedRef}
|
||||
ref={useMergeRefs(scrollRef, forwardedRef)}
|
||||
style={[
|
||||
style,
|
||||
!scrollEnabled && styles.scrollDisabled,
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
import React from 'react';
|
||||
import ScrollView from '../';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { createEventTarget } from 'dom-event-testing-library';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
describe('components/ScrollView', () => {
|
||||
test.skip('todo', () => {});
|
||||
describe('prop "onScroll"', () => {
|
||||
test('is called when element scrolls', () => {
|
||||
const onScroll = jest.fn();
|
||||
const ref = React.createRef();
|
||||
act(() => {
|
||||
render(<ScrollView onScroll={onScroll} ref={ref} scrollEventThrottle={16} />);
|
||||
});
|
||||
const target = createEventTarget(findDOMNode(ref.current));
|
||||
act(() => {
|
||||
target.scroll();
|
||||
target.scroll();
|
||||
});
|
||||
expect(onScroll).toBeCalled();
|
||||
});
|
||||
|
||||
test('is not called when descendant scrolls', () => {
|
||||
const onScroll = jest.fn();
|
||||
const ref = React.createRef();
|
||||
act(() => {
|
||||
render(
|
||||
<ScrollView onScroll={onScroll} scrollEventThrottle={16}>
|
||||
<div ref={ref} />
|
||||
</ScrollView>
|
||||
);
|
||||
});
|
||||
const target = createEventTarget(ref.current);
|
||||
act(() => {
|
||||
target.scroll();
|
||||
});
|
||||
expect(onScroll).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user