react-select-module
Version:
A Select control built with and for ReactJS
136 lines (118 loc) • 3.47 kB
JavaScript
import { Component } from 'react';
import { LOCK_STYLES, STYLE_KEYS } from './constants';
import {
allowTouchMove,
isTouchDevice,
preventInertiaScroll,
preventTouchMove,
} from './utils';
const canUseDOM = !!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
);
let activeScrollLocks = 0;
export default class ScrollLock extends Component {
originalStyles = {};
listenerOptions = {
capture: false,
passive: false,
};
static defaultProps = {
accountForScrollbars: true,
};
componentDidMount() {
if (!canUseDOM) return;
const { accountForScrollbars, touchScrollTarget } = this.props;
const target = document.body;
const targetStyle = target && target.style;
if (accountForScrollbars) {
// store any styles already applied to the body
STYLE_KEYS.forEach(key => {
const val = targetStyle && targetStyle[key];
this.originalStyles[key] = val;
});
}
// apply the lock styles and padding if this is the first scroll lock
if (accountForScrollbars && activeScrollLocks < 1) {
const currentPadding =
parseInt(this.originalStyles.paddingRight, 10) || 0;
const clientWidth = document.body ? document.body.clientWidth : 0;
const adjustedPadding =
window.innerWidth - clientWidth + currentPadding || 0;
Object.keys(LOCK_STYLES).forEach(key => {
const val = LOCK_STYLES[key];
if (targetStyle) {
targetStyle[key] = val;
}
});
if (targetStyle) {
targetStyle.paddingRight = `${adjustedPadding}px`;
}
}
// account for touch devices
if (target && isTouchDevice()) {
// Mobile Safari ignores { overflow: hidden } declaration on the body.
target.addEventListener(
'touchmove',
preventTouchMove,
this.listenerOptions
);
// Allow scroll on provided target
if (touchScrollTarget) {
touchScrollTarget.addEventListener(
'touchstart',
preventInertiaScroll,
this.listenerOptions
);
touchScrollTarget.addEventListener(
'touchmove',
allowTouchMove,
this.listenerOptions
);
}
}
// increment active scroll locks
activeScrollLocks += 1;
}
componentWillUnmount() {
if (!canUseDOM) return;
const { accountForScrollbars, touchScrollTarget } = this.props;
const target = document.body;
const targetStyle = target && target.style;
// safely decrement active scroll locks
activeScrollLocks = Math.max(activeScrollLocks - 1, 0);
// reapply original body styles, if any
if (accountForScrollbars && activeScrollLocks < 1) {
STYLE_KEYS.forEach(key => {
const val = this.originalStyles[key];
if (targetStyle) {
targetStyle[key] = val;
}
});
}
// remove touch listeners
if (target && isTouchDevice()) {
target.removeEventListener(
'touchmove',
preventTouchMove,
this.listenerOptions
);
if (touchScrollTarget) {
touchScrollTarget.removeEventListener(
'touchstart',
preventInertiaScroll,
this.listenerOptions
);
touchScrollTarget.removeEventListener(
'touchmove',
allowTouchMove,
this.listenerOptions
);
}
}
}
render() {
return null;
}
}