UNPKG

@etsoo/react

Version:

TypeScript ReactJs UI Independent Framework

229 lines (228 loc) 8.74 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ScrollerGrid = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = __importDefault(require("react")); const react_window_1 = require("react-window"); const useCombinedRefs_1 = require("../uses/useCombinedRefs"); /** * Scroller vertical grid * @param props Props * @returns Component */ const ScrollerGrid = (props) => { // Destruct const { autoLoad = true, defaultOrderBy, footerRenderer, headerRenderer, height = "100%", gridRef, width = "100%", style = {}, idField = "id", loadBatchSize, loadData, mRef, onCellsRendered, onSelectChange, rowHeight, threshold = 3, onInitLoad, onUpdateRows, ...rest } = props; // Style Object.assign(style, { width, height, overflowX: "hidden" }); // Refs const localRef = (0, react_window_1.useGridRef)(null); const refs = (0, useCombinedRefs_1.useCombinedRefs)(gridRef, localRef); // Rows const [rows, updateRows] = react_1.default.useState([]); const setRows = (rows, reset = false) => { stateRefs.current.loadedItems = rows.length; updateRows(rows); onUpdateRows?.(rows, stateRefs.current, reset); }; // State Refs const stateRefs = react_1.default.useRef({ queryPaging: { currentPage: 0, orderBy: defaultOrderBy, batchSize: 10 }, autoLoad, hasNextPage: true, isNextPageLoading: false, loadedItems: 0, 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.isNextPageLoading = false; stateRefs.current.hasNextPage = newItems >= stateRefs.current.queryPaging.batchSize; 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 { // Set current page 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 items if (stateRefs.current.isMounted !== false) setRows(items, true); }; react_1.default.useImperativeHandle(mRef, () => ({ 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, scrollToCell(param) { localRef.current?.scrollToCell(param); }, scrollToColumn(param) { localRef.current?.scrollToColumn(param); }, scrollToRow(param) { localRef.current?.scrollToRow(param); }, select(rowIndex) { // Select only one item const selectedItems = stateRefs.current.selectedItems; selectedItems[0] = rows[rowIndex]; if (onSelectChange) onSelectChange(selectedItems); }, selectAll(checked) { const selectedItems = stateRefs.current.selectedItems; rows.forEach((row) => { const index = selectedItems.findIndex((selectedItem) => selectedItem[idField] === row[idField]); if (checked) { if (index === -1) selectedItems.push(row); } else if (index !== -1) { selectedItems.splice(index, 1); } }); if (onSelectChange) onSelectChange(selectedItems); }, selectItem(item, checked) { const selectedItems = stateRefs.current.selectedItems; const index = selectedItems.findIndex((selectedItem) => selectedItem[idField] === item[idField]); if (checked) { if (index === -1) selectedItems.push(item); } else { if (index !== -1) selectedItems.splice(index, 1); } if (onSelectChange) onSelectChange(selectedItems); } }), [rows]); // Rows const rowCount = rows.length; 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]); react_1.default.useEffect(() => { return () => { stateRefs.current.isMounted = false; }; }, []); // Layout return ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [headerRenderer && headerRenderer(stateRefs.current), (0, jsx_runtime_1.jsx)(react_window_1.Grid, { cellProps: { rows, states: stateRefs.current }, gridRef: refs, onCellsRendered: (visibleCells, allCells) => { // No items, means no necessary to load more data during reset if (rowCount > 0 && visibleCells.rowStopIndex + threshold > rowCount) { // Auto load next page loadDataLocal(); } onCellsRendered?.(visibleCells, allCells); }, overscanCount: threshold, rowHeight: rowHeight, rowCount: rowCount, style: style, ...rest }), footerRenderer && footerRenderer(rows, stateRefs.current)] })); }; exports.ScrollerGrid = ScrollerGrid;