@dotconnor/grommet
Version:
focus on the essential experience
333 lines (258 loc) • 12 kB
JavaScript
exports.__esModule = true;
exports.InfiniteScroll = void 0;
var _react = _interopRequireWildcard(require("react"));
var _utils = require("../../utils");
var _Box = require("../Box");
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
var InfiniteScroll = function InfiniteScroll(_ref) {
var children = _ref.children,
_ref$items = _ref.items,
items = _ref$items === void 0 ? [] : _ref$items,
onMore = _ref.onMore,
renderMarker = _ref.renderMarker,
replace = _ref.replace,
showProp = _ref.show,
_ref$step = _ref.step,
step = _ref$step === void 0 ? 50 : _ref$step;
// item index to be made visible initially
var _useState = (0, _react.useState)(showProp),
show = _useState[0],
setShow = _useState[1]; // the last page we have items for
var lastPage = (0, _react.useMemo)(function () {
return Math.floor(items.length / step);
}, [items.length, step]); // the pages we are rendering
var _useState2 = (0, _react.useState)([0, show ? Math.floor((show + step) / step) - 1 : 0]),
renderPageBounds = _useState2[0],
setRenderPageBounds = _useState2[1]; // the heights of the pages, approximated after we render the first page
// and then updated for pages that have rendered
var _useState3 = (0, _react.useState)([]),
pageHeights = _useState3[0],
setPageHeights = _useState3[1]; // what we're waiting for onMore to give us
var _useState4 = (0, _react.useState)(0),
pendingLength = _useState4[0],
setPendingLength = _useState4[1];
var aboveMarkerRef = (0, _react.useRef)(); // only when replacing
var belowMarkerRef = (0, _react.useRef)(); // scroll and resize handling
(0, _react.useEffect)(function () {
var scrollParents;
var evaluate = function evaluate() {
if (!scrollParents) return;
var scrollParent = scrollParents[0]; // Determine the scroll position of the scroll container
var top;
var height;
if (scrollParent === document) {
top = document.documentElement.scrollTop || document.body.scrollTop;
height = window.innerHeight;
} else {
top = scrollParent.scrollTop;
var rect = scrollParent.getBoundingClientRect();
height = rect.height;
}
var offset = height / 4; // so we pre-load when the user scrolls slowly
// Use the pageHeights to determine what pages we should render based
// on the current scroll window.
var nextBeginPage = 0;
var index = 0;
var pagesHeight = pageHeights[index] || 0;
while (pageHeights[index + 1] && pagesHeight < top - offset) {
index += 1;
nextBeginPage += 1;
pagesHeight += pageHeights[index];
}
var nextEndPage = nextBeginPage;
while (pageHeights[index] !== undefined && pagesHeight < top + height + offset) {
index += 1;
nextEndPage += 1; // when we haven't rendered the nextEndPage and we aren't replacing,
// we might not have a height for it yet
pagesHeight += pageHeights[index] || 0;
}
if (!replace) {
// when not replacing, never shrink bounds
nextBeginPage = 0;
nextEndPage = Math.max(renderPageBounds[1], nextEndPage);
}
if (show) {
// ensure we try to render any show page
var showPage = Math.floor((show + step) / step) - 1;
nextBeginPage = Math.min(showPage, nextBeginPage);
nextEndPage = Math.max(showPage, nextEndPage);
}
if (nextBeginPage !== renderPageBounds[0] || nextEndPage !== renderPageBounds[1]) {
setRenderPageBounds([nextBeginPage, nextEndPage]);
}
};
var timer;
var debounce = function debounce() {
clearTimeout(timer);
timer = setTimeout(evaluate, 10);
}; // might not be there yet or might have already rendered everything
if (belowMarkerRef.current) {
scrollParents = (0, _utils.findScrollParents)(belowMarkerRef.current);
scrollParents.forEach(function (sp) {
return sp.addEventListener('scroll', debounce);
});
}
window.addEventListener('resize', debounce);
evaluate();
return function () {
if (scrollParents) {
scrollParents.forEach(function (sp) {
return sp.removeEventListener('scroll', debounce);
});
}
window.removeEventListener('resize', debounce);
clearTimeout(timer);
};
}, [pageHeights, renderPageBounds, replace, show, step]); // check if we need to ask for more
(0, _react.useEffect)(function () {
if (onMore && renderPageBounds[1] === lastPage && items.length >= pendingLength) {
// remember we've asked for more, so we don't keep asking if it takes
// a while
setPendingLength(items.length + 1);
onMore();
}
}, [items.length, lastPage, onMore, pendingLength, renderPageBounds, step]); // scroll to any 'show'
(0, _react.useLayoutEffect)(function () {
// ride out any animation delays, 100ms empirically measured
var timer = setTimeout(function () {
if (show && belowMarkerRef.current) {
// calculate show index based on beginPage
var showIndex = show - renderPageBounds[0] * step + (renderPageBounds[0] ? 1 : 0);
var showNode = belowMarkerRef.current.parentNode.children.item(showIndex);
if (showNode) {
var scrollParent = (0, _utils.findScrollParent)(showNode);
if ((0, _utils.isNodeBeforeScroll)(showNode, scrollParent)) {
showNode.scrollIntoView(true);
} else if ((0, _utils.isNodeAfterScroll)(showNode, scrollParent)) {
showNode.scrollIntoView(false);
} // clean up after having shown
setShow(undefined);
}
}
}, 100);
return function () {
return clearTimeout(timer);
};
}, [renderPageBounds, show, step]); // calculate and keep track of page heights
(0, _react.useLayoutEffect)(function () {
// if don't have a belowMarker, we must have rendered everything already
if (!belowMarkerRef.current) return; // calculate page heights for rendered pages
var rendered = belowMarkerRef.current.parentNode.children; // ensure we've rendered the state we have
// above? + items in rendered pages + below === rendered DOM elements length
if ((aboveMarkerRef.current ? 1 : 0) + (renderPageBounds[1] - renderPageBounds[0] + 1) * step + 1 === rendered.length) {
var nextPageHeights; // step through each page
var i = renderPageBounds[0];
var lastBottom;
while (i <= renderPageBounds[1]) {
var topIndex = (aboveMarkerRef.current ? 1 : 0) + (i - renderPageBounds[0]) * step;
var bottomIndex = Math.min(topIndex + step - 1, rendered.length - 1); // we use lastBottom for top to ensure grid layouts work
var top = lastBottom !== undefined ? lastBottom : rendered.item(topIndex).getBoundingClientRect().top;
var _rendered$item$getBou = rendered.item(bottomIndex).getBoundingClientRect(),
bottom = _rendered$item$getBou.bottom;
var height = bottom - top;
if (bottom && (!pageHeights || pageHeights[i] !== height)) {
if (!nextPageHeights) nextPageHeights = [].concat(pageHeights || []);
nextPageHeights[i] = height;
}
lastBottom = bottom;
i += 1;
} // estimate page heights for pages we haven't rendered yet
while (replace && i <= lastPage) {
if (!pageHeights[i] && pageHeights[i] !== pageHeights[0]) {
if (!nextPageHeights) nextPageHeights = [].concat(pageHeights || []);
var _nextPageHeights = nextPageHeights;
nextPageHeights[i] = _nextPageHeights[0];
}
i += 1;
}
if (nextPageHeights) setPageHeights(nextPageHeights);
}
}, [lastPage, pageHeights, renderPageBounds, replace, step]); // calculate the height above the first rendered page using the pageHeights
var aboveHeight = (0, _react.useMemo)(function () {
if (!replace) return 0;
var height = 0;
var i = 0;
while (i < renderPageBounds[0]) {
height += pageHeights[i] || 0;
i += 1;
}
return height;
}, [pageHeights, renderPageBounds, replace]); // calculate the height below the last rendered page using the pageHeights
var belowHeight = (0, _react.useMemo)(function () {
if (!replace) return 0;
var height = 0;
var i = renderPageBounds[1] + 1;
while (i <= lastPage) {
height += pageHeights[i] || 0;
i += 1;
}
return height;
}, [lastPage, pageHeights, renderPageBounds, replace]);
var firstIndex = renderPageBounds[0] * step;
var lastIndex = Math.min((renderPageBounds[1] + 1) * step, items.length) - 1;
var result = [];
if (aboveHeight) {
var marker = /*#__PURE__*/_react["default"].createElement(_Box.Box, {
key: "above",
ref: aboveMarkerRef,
flex: false,
height: aboveHeight + "px"
});
if (renderMarker) {
// need to give it a key
marker = /*#__PURE__*/_react["default"].cloneElement(renderMarker(marker), {
key: 'above'
});
}
result.push(marker);
}
items.slice(firstIndex, lastIndex + 1).forEach(function (item, index) {
var itemsIndex = firstIndex + index;
var child = children(item, itemsIndex);
result.push(child);
});
if (replace || renderPageBounds[1] < lastPage || onMore) {
var _marker = /*#__PURE__*/_react["default"].createElement(_Box.Box, {
key: "below",
ref: !renderMarker && belowMarkerRef || undefined,
flex: false,
height: (belowHeight || 0) + "px"
});
if (renderMarker) {
// need to give it a key
var renderedMarker = renderMarker(_marker);
_marker = /*#__PURE__*/_react["default"].cloneElement(renderedMarker, {
key: 'below',
// We need to make sure our belowMarkerRef is tied to a component
// that has the same parent as the items being rendered. This is so
// we can use belowMarkerRef.current.parentNode.children to
// get a reference to the items in the DOM for calculating pageHeights.
//
// Since the caller might have included a ref in what their
// renderMarker returns, we have to take care of both refs.
// https://github.com/facebook/react/issues/8873#issuecomment-489579878
ref: function ref(node) {
// Keep your own reference
belowMarkerRef.current = node; // Call the original ref, if any
var ref = renderedMarker.ref;
if (typeof ref === 'function') {
ref(node);
} else if (ref !== null) {
ref.current = node;
}
}
});
}
result.push(_marker);
}
return result;
};
var InfiniteScrollDoc;
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line global-require
InfiniteScrollDoc = require('./doc').doc(InfiniteScroll);
}
var InfiniteScrollWrapper = InfiniteScrollDoc || InfiniteScroll;
exports.InfiniteScroll = InfiniteScrollWrapper;
;