UNPKG

@appbuckets/react-ui

Version:
752 lines (749 loc) 23.4 kB
import { __rest, __assign, __read } from 'tslib'; import * as React from 'react'; import clsx from 'clsx'; import { areEqual, VariableSizeList } from 'react-window'; import { useElementSize } from '../hooks/useElementSize.js'; import '../BucketTheme/BucketTheme.js'; import { useWithDefaultProps } from '../BucketTheme/BucketContext.js'; import '../RxTable/RxTable.js'; import { useRxTable, RxTableProvider } from '../RxTable/RxTable.context.js'; import { useRxTableFactory } from '../RxTable/RxTable.factory.js'; import BodyRow from '../RxTable/components/BodyRow.js'; import FooterRow from '../RxTable/components/FooterRow.js'; import FiltersRow from '../RxTable/components/FiltersRow.js'; import HeaderRow from '../RxTable/components/HeaderRow.js'; import StateDependentBodyRow from '../RxTable/components/StateDependentBodyRow.js'; import RxTableBodyCell from '../RxTable/defaults/RxTableBodyCell.js'; import RxTableBodyRow from '../RxTable/defaults/RxTableBodyRow.js'; import RxTableFooterCell from '../RxTable/defaults/RxTableFooterCell.js'; import RxTableHeaderCell from '../RxTable/defaults/RxTableHeaderCell.js'; import RxTableEmptyContent from '../RxTable/defaults/RxTableEmptyContent.js'; import RxTableError from '../RxTable/defaults/RxTableError.js'; import RxTableLoader from '../RxTable/defaults/RxTableLoader.js'; import ScrollOnTop from './atoms/ScrollOnTop.js'; /* -------- * Memoize the BodyRow Component to be used with VariableSizeList * -------- */ var MemoizedBodyRow = React.memo(BodyRow, areEqual); /* -------- * Variable Size List Inner Element * --- * Is extracted from original code to avoid * list rerender * -------- */ var VariableSizeListInnerElement = React.forwardRef(function (props, ref) { /** Get Body Component */ var _a = useRxTable(), Body = _a.Components.Body, classes = _a.classes, styles = _a.styles; /** Extract style and classes from props */ var style = props.style, className = props.className, rest = __rest(props, ['style', 'className']); /** Merge Body Classes */ var bodyClasses = clsx(className, classes.Body); /** Render the Component */ return React.createElement( Body, __assign({}, rest, { ref: ref, className: bodyClasses, style: __assign(__assign({}, styles.Body), style), }) ); }); /* -------- * Variable Size List Outer Element * --- * Is extracted from original code to avoid * list rerender * -------- */ var VariableSizeListOuterElement = React.forwardRef(function (props, ref) { /** Get Body Component */ var _a = useRxTable(), BodyWrapper = _a.Components.BodyWrapper, classes = _a.classes, styles = _a.styles; /** Extract style and classes from props */ var style = props.style, className = props.className, rest = __rest(props, ['style', 'className']); /** Merge Body Classes */ var bodyWrapperClasses = clsx(className, classes.BodyWrapper); /** Render the Component */ return React.createElement( BodyWrapper, __assign({}, rest, { ref: ref, className: bodyWrapperClasses, style: __assign(__assign({}, styles.BodyWrapper), style), }) ); }); /* -------- * Component Definition * -------- */ var VirtualizedTable = function (receivedProps) { var props = useWithDefaultProps('virtualizedTable', receivedProps); var /** RxTable Shared Props */ userDefinedClasses = props.classes; props.className; var columns = props.columns, userDefinedComponents = props.Components, data = props.data, defaultData = props.defaultData, userDefinedDefaultReverseSorting = props.defaultReverseSorting, userDefinedSelectedData = props.defaultSelectedData, userDefinedDefaultSort = props.defaultSort, disableHeader = props.disableHeader, filterLogic = props.filterLogic, userDefinedGetRowKey = props.getRowKey, userDefinedHeight = props.height, initiallyLoading = props.initiallyLoading, loaderProps = props.loaderProps, noFilteredDataEmptyContentProps = props.noFilteredDataEmptyContentProps, noDataEmptyContentProps = props.noDataEmptyContentProps, onRowClick = props.onRowClick, onSortChange = props.onSortChange, onSelectedDataChange = props.onSelectedDataChange, reloadDependency = props.reloadDependency, reloadSilently = props.reloadSilently, userDefinedReverseSorting = props.reverseSorting, scrollOnTopButtonProps = props.scrollOnTopButtonProps, scrollOnTopOffsetVisibility = props.scrollOnTopOffsetVisibility, selectable = props.selectable, selectColumnProps = props.selectColumnProps, userDefinedSort = props.sort, style = props.style, userDefinedStyles = props.styles, userDefinedWidth = props.width, useScrollOnTop = props.useScrollOnTop, /** Dedicated VirtualizedTable Props */ compressed = props.compressed, userDefinedFilterRowHeight = props.filterRowHeight, userDefinedFooterRowHeight = props.footerRowHeight, userDefinedHeaderHeight = props.headerHeight, rowHeight = props.rowHeight, /** Size Detector Props */ maximumWidth = props.maximumWidth, maximumHeight = props.maximumHeight, minimumWidth = props.minimumWidth, minimumHeight = props.minimumHeight, subtractToWidth = props.subtractToWidth, subtractToHeight = props.subtractToHeight, /** Extracted Variable Size List Props */ direction = props.direction, itemKey = props.itemKey, overscanCount = props.overscanCount, onItemsRendered = props.onItemsRendered, userDefinedOnScroll = props.onScroll, useIsScrolling = props.useIsScrolling, rest = __rest(props, [ 'classes', 'className', 'columns', 'Components', 'data', 'defaultData', 'defaultReverseSorting', 'defaultSelectedData', 'defaultSort', 'disableHeader', 'filterLogic', 'getRowKey', 'height', 'initiallyLoading', 'loaderProps', 'noFilteredDataEmptyContentProps', 'noDataEmptyContentProps', 'onRowClick', 'onSortChange', 'onSelectedDataChange', 'reloadDependency', 'reloadSilently', 'reverseSorting', 'scrollOnTopButtonProps', 'scrollOnTopOffsetVisibility', 'selectable', 'selectColumnProps', 'sort', 'style', 'styles', 'width', 'useScrollOnTop', 'compressed', 'filterRowHeight', 'footerRowHeight', 'headerHeight', 'rowHeight', 'maximumWidth', 'maximumHeight', 'minimumWidth', 'minimumHeight', 'subtractToWidth', 'subtractToHeight', 'direction', 'itemKey', 'overscanCount', 'onItemsRendered', 'onScroll', 'useIsScrolling', ]); // ---- // Initialize the VariableSizeList ref to use ScrollOnTop // ---- var variableSizeListRef = React.useRef(null); // ---- // Use an internal State to Show/Hide ScrollOnTop Component // ---- var _a = __read(React.useState(false), 2), scrollOnTopVisible = _a[0], setScrollOnTopVisible = _a[1]; // ---- // Checker Builder // ---- var hasFilterRow = React.useMemo( function () { return columns.some(function (column) { return !!column.filter; }); }, [columns] ); var hasFooterRow = React.useMemo( function () { return columns.some(function (column) { return !!column.footer; }); }, [columns] ); var hasHeaderRow = React.useMemo( function () { return columns.some(function (column) { return !!column.header; }); }, [columns] ); // ---- // Initialize the Width Detector // ---- var _b = __read( useElementSize({ useDetectorWidthOnly: true, fixedHeight: userDefinedHeight, fixedWidth: userDefinedWidth, maximumWidth: maximumWidth, maximumHeight: maximumHeight, minimumWidth: minimumWidth, minimumHeight: minimumHeight, subtractToWidth: subtractToWidth, subtractToHeight: subtractToHeight, }), 2 ), widthDetector = _b[0], _c = _b[1], width = _c.width, height = _c.height; var headerHeight = React.useMemo( function () { /** If table has not header row, return 0 */ if (!hasHeaderRow) { return 0; } var baseHeaderHeight = typeof userDefinedHeaderHeight === 'number' ? userDefinedHeaderHeight : typeof rowHeight === 'number' ? rowHeight : 0; /** If table is not selectable, return the base height */ if (!selectable || hasFilterRow) { return baseHeaderHeight; } return Math.max(40, baseHeaderHeight); }, [hasFilterRow, hasHeaderRow, rowHeight, selectable, userDefinedHeaderHeight] ); var filterRowHeight = hasFilterRow ? typeof userDefinedFilterRowHeight === 'number' ? userDefinedFilterRowHeight : headerHeight : 0; var footerRowHeight = hasFooterRow ? typeof userDefinedFooterRowHeight === 'number' ? userDefinedFooterRowHeight : headerHeight : 0; // ---- // Load RxTableProps // ---- var rxTableProps = useRxTableFactory({ classes: __assign( { Body: clsx( 'virtualized body', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.Body ), BodyCell: clsx( 'virtualized cell', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.BodyCell ), BodyRow: clsx( 'virtualized row', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.BodyRow ), BodyWrapper: clsx( 'virtualized table virtualized-body', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.BodyWrapper ), ErrorCell: clsx( 'cell error-cell', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.ErrorCell ), ErrorRow: clsx( 'row error-row', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.ErrorRow ), Footer: clsx( 'virtualized foot', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.Footer ), FooterRow: clsx( 'virtualized row', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.FooterRow ), FooterWrapper: clsx( 'virtualized table virtualized-foot', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.FooterWrapper ), Header: clsx( 'virtualized head', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.Header ), HeaderRow: clsx( 'virtualized row', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.HeaderRow ), HeaderWrapper: clsx( 'virtualized table virtualized-head', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.HeaderWrapper ), LoaderCell: clsx( 'cell loading-cell', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.LoaderCell ), LoaderRow: clsx( 'row loading-row', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.LoaderRow ), NoContentCell: clsx( 'cell no-content-cell', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.NoContentCell ), NoContentRow: clsx( 'row no-content-row', userDefinedClasses === null || userDefinedClasses === void 0 ? void 0 : userDefinedClasses.NoContentRow ), }, userDefinedClasses ), styles: __assign( { HeaderCell: __assign( { height: headerHeight }, userDefinedStyles === null || userDefinedStyles === void 0 ? void 0 : userDefinedStyles.HeaderCell ), FilterCell: __assign( { height: filterRowHeight }, userDefinedStyles === null || userDefinedStyles === void 0 ? void 0 : userDefinedStyles.FilterCell ), FooterCell: __assign( { height: footerRowHeight }, userDefinedStyles === null || userDefinedStyles === void 0 ? void 0 : userDefinedStyles.FooterCell ), }, userDefinedStyles ), columns: columns, data: data, defaultData: defaultData, defaultLoading: initiallyLoading, defaultReverseSorting: userDefinedDefaultReverseSorting, defaultSelectedData: userDefinedSelectedData, defaultSort: userDefinedDefaultSort, filterLogic: filterLogic, getRowKey: userDefinedGetRowKey, isVirtualized: true, onRowClick: onRowClick, onSelectedDataChange: onSelectedDataChange, onSortChange: onSortChange, reloadDependency: reloadDependency, reloadSilently: reloadSilently, reverseSorting: userDefinedReverseSorting, selectable: selectable, selectColumnProps: selectColumnProps, sort: userDefinedSort, width: width, }); // ---- // Row Height Calculator // ---- var estimatedItemSize = typeof rowHeight === 'number' ? rowHeight : undefined; var getRowHeight = React.useCallback( function (index) { if (typeof rowHeight === 'number') { return rowHeight; } return rowHeight(index); }, [rowHeight] ); // ---- // Compute Table Width and Height and Accessor // ---- var tableBodyHeight = height - (!disableHeader ? headerHeight : 0) - filterRowHeight - footerRowHeight; var tableDataHeight = typeof rowHeight === 'number' ? rxTableProps.tableData.length * rowHeight : typeof estimatedItemSize === 'number' ? rxTableProps.tableData.length * estimatedItemSize : Number.MAX_SAFE_INTEGER; var effectiveBodyHeight = Math.max( 0, Math.min(tableBodyHeight, tableDataHeight) ); var effectiveTableHeight = effectiveBodyHeight + (!disableHeader ? headerHeight : 0) + filterRowHeight + footerRowHeight; // ---- // Row Key Getter // ---- var getRowKey = React.useCallback( function (index) { /** If an itemKey function exists, use it */ if (typeof itemKey === 'function') { return itemKey(index, rxTableProps.tableData); } /** Use the data selector function */ var extractedKey = rxTableProps.selection.getRowKey( rxTableProps.tableData[index], index, rxTableProps.tableData ); return extractedKey === '' ? index : extractedKey; }, [itemKey, rxTableProps.selection, rxTableProps.tableData] ); // ---- // Define Components // ---- var Components = React.useMemo( function () { return __assign( { Body: 'div', BodyCell: RxTableBodyCell, BodyRow: RxTableBodyRow, BodyWrapper: 'div', Error: RxTableError, ErrorRow: 'div', ErrorCell: 'div', Footer: 'div', FooterCell: RxTableFooterCell, FooterRow: 'div', FooterWrapper: 'div', Header: 'div', HeaderCell: RxTableHeaderCell, HeaderRow: 'div', HeaderWrapper: 'div', Loader: RxTableLoader, LoaderRow: 'div', LoaderCell: 'div', NoContent: RxTableEmptyContent, NoContentCell: 'div', NoContentRow: 'div', }, userDefinedComponents ); }, [userDefinedComponents] ); // ---- // Context Building // ---- var rxTableContext = __assign(__assign({}, rxTableProps), { Components: Components, loaderProps: loaderProps, noFilteredDataEmptyContentProps: noFilteredDataEmptyContentProps, noDataEmptyContentProps: noDataEmptyContentProps, }); // ---- // Fragments could not have properties extra from key // ---- var headerWrapperProps = Components.HeaderWrapper !== React.Fragment ? { className: rxTableProps.classes.HeaderWrapper, style: rxTableProps.styles.HeaderWrapper, } : {}; var footerWrapperProps = Components.FooterWrapper !== React.Fragment ? { className: rxTableProps.classes.FooterWrapper, style: rxTableProps.styles.FooterWrapper, } : {}; // ---- // Build a custom onScroll handler to show/hide ScrollOnTop component // ---- var handleTableScroll = React.useCallback( function (scrollProps) { /** If must use the OnScroll Component, check if this must be visible or invisible */ if (useScrollOnTop && effectiveBodyHeight > 0) { /** Get the offset */ var offset = scrollOnTopOffsetVisibility || effectiveBodyHeight * 2; /** Update only if is necessary */ if (scrollOnTopVisible !== scrollProps.scrollOffset > offset) { setScrollOnTopVisible(scrollProps.scrollOffset > offset); } } /** If a userDefinedOnScroll function exists, use it */ if (typeof userDefinedOnScroll === 'function') { userDefinedOnScroll(scrollProps); } }, [ userDefinedOnScroll, useScrollOnTop, scrollOnTopOffsetVisibility, effectiveBodyHeight, scrollOnTopVisible, ] ); var handleScrollOnTopClick = React.useCallback(function () { /** Scroll the list on top */ if (variableSizeListRef.current) { variableSizeListRef.current.scrollTo(0); } }, []); // ---- // Wrap the StateDependentRow to avoid multiple Body and BodyWrapper component nest // ---- var isShowingData = !( rxTableProps.dataState.isLoading || rxTableProps.dataState.error || !rxTableProps.tableData.length ); var BodyWrapper = Components.BodyWrapper, Body = Components.Body; var tableBodyContent = React.useMemo( function () { if (!isShowingData) { var bodyWrapperProps = BodyWrapper !== React.Fragment ? { className: rxTableProps.classes.BodyWrapper, style: rxTableProps.styles.BodyWrapper, } : {}; return React.createElement( BodyWrapper, __assign({}, bodyWrapperProps), React.createElement( Body, { style: rxTableProps.styles.Body, className: rxTableProps.classes.Body, }, React.createElement(StateDependentBodyRow, null) ) ); } return React.createElement( VariableSizeList, { ref: variableSizeListRef, direction: direction, itemKey: getRowKey, overscanCount: overscanCount, onItemsRendered: onItemsRendered, onScroll: useScrollOnTop || typeof userDefinedOnScroll === 'function' ? handleTableScroll : undefined, useIsScrolling: useIsScrolling, width: rxTableProps.layout.effectiveTableWidth, height: effectiveBodyHeight, itemSize: getRowHeight, estimatedItemSize: estimatedItemSize, itemCount: rxTableProps.tableData.length, outerElementType: VariableSizeListOuterElement, innerElementType: VariableSizeListInnerElement, }, MemoizedBodyRow ); }, [ isShowingData, direction, getRowKey, overscanCount, onItemsRendered, useScrollOnTop, userDefinedOnScroll, handleTableScroll, useIsScrolling, rxTableProps.layout.effectiveTableWidth, rxTableProps.tableData.length, rxTableProps.classes.BodyWrapper, rxTableProps.classes.Body, rxTableProps.styles.BodyWrapper, rxTableProps.styles.Body, effectiveBodyHeight, getRowHeight, estimatedItemSize, Body, BodyWrapper, ] ); // ---- // Build Table ClassList // ---- var wrapperClasses = clsx('virtualized-table', compressed && 'compressed'); // ---- // Build Wrapper Style // ---- var wrapperStyle = React.useMemo( function () { return isShowingData ? __assign( { height: ''.concat(effectiveTableHeight, 'px'), width: ''.concat(width, 'px'), overflow: 'auto', maxHeight: '100vh', minHeight: ''.concat( (!disableHeader ? headerHeight : 0) + filterRowHeight, 'px' ), }, style ) : __assign({}, style); }, [ effectiveTableHeight, width, style, isShowingData, disableHeader, headerHeight, filterRowHeight, ] ); // ---- // Component Render // ---- return React.createElement( React.Fragment, null, widthDetector, React.createElement( 'div', __assign({}, rest, { className: wrapperClasses, style: wrapperStyle }), React.createElement( RxTableProvider, { value: rxTableContext }, (rxTableProps.layout.hasHeaderRow || rxTableProps.layout.hasFilterRow) && React.createElement( Components.HeaderWrapper, __assign({}, headerWrapperProps), React.createElement( Components.Header, { className: rxTableProps.classes.Header, style: rxTableProps.styles.Header, }, rxTableProps.layout.hasHeaderRow && React.createElement(HeaderRow, null), rxTableProps.layout.hasFilterRow && React.createElement(FiltersRow, null) ) ), tableBodyContent, rxTableProps.layout.hasFooterRow && React.createElement( Components.FooterWrapper, __assign({}, footerWrapperProps), React.createElement( Components.Footer, { style: rxTableProps.styles.Footer, className: rxTableProps.classes.Footer, }, React.createElement(FooterRow, null) ) ), useScrollOnTop && React.createElement( ScrollOnTop, __assign({}, scrollOnTopButtonProps, { visible: scrollOnTopVisible, onClick: handleScrollOnTopClick, }) ) ) ) ); }; VirtualizedTable.displayName = 'VirtualizedTable'; export { VirtualizedTable as default };