UNPKG

wix-style-react

Version:
162 lines (139 loc) 4.59 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; // This is a copy of https://github.com/CassetteRocks/react-infinite-scroller with https://github.com/CassetteRocks/react-infinite-scroller/pull/38/files merged export default class InfiniteScroll extends Component { static propTypes = { hasMore: PropTypes.bool, initialLoad: PropTypes.bool, data: PropTypes.array, loadMore: PropTypes.func.isRequired, pageStart: PropTypes.number, threshold: PropTypes.number, useWindow: PropTypes.bool, isReverse: PropTypes.bool, scrollElement: PropTypes.object, children: PropTypes.node, loader: PropTypes.node, }; static defaultProps = { hasMore: false, initialLoad: true, pageStart: 0, threshold: 250, useWindow: true, isReverse: false, scrollElement: null, }; constructor(props) { super(props); this.scrollListener = this.scrollListener.bind(this); } componentDidMount() { this.pageLoaded = this.props.pageStart; this.attachScrollListener(); if (this.props.initialLoad) { this.scrollListener(); } // when component mounts try to load more only when initial data was already rendered else if (this.props.data?.length) { this.scrollComponent?.scrollHeight && this.scrollListener(); } } componentDidUpdate(prevProps) { this.attachScrollListener(); // scroll element might be not scrollable - trigger scroll listener when new data changes the scroll element height if (prevProps.data?.length !== this.props.data?.length) { // To avoid infinite loop of `loadMore` in `jsdom` we require the scroll element to have height before we trigger `scrollListener` as a reaction to new `props.data`. this.scrollComponent?.scrollHeight && this.scrollListener(); } } render() { const { children, hasMore, loader, scrollElement, dataHook } = this.props; let ref; if (scrollElement) { ref = () => (this.scrollComponent = scrollElement); } else { ref = node => (this.scrollComponent = node); } return React.createElement( 'div', { ref, 'data-hook': dataHook }, children, hasMore && (loader || this._defaultLoader), ); } calculateTopPosition(el) { if (!el) { return 0; } return el.offsetTop + this.calculateTopPosition(el.offsetParent); } scrollListener() { const el = this.scrollComponent; let offset; if (this.props.scrollElement) { if (this.props.isReverse) { offset = el.scrollTop; } else { offset = el.scrollHeight - el.scrollTop - el.clientHeight; } } else if (this.props.useWindow) { const scrollTop = window.pageYOffset !== undefined ? window.pageYOffset : ( document.documentElement || document.body.parentNode || document.body ).scrollTop; if (this.props.isReverse) { offset = scrollTop; } else { offset = this.calculateTopPosition(el) + el.offsetHeight - scrollTop - window.innerHeight; } } else if (this.props.isReverse) { offset = el.parentNode.scrollTop; } else { offset = el.scrollHeight - el.parentNode.scrollTop - el.parentNode.clientHeight; } if (offset < Number(this.props.threshold)) { this.detachScrollListener(); // Call loadMore after detachScrollListener to allow for non-async loadMore functions if (typeof this.props.loadMore === 'function') { this.props.loadMore((this.pageLoaded += 1)); } } } attachScrollListener() { this.detachScrollListener(); if (!this.props.hasMore) { return; } let scrollEl = window; if (this.props.scrollElement) { scrollEl = this.scrollComponent; } else if (this.props.useWindow === false) { scrollEl = this.scrollComponent.parentNode; } scrollEl.addEventListener('scroll', this.scrollListener); scrollEl.addEventListener('resize', this.scrollListener); this.detachScrollListener = () => { scrollEl.removeEventListener('scroll', this.scrollListener); scrollEl.removeEventListener('resize', this.scrollListener); this.detachScrollListener = () => {}; }; } detachScrollListener = () => {}; componentWillUnmount() { this.detachScrollListener(); } // Set a default loader for all your `InfiniteScroll` components setDefaultLoader(loader) { this._defaultLoader = loader; } }