create-expo-cljs-app
Version:
Create a react native application with Expo and Shadow-CLJS!
178 lines (161 loc) • 4.53 kB
JavaScript
/**
* Copyright (c) Nicolas Gallagher.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type { ViewProps } from '../View';
import * as React from 'react';
import StyleSheet from '../StyleSheet';
import View from '../View';
import useMergeRefs from '../../modules/useMergeRefs';
type Props = {
...ViewProps,
onMomentumScrollBegin?: (e: any) => void,
onMomentumScrollEnd?: (e: any) => void,
onScroll?: (e: any) => void,
onScrollBeginDrag?: (e: any) => void,
onScrollEndDrag?: (e: any) => void,
onTouchMove?: (e: any) => void,
onWheel?: (e: any) => void,
scrollEnabled?: boolean,
scrollEventThrottle?: number,
showsHorizontalScrollIndicator?: boolean,
showsVerticalScrollIndicator?: boolean
};
function normalizeScrollEvent(e) {
return {
nativeEvent: {
contentOffset: {
get x() {
return e.target.scrollLeft;
},
get y() {
return e.target.scrollTop;
}
},
contentSize: {
get height() {
return e.target.scrollHeight;
},
get width() {
return e.target.scrollWidth;
}
},
layoutMeasurement: {
get height() {
return e.target.offsetHeight;
},
get width() {
return e.target.offsetWidth;
}
}
},
timeStamp: Date.now()
};
}
function shouldEmitScrollEvent(lastTick: number, eventThrottle: number) {
const timeSinceLastTick = Date.now() - lastTick;
return eventThrottle > 0 && timeSinceLastTick >= eventThrottle;
}
/**
* Encapsulates the Web-specific scroll throttling and disabling logic
*/
const ScrollViewBase: React.AbstractComponent<
Props,
React.ElementRef<typeof View>
> = React.forwardRef((props, forwardedRef) => {
const {
onScroll,
onTouchMove,
onWheel,
scrollEnabled = true,
scrollEventThrottle = 0,
showsHorizontalScrollIndicator,
showsVerticalScrollIndicator,
style,
...rest
} = props;
const scrollState = React.useRef({ isScrolling: false, scrollLastTick: 0 });
const scrollTimeout = React.useRef(null);
const scrollRef = React.useRef(null);
function createPreventableScrollHandler(handler: Function) {
return (e: Object) => {
if (scrollEnabled) {
if (handler) {
handler(e);
}
}
};
}
function handleScroll(e: Object) {
e.stopPropagation();
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);
}
}
}
function handleScrollStart(e: Object) {
scrollState.current.isScrolling = true;
handleScrollTick(e);
}
function handleScrollTick(e: Object) {
scrollState.current.scrollLastTick = Date.now();
if (onScroll) {
onScroll(normalizeScrollEvent(e));
}
}
function handleScrollEnd(e: Object) {
scrollState.current.isScrolling = false;
if (onScroll) {
onScroll(normalizeScrollEvent(e));
}
}
const hideScrollbar =
showsHorizontalScrollIndicator === false || showsVerticalScrollIndicator === false;
return (
<View
{...rest}
onScroll={handleScroll}
onTouchMove={createPreventableScrollHandler(onTouchMove)}
onWheel={createPreventableScrollHandler(onWheel)}
ref={useMergeRefs(scrollRef, forwardedRef)}
style={[
style,
!scrollEnabled && styles.scrollDisabled,
hideScrollbar && styles.hideScrollbar
]}
/>
);
});
// Chrome doesn't support e.preventDefault in this case; touch-action must be
// used to disable scrolling.
// https://developers.google.com/web/updates/2017/01/scrolling-intervention
const styles = StyleSheet.create({
scrollDisabled: {
overflowX: 'hidden',
overflowY: 'hidden',
touchAction: 'none'
},
hideScrollbar: {
scrollbarWidth: 'none'
}
});
export default ScrollViewBase;