'use strict'; var jsxRuntime = require('react/jsx-runtime'); var react = require('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, SuppressedError, Symbol */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; const isPromise = (param) => { if (!param) { return false; } const convertedParam = param; if (typeof convertedParam.then === 'function') { return true; } return false; }; /** This is arbitrary number thus subject to change. */ const TIMEOUT_INTERVAL = 5; const InfiniteScroll = ({ isItemLoaded, loadMoreItems, onItemsRendered, itemCount, threshold, scrollOffset, children, outerRef, data, }) => { /** To prevent call loadMoreItems redundantly, in both `onItemsRendered` and on data change. */ const pending = react.useRef(false); const prevHeight = react.useRef(null); /** A dummy state to trigger rerender when promises by `loadMoreItems` resolves. */ const [dummyState, setDummyState] = react.useState(0); const forceRerender = () => setDummyState(pre => pre + 1); const prevData = react.useRef(data); const [shouldCheckDataChange, setShouldCheckDataChange] = react.useState(false); /** * Prevent calling `loadMoreItems` frequently when loading items at the start. * However, do this only if enough items are loaded that * the `outerRef`'s scrollHeight is large enough to scroll the element. */ const shouldBlockLoadMoreItems = react.useRef(null); const getOuterElement = react.useCallback(() => !outerRef ? null : 'current' in outerRef ? outerRef.current : outerRef, [outerRef]); const _loadMoreItems = react.useCallback((direction) => __awaiter(void 0, void 0, void 0, function* () { const outerElement = getOuterElement(); if (pending.current || !outerElement) { return; } const loadedEnoughItems = outerElement.clientHeight + scrollOffset < outerElement.scrollHeight; if (shouldBlockLoadMoreItems.current !== null && loadedEnoughItems) { return; } pending.current = true; // Record previous height to scroll before browser repaint. if (direction === 'start') { prevHeight.current = outerElement.scrollHeight; } const ret = loadMoreItems(direction); if (isPromise(ret)) { yield ret; setShouldCheckDataChange(true); } pending.current = false; }), [getOuterElement, loadMoreItems, scrollOffset]); const _onItemsRendered = react.useCallback((args) => { const { visibleStartIndex, visibleStopIndex } = args; onItemsRendered === null || onItemsRendered === void 0 ? void 0 : onItemsRendered(args); if (data.length === itemCount) { // All items are loaded and visible. return; } if (visibleStopIndex >= data.length - threshold && !isItemLoaded(data.length)) { // Last 'threshold' items are visible. _loadMoreItems('end'); } else if (visibleStartIndex <= threshold - 1 && !isItemLoaded(-1)) { // First 'threshold' items are visible. _loadMoreItems('start'); } }, [data.length, threshold, itemCount, isItemLoaded, _loadMoreItems, onItemsRendered]); // Force rerender only if the data has just been changed. // This is to prevent the following bugs. // - loadMoreItems function does not run even if it needs to. // - Force rerender logics cause an infinite rerender loop. // https://github.com/dlguswo333/react-window-infinite-scroll/issues/21 react.useEffect(() => { if (!shouldCheckDataChange) { return; } setShouldCheckDataChange(false); if (prevData.current !== data) { prevData.current = data; forceRerender(); } }, [data, shouldCheckDataChange]); // Call loadMoreItems when data changes to fetch data enough to fill the screen // without user having to scroll to induce onItemsRendered callback. react.useEffect(() => { const outerElement = getOuterElement(); if (!outerElement) { return; } const element = outerElement; const isAtBottom = element.scrollTop + element.offsetHeight + scrollOffset > element.scrollHeight; const isAtTop = element.scrollTop < scrollOffset; if (isAtBottom && !isItemLoaded(data.length)) { // Scrolled to bottom. _loadMoreItems('end'); } else if (isAtTop && !isItemLoaded(-1)) { // Scrolled to top. _loadMoreItems('start'); } }, [data, dummyState, isItemLoaded, itemCount, scrollOffset, _loadMoreItems, getOuterElement]); // Scroll downward to prevent calling loadMoreItems infinitely. // Do not pass deps argument as the effect should run with ref value. // Since it checks `prevHeight`, the function will execute only after loadMoreItems('start') is called. react.useLayoutEffect(() => { const set = () => { shouldBlockLoadMoreItems.current = setTimeout(() => { shouldBlockLoadMoreItems.current = null; }, TIMEOUT_INTERVAL); }; const reset = () => { if (shouldBlockLoadMoreItems.current !== null) { clearTimeout(shouldBlockLoadMoreItems.current); } }; const outerElement = getOuterElement(); if (prevHeight.current === null || outerElement === null) { if (shouldBlockLoadMoreItems.current !== null) { // Set the flag only when the flag is truthy // to gurantee to load more items at 'start' for the first time. set(); } return reset; } outerElement.scrollTop = Math.max(outerElement.scrollHeight - prevHeight.current, scrollOffset); prevHeight.current = null; set(); return reset; }); return jsxRuntime.jsx(jsxRuntime.Fragment, { children: children({ onItemsRendered: _onItemsRendered }) }); }; module.exports = InfiniteScroll; //# sourceMappingURL=index.cjs.map