sams-infinite-scroll
Version:
Sams fork Infinite Scroll component in react.
459 lines (426 loc) • 19.9 kB
JavaScript
'use strict';
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(React);
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
/* eslint-disable no-undefined,no-param-reassign,no-shadow */
/**
* Throttle execution of a function. Especially useful for rate limiting
* execution of handlers on events like resize and scroll.
*
* @param {number} delay - A zero-or-greater delay in milliseconds. For event callbacks, values around 100 or 250 (or even higher) are most useful.
* @param {boolean} [noTrailing] - Optional, defaults to false. If noTrailing is true, callback will only execute every `delay` milliseconds while the
* throttled-function is being called. If noTrailing is false or unspecified, callback will be executed one final time
* after the last throttled-function call. (After the throttled-function has not been called for `delay` milliseconds,
* the internal counter is reset).
* @param {Function} callback - A function to be executed after delay milliseconds. The `this` context and all arguments are passed through, as-is,
* to `callback` when the throttled-function is executed.
* @param {boolean} [debounceMode] - If `debounceMode` is true (at begin), schedule `clear` to execute after `delay` ms. If `debounceMode` is false (at end),
* schedule `callback` to execute after `delay` ms.
*
* @returns {Function} A new, throttled, function.
*/
function throttle (delay, noTrailing, callback, debounceMode) {
/*
* After wrapper has stopped being called, this timeout ensures that
* `callback` is executed at the proper times in `throttle` and `end`
* debounce modes.
*/
var timeoutID;
var cancelled = false; // Keep track of the last time `callback` was executed.
var lastExec = 0; // Function to clear existing timeout
function clearExistingTimeout() {
if (timeoutID) {
clearTimeout(timeoutID);
}
} // Function to cancel next exec
function cancel() {
clearExistingTimeout();
cancelled = true;
} // `noTrailing` defaults to falsy.
if (typeof noTrailing !== 'boolean') {
debounceMode = callback;
callback = noTrailing;
noTrailing = undefined;
}
/*
* The `wrapper` function encapsulates all of the throttling / debouncing
* functionality and when executed will limit the rate at which `callback`
* is executed.
*/
function wrapper() {
for (var _len = arguments.length, arguments_ = new Array(_len), _key = 0; _key < _len; _key++) {
arguments_[_key] = arguments[_key];
}
var self = this;
var elapsed = Date.now() - lastExec;
if (cancelled) {
return;
} // Execute `callback` and update the `lastExec` timestamp.
function exec() {
lastExec = Date.now();
callback.apply(self, arguments_);
}
/*
* If `debounceMode` is true (at begin) this is used to clear the flag
* to allow future `callback` executions.
*/
function clear() {
timeoutID = undefined;
}
if (debounceMode && !timeoutID) {
/*
* Since `wrapper` is being called for the first time and
* `debounceMode` is true (at begin), execute `callback`.
*/
exec();
}
clearExistingTimeout();
if (debounceMode === undefined && elapsed > delay) {
/*
* In throttle mode, if `delay` time has been exceeded, execute
* `callback`.
*/
exec();
} else if (noTrailing !== true) {
/*
* In trailing throttle mode, since `delay` time has not been
* exceeded, schedule `callback` to execute `delay` ms after most
* recent execution.
*
* If `debounceMode` is true (at begin), schedule `clear` to execute
* after `delay` ms.
*
* If `debounceMode` is false (at end), schedule `callback` to
* execute after `delay` ms.
*/
timeoutID = setTimeout(debounceMode ? clear : exec, debounceMode === undefined ? delay - elapsed : delay);
}
}
wrapper.cancel = cancel; // Return the wrapper function.
return wrapper;
}
var ThresholdUnits = {
Pixel: 'Pixel',
Percent: 'Percent',
};
var defaultThreshold = {
unit: ThresholdUnits.Percent,
value: 0.8,
};
function parseThreshold(scrollThreshold) {
if (typeof scrollThreshold === 'number') {
return {
unit: ThresholdUnits.Percent,
value: scrollThreshold * 100,
};
}
if (typeof scrollThreshold === 'string') {
if (scrollThreshold.match(/^(\d*(\.\d+)?)px$/)) {
return {
unit: ThresholdUnits.Pixel,
value: parseFloat(scrollThreshold),
};
}
if (scrollThreshold.match(/^(\d*(\.\d+)?)%$/)) {
return {
unit: ThresholdUnits.Percent,
value: parseFloat(scrollThreshold),
};
}
console.warn('scrollThreshold format is invalid. Valid formats: "120px", "50%"...');
return defaultThreshold;
}
console.warn('scrollThreshold should be string or number');
return defaultThreshold;
}
var InfiniteScroll = /** @class */ (function (_super) {
__extends(InfiniteScroll, _super);
function InfiniteScroll(props) {
var _this = _super.call(this, props) || this;
_this.lastScrollTop = 0;
_this.actionTriggered = false;
// variables to keep track of pull down behaviour
_this.startY = 0;
_this.currentY = 0;
_this.dragging = false;
// will be populated in componentDidMount
// based on the height of the pull down element
_this.maxPullDownDistance = 0;
_this.getScrollableTarget = function () {
if (_this.props.scrollableTarget instanceof HTMLElement)
return _this.props.scrollableTarget;
if (typeof _this.props.scrollableTarget === 'string') {
return document.getElementById(_this.props.scrollableTarget);
}
if (_this.props.scrollableTarget === null) {
console.warn("You are trying to pass scrollableTarget but it is null. This might\n happen because the element may not have been added to DOM yet.\n See https://github.com/ankeetmaini/react-infinite-scroll-component/issues/59 for more info.\n ");
}
return null;
};
_this.onStart = function (evt) {
if (_this.lastScrollTop)
return;
_this.dragging = true;
if (evt instanceof MouseEvent) {
_this.startY = evt.pageY;
}
else if (evt instanceof TouchEvent) {
_this.startY = evt.touches[0].pageY;
}
_this.currentY = _this.startY;
if (_this._infScroll) {
_this._infScroll.style.willChange = 'transform';
_this._infScroll.style.transition = "transform 0.2s cubic-bezier(0,0,0.31,1)";
}
};
_this.onMove = function (evt) {
if (!_this.dragging)
return;
if (evt instanceof MouseEvent) {
_this.currentY = evt.pageY;
}
else if (evt instanceof TouchEvent) {
_this.currentY = evt.touches[0].pageY;
}
// user is scrolling down to up
if (_this.currentY < _this.startY)
return;
if (_this.currentY - _this.startY >=
Number(_this.props.pullDownToRefreshThreshold)) {
_this.setState({
pullToRefreshThresholdBreached: true,
});
}
// so you can drag upto 1.5 times of the maxPullDownDistance
if (_this.currentY - _this.startY > _this.maxPullDownDistance * 1.5)
return;
if (_this._infScroll) {
_this._infScroll.style.overflow = 'visible';
_this._infScroll.style.transform = "translate3d(0px, " + (_this.currentY -
_this.startY) + "px, 0px)";
}
};
_this.onEnd = function () {
_this.startY = 0;
_this.currentY = 0;
_this.dragging = false;
if (_this.state.pullToRefreshThresholdBreached) {
_this.props.refreshFunction && _this.props.refreshFunction();
_this.setState({
pullToRefreshThresholdBreached: false,
});
}
requestAnimationFrame(function () {
// this._infScroll
if (_this._infScroll) {
_this._infScroll.style.overflow = 'auto';
_this._infScroll.style.transform = 'none';
_this._infScroll.style.willChange = 'unset';
}
});
};
_this.onScrollListener = function (event) {
if (typeof _this.props.onScroll === 'function') {
// Execute this callback in next tick so that it does not affect the
// functionality of the library.
setTimeout(function () { return _this.props.onScroll && _this.props.onScroll(event); }, 0);
}
var target = _this.props.height || _this._scrollableNode
? event.target
: document.documentElement.scrollTop
? document.documentElement
: document.body;
// to fix Pull to Refresh is disabled when hasMore is flipped to false
_this.lastScrollTop = target.scrollTop;
// return immediately if the action has already been triggered,
// prevents multiple triggers.
if (_this.actionTriggered)
return;
var atBottom = _this.props.inverse
? _this.isElementAtTop(target, _this.props.scrollThreshold)
: _this.isElementAtBottom(target, _this.props.scrollThreshold);
// call the `next` function in the props to trigger the next data fetch
if (atBottom && _this.props.hasMore) {
_this.actionTriggered = true;
_this.setState({ showLoader: true });
_this.props.next && _this.props.next();
}
};
_this.state = {
showLoader: false,
pullToRefreshThresholdBreached: false,
prevDataLength: props.dataLength,
};
_this.throttledOnScrollListener = throttle(150, _this.onScrollListener).bind(_this);
_this.onStart = _this.onStart.bind(_this);
_this.onMove = _this.onMove.bind(_this);
_this.onEnd = _this.onEnd.bind(_this);
return _this;
}
InfiniteScroll.prototype.componentDidMount = function () {
if (typeof this.props.dataLength === 'undefined') {
throw new Error("mandatory prop \"dataLength\" is missing. The prop is needed" +
" when loading more content. Check README.md for usage");
}
this._scrollableNode = this.getScrollableTarget();
this.el = this.props.height
? this._infScroll
: this._scrollableNode || window;
if (this.el) {
this.el.addEventListener('scroll', this
.throttledOnScrollListener);
}
if (typeof this.props.initialScrollY === 'number' &&
this.el &&
this.el instanceof HTMLElement &&
this.el.scrollHeight > this.props.initialScrollY) {
this.el.scrollTo(0, this.props.initialScrollY);
}
if (this.props.pullDownToRefresh && this.el) {
this.el.addEventListener('touchstart', this.onStart);
this.el.addEventListener('touchmove', this.onMove);
this.el.addEventListener('touchend', this.onEnd);
this.el.addEventListener('mousedown', this.onStart);
this.el.addEventListener('mousemove', this.onMove);
this.el.addEventListener('mouseup', this.onEnd);
// get BCR of pullDown element to position it above
this.maxPullDownDistance =
(this._pullDown &&
this._pullDown.firstChild &&
this._pullDown.firstChild.getBoundingClientRect()
.height) ||
0;
this.forceUpdate();
if (typeof this.props.refreshFunction !== 'function') {
throw new Error("Mandatory prop \"refreshFunction\" missing.\n Pull Down To Refresh functionality will not work\n as expected. Check README.md for usage'");
}
}
};
InfiniteScroll.prototype.componentWillUnmount = function () {
if (this.el) {
this.el.removeEventListener('scroll', this
.throttledOnScrollListener);
if (this.props.pullDownToRefresh) {
this.el.removeEventListener('touchstart', this.onStart);
this.el.removeEventListener('touchmove', this.onMove);
this.el.removeEventListener('touchend', this.onEnd);
this.el.removeEventListener('mousedown', this.onStart);
this.el.removeEventListener('mousemove', this.onMove);
this.el.removeEventListener('mouseup', this.onEnd);
}
}
};
InfiniteScroll.prototype.componentDidUpdate = function (prevProps) {
// do nothing when dataLength is unchanged
if (this.props.dataLength === prevProps.dataLength)
return;
this.actionTriggered = false;
// update state when new data was sent in
this.setState({
showLoader: false,
});
};
InfiniteScroll.getDerivedStateFromProps = function (nextProps, prevState) {
var dataLengthChanged = nextProps.dataLength !== prevState.prevDataLength;
// reset when data changes
if (dataLengthChanged) {
return __assign(__assign({}, prevState), { prevDataLength: nextProps.dataLength });
}
return null;
};
InfiniteScroll.prototype.isElementAtTop = function (target, scrollThreshold) {
if (scrollThreshold === void 0) { scrollThreshold = 0.8; }
var clientHeight = target === document.body || target === document.documentElement
? window.screen.availHeight
: target.clientHeight;
var threshold = parseThreshold(scrollThreshold);
if (threshold.unit === ThresholdUnits.Pixel) {
return (target.scrollTop <=
threshold.value + clientHeight - target.scrollHeight + 1);
}
return (target.scrollTop <=
threshold.value / 100 + clientHeight - target.scrollHeight + 1);
};
InfiniteScroll.prototype.isElementAtBottom = function (target, scrollThreshold) {
if (scrollThreshold === void 0) { scrollThreshold = 0.8; }
var clientHeight = target === document.body || target === document.documentElement
? window.screen.availHeight
: target.clientHeight;
var threshold = parseThreshold(scrollThreshold);
if (threshold.unit === ThresholdUnits.Pixel) {
return (target.scrollTop + clientHeight >= target.scrollHeight - threshold.value);
}
return (target.scrollTop + clientHeight >=
(threshold.value / 100) * target.scrollHeight);
};
InfiniteScroll.prototype.render = function () {
var _this = this;
var style = __assign({ height: this.props.height || 'auto', overflow: 'auto', WebkitOverflowScrolling: 'touch' }, this.props.style);
var hasChildren = this.props.hasChildren ||
!!(this.props.children &&
this.props.children instanceof Array &&
this.props.children.length);
// because heighted infiniteScroll visualy breaks
// on drag down as overflow becomes visible
var outerDivStyle = this.props.pullDownToRefresh && this.props.height
? { overflow: 'auto' }
: {};
return (React__default.createElement("div", { style: outerDivStyle, className: "infinite-scroll-component__outerdiv" },
React__default.createElement("div", { className: "infinite-scroll-component " + (this.props.className || ''), ref: function (infScroll) { return (_this._infScroll = infScroll); }, style: style },
this.props.pullDownToRefresh && (React__default.createElement("div", { style: { position: 'relative' }, ref: function (pullDown) { return (_this._pullDown = pullDown); } },
React__default.createElement("div", { style: {
position: 'absolute',
left: 0,
right: 0,
top: -1 * this.maxPullDownDistance,
} }, this.state.pullToRefreshThresholdBreached
? this.props.releaseToRefreshContent
: this.props.pullDownToRefreshContent))),
this.props.children,
!this.state.showLoader &&
!hasChildren &&
this.props.hasMore &&
this.props.loader,
this.state.showLoader && this.props.hasMore && this.props.loader,
!this.props.hasMore && this.props.endMessage)));
};
return InfiniteScroll;
}(React.Component));
module.exports = InfiniteScroll;
//# sourceMappingURL=index.js.map