UNPKG

react-scoll

Version:

infinite scroller, record scroll position, like twitter's timeline

199 lines (186 loc) 7.93 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var React = require('react'); var React__default = _interopDefault(React); const ScrollContext = React.createContext({ items: [], setItems() { }, index: 0, setIndex() { }, scrollTop: 0, setScrollTop() { }, page: 1, setPage() { }, isControl: false, isComplete: false, setIsComplete() { }, loading: false, setLoading() { }, }); const packItems = (items = [], startIndex = 0) => { return items.map((item, i) => ({ index: startIndex + i, data: item })); }; const Provider = ({ children, source }) => { const [items, setItems] = React.useState([]); const [index, setIndex] = React.useState(0); const [page, setPage] = React.useState(1); const [scrollTop, setScrollTop] = React.useState(0); const [isControl, setIsControl] = React.useState(false); const [isComplete, setIsComplete] = React.useState(false); const [loading, setLoading] = React.useState(false); const lastItemsLength = React.useRef(0); React.useEffect(() => { if (!source) return; setItems(packItems(source)); setLoading(false); setIsControl(true); if (lastItemsLength.current === source.length) { setIsComplete(true); } else { lastItemsLength.current = source.length; setIsComplete(false); } }, [source]); return (React__default.createElement(ScrollContext.Provider, { value: { items, setItems, index, setIndex, scrollTop, setScrollTop, page, setPage, isControl, isComplete, setIsComplete, loading, setLoading, } }, React__default.Children.only(children))); }; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } const Scroller = (_a, outRef) => { var { averageHeight = 350, element: Element, length: customLength, style = {}, onFetch, upperRender } = _a, divProps = __rest(_a, ["averageHeight", "element", "length", "style", "onFetch", "upperRender"]); const [mounted, setMounted] = React.useState(false); const [length, setLength] = React.useState(0); const [topCount, setTopCount] = React.useState(0); const lastScrollTop = React.useRef(0); const contanierRef = React.useRef(); const upperRenderRef = React.useRef(null); const { items, setItems, index, setIndex, scrollTop, setScrollTop, page, setPage, isControl, isComplete, setIsComplete, loading, setLoading, } = React.useContext(ScrollContext); const canFetchRef = React.useRef(!items.length); const activeItems = React.useMemo(() => items.slice(index, index + length), [items, index, length]); const upperHolderHeight = React.useMemo(() => averageHeight * index, [ index, averageHeight, ]); const underHolderHight = React.useMemo(() => { const v = averageHeight * (items.length - index - length); return v >= 0 ? v : 0; }, [averageHeight, items.length, index, length]); // restore scroll position React.useEffect(() => { if (mounted) return; if (contanierRef.current) { contanierRef.current.scrollTop = scrollTop; } setMounted(true); }, [scrollTop, mounted]); // guess item count, visible count eg. React.useEffect(() => { if (customLength) { setLength(customLength); setTopCount(customLength * 0.2); return; } if (items.length) { const len = Math.floor(items.length * 0.7); setLength(len); setTopCount(Math.floor(len * 0.2)); } }, [customLength, items.length, page]); React.useEffect(() => { if (!canFetchRef.current) return; setLoading(true); const push = (data) => { setItems((state) => { const list = packItems(data, state.length); return page === 1 ? list : [...state, ...list]; }); if (!data.length) { setIsComplete(true); } setLoading(false); }; if (onFetch) { isControl ? onFetch() : onFetch({ page, push }); canFetchRef.current = false; } }, [isControl, onFetch, page, setIsComplete, setItems, setLoading]); React.useEffect(() => { if (!contanierRef.current) return; const cd = contanierRef.current; const onScroll = () => { if (!cd) return; const direction = cd.scrollTop - lastScrollTop.current > 0; if (direction && !loading && !isComplete && cd.scrollTop + 3000 > cd.scrollHeight - cd.clientHeight) { setPage((state) => state + 1); canFetchRef.current = true; } const upperRenderHeight = upperRenderRef.current ? upperRenderRef.current.clientHeight : 0; const startIndex = Math.floor((cd.scrollTop - topCount * averageHeight - upperRenderHeight) / averageHeight); setIndex(startIndex >= 0 ? startIndex : 0); lastScrollTop.current = cd.scrollTop; setScrollTop(cd.scrollTop); }; cd.addEventListener('scroll', onScroll); return () => cd.removeEventListener('scroll', onScroll); }, [averageHeight, loading, topCount, setIndex, setScrollTop, setPage, isComplete]); return (React__default.createElement("div", Object.assign({}, divProps, { ref: (n) => { contanierRef.current = n; outRef && (outRef.current = n); }, style: Object.assign({ height: '100vh', overflow: 'auto' }, style) }), React__default.createElement("div", { ref: upperRenderRef }, upperRender && upperRender()), React__default.createElement("div", { style: { height: upperHolderHeight } }), activeItems.map((item) => React__default.createElement(Element, Object.assign({ key: item.index }, item.data))), React__default.createElement("div", { style: { height: underHolderHight, } }))); }; var scroller = React.forwardRef(Scroller); exports.Provider = Provider; exports.Scroller = scroller;