import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { debounce } from 'throttle-debounce';
import scrollTo from '../../util/scrollTo';
const SCROLL_TO_DEBOUNCE = 200;
const SCROLL_TO_DURATION = 500;
/**
* Helper class that watches the redux state for points to scroll to. When new points
* come in, it will scroll to the highest point and clear the points from the state.
*/
class ScrollManager extends Component {
componentDidUpdate(prevProps) {
if (this.props.scrollTo !== prevProps.scrollTo) {
this.executeScrollDebounced();
}
}
executeScroll = () => {
if (this.props.scrollTo.length) {
const topScrollTo = this.props.scrollTo.reduce(
(top, current) => (current.position < top.position ? current : top),
{ position: Number.MAX_VALUE },
);
const documentScrollTop =
(document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;
const windowBottom = documentScrollTop + window.innerHeight;
const { fixedHeaderElements } = this.props;
const fixedNames = Object.keys(fixedHeaderElements);
const fixedOffset = fixedNames.length
? Math.max(...fixedNames.map(name => fixedHeaderElements[name]))
: 0;
const finalScrollTo = topScrollTo.position - fixedOffset;
// only scroll to if position is not already within viewport
if (
!topScrollTo.onlyScrollWhenNotInView ||
finalScrollTo < documentScrollTop ||
finalScrollTo > windowBottom
) {
scrollTo(0, finalScrollTo, { duration: SCROLL_TO_DURATION });
}
this.props.clearScrollTo();
}
};
executeScrollDebounced = debounce(SCROLL_TO_DEBOUNCE, false, this.executeScroll);
render() {
return <noscript />;
}
}
ScrollManager.propTypes = {
scrollTo: PropTypes.arrayOf(
PropTypes.shape({
position: PropTypes.number.required,
onlyScrollWhenNotInView: PropTypes.bool,
}),
).isRequired,
clearScrollTo: PropTypes.func.isRequired,
fixedHeaderElements: PropTypes.objectOf(PropTypes.number),
};
export default ScrollManager;