UNPKG

@etsoo/react

Version:

TypeScript ReactJs UI Independent Framework

195 lines (194 loc) 7.24 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ScrollerList = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const shared_1 = require("@etsoo/shared"); const react_1 = __importDefault(require("react")); const react_window_1 = require("react-window"); const useCombinedRefs_1 = require("../uses/useCombinedRefs"); // Calculate loadBatchSize const calculateBatchSize = (height, rowHeight) => { if (typeof height === "number" && typeof rowHeight === "number" && rowHeight > 0) { return 1 + Math.ceil(height / rowHeight); } return 10; }; /** * Scroller vertical list * @param props Props * @returns Component */ const ScrollerList = (props) => { // Destruct const { autoLoad = true, defaultOrderBy, height = "100%", width = "100%", mRef, style = {}, idField = "id", rowHeight, listRef, loadBatchSize = calculateBatchSize(height, rowHeight), loadData, threshold = 3, onRowsRendered, onInitLoad, onUpdateRows, ...rest } = props; // Style Object.assign(style, { width, height }); const localRef = (0, react_window_1.useListRef)(null); const refs = (0, useCombinedRefs_1.useCombinedRefs)(listRef, localRef); const [rows, updateRows] = react_1.default.useState([]); const setRows = (rows, reset = false) => { stateRefs.current.loadedItems = rows.length; updateRows(rows); onUpdateRows?.(rows, stateRefs.current, reset); }; const batchSize = shared_1.Utils.getResult(loadBatchSize, height); const stateRefs = react_1.default.useRef({ queryPaging: { currentPage: 0, orderBy: defaultOrderBy, batchSize }, autoLoad, loadedItems: 0, hasNextPage: true, isNextPageLoading: false, selectedItems: [], idCache: {} }); // Load data const loadDataLocal = (pageAdd = 1) => { // Prevent multiple loadings if (!stateRefs.current.hasNextPage || stateRefs.current.isNextPageLoading || stateRefs.current.isMounted === false) return; // Update state stateRefs.current.isNextPageLoading = true; // Parameters const { queryPaging, data } = stateRefs.current; const loadProps = { queryPaging, data }; loadData(loadProps, stateRefs.current.lastItem).then((result) => { if (result == null || stateRefs.current.isMounted === false) { return; } stateRefs.current.isMounted = true; const newItems = result.length; stateRefs.current.lastLoadedItems = newItems; stateRefs.current.lastItem = result.at(-1); stateRefs.current.hasNextPage = newItems >= batchSize; stateRefs.current.isNextPageLoading = false; if (pageAdd === 0) { // New items const newRows = stateRefs.current.lastLoadedItems ? [...rows] .splice(rows.length - stateRefs.current.lastLoadedItems, stateRefs.current.lastLoadedItems) .concat(result) : result; stateRefs.current.idCache = {}; for (const row of newRows) { const id = row[idField]; stateRefs.current.idCache[id] = null; } // Update rows setRows(newRows); } else { if (stateRefs.current.queryPaging.currentPage == null) stateRefs.current.queryPaging.currentPage = pageAdd; else stateRefs.current.queryPaging.currentPage += pageAdd; // Update rows, avoid duplicate items const newRows = [...rows]; for (const item of result) { const id = item[idField]; if (stateRefs.current.idCache[id] === undefined) { newRows.push(item); } } setRows(newRows); } }); }; // Reset the state and load again const reset = (add, items = []) => { const { queryPaging, ...rest } = add ?? {}; const resetState = { autoLoad: true, loadedItems: 0, hasNextPage: true, isNextPageLoading: false, lastLoadedItems: undefined, lastItem: undefined, ...rest }; Object.assign(stateRefs.current, resetState); Object.assign(stateRefs.current.queryPaging, { currentPage: 0, ...queryPaging }); // Reset if (stateRefs.current.isMounted !== false) setRows(items, true); }; react_1.default.useImperativeHandle(mRef, () => { return { get element() { return localRef.current?.element; }, delete(index) { const item = rows.at(index); if (item) { const newRows = [...rows]; newRows.splice(index, 1); setRows(newRows); } return item; }, insert(item, start) { const newRows = [...rows]; newRows.splice(start, 0, item); setRows(newRows); }, refresh() { loadDataLocal(0); }, reset, scrollToRow(param) { localRef.current?.scrollToRow(param); } }; }, []); // When layout ready react_1.default.useEffect(() => { // Return clear function return () => { stateRefs.current.isMounted = false; }; }, []); react_1.default.useEffect(() => { // Auto load data when current page is 0 if (stateRefs.current.queryPaging?.currentPage === 0 && stateRefs.current.autoLoad) { const initItems = onInitLoad == null || localRef.current == null ? undefined : onInitLoad(localRef.current); if (initItems) reset(initItems[1], initItems[0]); else loadDataLocal(); } }, [onInitLoad, loadDataLocal]); // Row count const rowCount = rows.length; // Layout return ((0, jsx_runtime_1.jsx)(react_window_1.List, { listRef: refs, onRowsRendered: (visibleCells, allCells) => { // No items, means no necessary to load more data during reset if (rowCount > 0 && visibleCells.stopIndex + threshold > rowCount) { // Auto load next page loadDataLocal(); } onRowsRendered?.(visibleCells, allCells); }, overscanCount: threshold, rowHeight: rowHeight, rowCount: rowCount, rowProps: { items: rows }, style: style, ...rest })); }; exports.ScrollerList = ScrollerList;