react-select-module
Version:
A Select control built with and for ReactJS
127 lines (112 loc) • 3.46 kB
JavaScript
import React, { Component } from 'react';
import NodeResolver from './NodeResolver';
class ScrollCaptor extends Component {
isBottom = false;
isTop = false;
scrollTarget;
touchStart;
componentDidMount() {
this.startListening(this.scrollTarget);
}
componentWillUnmount() {
this.stopListening(this.scrollTarget);
}
startListening(el) {
// bail early if no element is available to attach to
if (!el) return;
// all the if statements are to appease Flow 😢
if (typeof el.addEventListener === 'function') {
el.addEventListener('wheel', this.onWheel, false);
}
if (typeof el.addEventListener === 'function') {
el.addEventListener('touchstart', this.onTouchStart, false);
}
if (typeof el.addEventListener === 'function') {
el.addEventListener('touchmove', this.onTouchMove, false);
}
}
stopListening(el) {
if (!el) return;
// all the if statements are to appease Flow 😢
if (typeof el.removeEventListener === 'function') {
el.removeEventListener('wheel', this.onWheel, false);
}
if (typeof el.removeEventListener === 'function') {
el.removeEventListener('touchstart', this.onTouchStart, false);
}
if (typeof el.removeEventListener === 'function') {
el.removeEventListener('touchmove', this.onTouchMove, false);
}
}
cancelScroll = event => {
event.preventDefault();
event.stopPropagation();
};
handleEventDelta = (event, delta) => {
const {
onBottomArrive,
onBottomLeave,
onTopArrive,
onTopLeave,
} = this.props;
const { scrollTop, scrollHeight, clientHeight } = this.scrollTarget;
const target = this.scrollTarget;
const isDeltaPositive = delta > 0;
const availableScroll = scrollHeight - clientHeight - scrollTop;
let shouldCancelScroll = false;
// reset bottom/top flags
if (availableScroll > delta && this.isBottom) {
if (onBottomLeave) onBottomLeave(event);
this.isBottom = false;
}
if (isDeltaPositive && this.isTop) {
if (onTopLeave) onTopLeave(event);
this.isTop = false;
}
// bottom limit
if (isDeltaPositive && delta > availableScroll) {
if (onBottomArrive && !this.isBottom) {
onBottomArrive(event);
}
target.scrollTop = scrollHeight;
shouldCancelScroll = true;
this.isBottom = true;
// top limit
} else if (!isDeltaPositive && -delta > scrollTop) {
if (onTopArrive && !this.isTop) {
onTopArrive(event);
}
target.scrollTop = 0;
shouldCancelScroll = true;
this.isTop = true;
}
// cancel scroll
if (shouldCancelScroll) {
this.cancelScroll(event);
}
};
onWheel = event => {
this.handleEventDelta(event, event.deltaY);
};
onTouchStart = event => {
// set touch start so we can calculate touchmove delta
this.touchStart = event.changedTouches[0].clientY;
};
onTouchMove = event => {
const deltaY = this.touchStart - event.changedTouches[0].clientY;
this.handleEventDelta(event, deltaY);
};
getScrollTarget = ref => {
this.scrollTarget = ref;
};
render() {
return (
<NodeResolver innerRef={this.getScrollTarget}>
{this.props.children}
</NodeResolver>
);
}
}
export default function ScrollCaptorSwitch({ isEnabled = true, ...props }) {
return isEnabled ? <ScrollCaptor {...props} /> : props.children;
}