UNPKG

@ui5/webcomponents-react

Version:

React Wrapper for UI5 Web Components and additional components

771 lines (765 loc) 36.7 kB
'use client'; import { useVirtualizer } from '@tanstack/react-virtual'; import { ARIA_LABEL_EMPTY_CELL } from '@ui5/webcomponents/dist/generated/i18n/i18n-defaults.js'; import { debounce, enrichEventWithDetails, useI18nBundle, useIsomorphicLayoutEffect, useIsRTL, useStylesheet, useSyncRef } from '@ui5/webcomponents-react-base'; import { clsx } from 'clsx'; import { forwardRef, useCallback, useEffect, useId, useMemo, useRef } from 'react'; import { AnalyticalTableNoDataReason } from '../../enums/AnalyticalTableNoDataReason.js'; import { AnalyticalTablePopinDisplay } from '../../enums/AnalyticalTablePopinDisplay.js'; import { AnalyticalTableScaleWidthMode } from '../../enums/AnalyticalTableScaleWidthMode.js'; import { AnalyticalTableSelectionBehavior } from '../../enums/AnalyticalTableSelectionBehavior.js'; import { AnalyticalTableSelectionMode } from '../../enums/AnalyticalTableSelectionMode.js'; import { AnalyticalTableSubComponentsBehavior } from '../../enums/AnalyticalTableSubComponentsBehavior.js'; import { AnalyticalTableVisibleRowCountMode } from '../../enums/AnalyticalTableVisibleRowCountMode.js'; import { COLLAPSE_NODE, COLLAPSE_PRESS_SPACE, DESELECT_ALL, EXPAND_NODE, EXPAND_PRESS_SPACE, FILTERED, GROUPED, HIGHLIGHT_COLUMN, INVALID_TABLE, LIST_NO_DATA, NAVIGATION_COLUMN, NO_DATA_FILTERED, PLEASE_WAIT, ROW_COLLAPSED, ROW_EXPANDED, SELECT_ALL, SELECT_ALL_PRESS_SPACE, SELECT_PRESS_SPACE, SELECTION_COLUMN, UNSELECT_ALL_PRESS_SPACE, UNSELECT_PRESS_SPACE } from '../../i18n/i18n-defaults.js'; import { BusyIndicator } from '../../webComponents/BusyIndicator/index.js'; import { FlexBox } from '../FlexBox/index.js'; import { classNames, styleData } from './AnalyticalTable.module.css.js'; import { ColumnHeaderContainer } from './ColumnHeader/ColumnHeaderContainer.js'; import { DefaultColumn } from './defaults/Column/index.js'; import { TablePlaceholder } from './defaults/LoadingComponent/TablePlaceholder.js'; import { DefaultNoDataComponent } from './defaults/NoDataComponent/index.js'; import { useA11y } from './hooks/useA11y.js'; import { useAutoResize } from './hooks/useAutoResize.js'; import { useCanUseVoiceOver } from './hooks/useCanUseVoiceOver.js'; import { useColumnResizing } from './hooks/useColumnResizing.js'; import { useColumnsDeps } from './hooks/useColumnsDeps.js'; import { useColumnDragAndDrop } from './hooks/useDragAndDrop.js'; import { useDynamicColumnWidths } from './hooks/useDynamicColumnWidths.js'; import { useFontsReady } from './hooks/useFontsReady.js'; import { useIsFirefox } from './hooks/useIsFirefox.js'; import { useKeyboardNavigation } from './hooks/useKeyboardNavigation.js'; import { usePopIn } from './hooks/usePopIn.js'; import { useRowHighlight } from './hooks/useRowHighlight.js'; import { useRowNavigationIndicators } from './hooks/useRowNavigationIndicator.js'; import { useRowSelect } from './hooks/useRowSelect.js'; import { useRowSelectionColumn } from './hooks/useRowSelectionColumn.js'; import { useScrollToRef } from './hooks/useScrollToRef.js'; import { useSelectionChangeCallback } from './hooks/useSelectionChangeCallback.js'; import { useSingleRowStateSelection } from './hooks/useSingleRowStateSelection.js'; import { useStyling } from './hooks/useStyling.js'; import { useSyncScroll } from './hooks/useSyncScroll.js'; import { useToggleRowExpand } from './hooks/useToggleRowExpand.js'; import { useVisibleColumnsWidth } from './hooks/useVisibleColumnsWidth.js'; import { useColumnOrder, useExpanded, useFilters, useGlobalFilter, useGroupBy, useSortBy, useTable } from './react-table/index.js'; import { VerticalScrollbar } from './scrollbars/VerticalScrollbar.js'; import { VirtualTableBody } from './TableBody/VirtualTableBody.js'; import { VirtualTableBodyContainer } from './TableBody/VirtualTableBodyContainer.js'; import { stateReducer } from './tableReducer/stateReducer.js'; import { TitleBar } from './TitleBar/index.js'; import { getCombinedElementsHeight, getRowHeight, getSubRowsByString, tagNamesWhichShouldNotSelectARow } from './util/index.js'; import { VerticalResizer } from './VerticalResizer.js'; // When a sorted column is removed from the visible columns array (e.g. when "popped-in"), it doesn't clean up the sorted columns leading to an undefined `sortType`. import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; const sortTypesFallback = { undefined: () => undefined }; const measureElement = el => { return el.offsetHeight; }; /** * The `AnalyticalTable` provides a set of convenient functions for responsive table design, including virtualization of rows and columns, infinite scrolling and customizable columns that will, unless otherwise defined, distribute the available space equally among themselves. * It also provides several possibilities for working with the data, including sorting, filtering, grouping and aggregation. * * __Note:__ Some features listed below have technical limitations, lack a defined design specification, or should be enabled by default to align with UXC guidelines: * *| Function / Feature | Reason | *|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| *| `useF2CellEdit` plugin hook | To mimic the `sap.ui.table` edit-mode and tabbing behavior, use the `useF2CellEdit` plugin hook. | *| No sticky columns/rows | Not supported due to technical limitations. | *| Pop-in behavior | The `sap.ui.table` doesn’t support pop-in behavior (unlike `sap.m.Table`); it’s unclear whether this should be part of the design. | *| `visibleRowCountMode: "Auto"` | `"AutoWithEmptyRows"` is preferred. `"Auto"` mode can lead to inconsistent table heights depending on the container. | *| `alwaysShowBusyIndicator` | Should generally be `true`, only if loading times are over 1 second, the default skeleton loading indicator is sufficient: [Fiori Skeleton Loading](https://www.sap.com/design-system/fiori-design-ios/ui-elements/patterns/skeleton-loading/?external). | *| `scaleWidthMode` | Only the default mode is available out of the box for the `sap.m.Table`; similar behavior to the `"Grow"` mode can be achieved in `sap.ui.table` using `autoResizeColumn`. | *| `renderRowSubComponent` | There is no design/UX concept for this functionality. | *| `useIndeterminateRowSelection` | There is no design/UX concept for this functionality. | *| `useRowDisableSelection` (deprecated) | Table rows should not be disabled. | * */ const AnalyticalTable = /*#__PURE__*/forwardRef((props, ref) => { const { accessibleName, accessibleNameRef, adjustTableHeightOnPopIn, alternateRowColor, alwaysShowBusyIndicator, className, columnOrder, columns, data: rawData, extension, filterable, globalFilterValue, groupBy, groupable, header, headerRowHeight, highlightField = 'status', infiniteScroll, infiniteScrollThreshold = 20, isTreeTable, loading, loadingDelay, markNavigatedRow, minRows = 5, noDataText, overscanCount, overscanCountHorizontal = 10, retainColumnWidth, reactTableOptions, renderRowSubComponent, rowHeight, scaleWidthMode = AnalyticalTableScaleWidthMode.Default, scaleXFactor, selectedRowIds, selectionBehavior = AnalyticalTableSelectionBehavior.Row, selectionMode = AnalyticalTableSelectionMode.None, showOverlay, sortable, style, subComponentsBehavior = AnalyticalTableSubComponentsBehavior.Expandable, subRowsKey = 'subRows', tableHooks = [], tableInstance, visibleRowCountMode = AnalyticalTableVisibleRowCountMode.Fixed, visibleRows = 15, withNavigationHighlight, withRowHighlight, onColumnsReorder, onGroup, onLoadMore, onRowClick, onRowContextMenu, onRowExpandChange, onRowSelect, onSort, onTableScroll, onAutoResize, onFilter, NoDataComponent = DefaultNoDataComponent, additionalEmptyRowsCount = 0, ...rest } = props; useStylesheet(styleData, AnalyticalTable.displayName); const isInitialized = useRef(false); const fontsReady = useFontsReady(); const isFirefox = useIsFirefox(); const canUseVoiceOver = useCanUseVoiceOver(); const alwaysShowSubComponent = subComponentsBehavior === AnalyticalTableSubComponentsBehavior.Visible || subComponentsBehavior === AnalyticalTableSubComponentsBehavior.IncludeHeight; const uniqueId = useId(); const i18nBundle = useI18nBundle('@ui5/webcomponents-react'); const i18nBundleWc = useI18nBundle('@ui5/webcomponents'); const titleBarId = `titlebar-${uniqueId}`; const invalidTableTextId = `invalidTableText-${uniqueId}`; const cellSelectDescId = `cell-select-${uniqueId}`; const cellUnselectDescId = `cell-unselect-${uniqueId}`; const cellExpandDescId = `cell-expand-${uniqueId}`; const cellCollapseDescId = `cell-collapse-${uniqueId}`; const cellEmptyDescId = `cell-empty-${uniqueId}`; const headerSelectAllDescId = `header-select-all-${uniqueId}`; const headerDeselectAllDescId = `header-deselect-all-${uniqueId}`; const tableRef = useRef(null); const parentRef = useRef(null); const verticalScrollBarRef = useRef(null); const getSubRows = useCallback(row => getSubRowsByString(subRowsKey, row) || [], [subRowsKey]); const invalidTableA11yText = i18nBundle.getText(INVALID_TABLE); const tableInstanceRef = useRef(null); const scrollContainerRef = useRef(null); const dedupedOnFilter = useMemo(() => typeof onFilter === 'function' ? debounce(onFilter, 0) : undefined, [onFilter]); useEffect(() => () => { dedupedOnFilter?.cancel(); }, [dedupedOnFilter]); tableInstanceRef.current = useTable({ columns, data: rawData, defaultColumn: DefaultColumn, getSubRows, stateReducer, disableFilters: !filterable, disableSortBy: !sortable, disableGroupBy: isTreeTable || !alwaysShowSubComponent && renderRowSubComponent ? true : !groupable, selectSubRows: false, sortTypes: sortTypesFallback, webComponentsReactProperties: { a11yElementIds: { cellSelectDescId, cellUnselectDescId, cellExpandDescId, cellCollapseDescId, cellEmptyDescId, headerSelectAllDescId, headerDeselectAllDescId }, translatableTexts: { selectAllText: i18nBundle.getText(SELECT_ALL), deselectAllText: i18nBundle.getText(DESELECT_ALL), expandNodeA11yText: i18nBundle.getText(EXPAND_NODE), collapseNodeA11yText: i18nBundle.getText(COLLAPSE_NODE), filteredA11yText: i18nBundle.getText(FILTERED), groupedA11yText: i18nBundle.getText(GROUPED), selectAllA11yText: i18nBundle.getText(SELECT_ALL_PRESS_SPACE), deselectAllA11yText: i18nBundle.getText(UNSELECT_ALL_PRESS_SPACE), rowExpandedAnnouncementText: i18nBundle.getText(ROW_EXPANDED), rowCollapsedAnnouncementText: i18nBundle.getText(ROW_COLLAPSED), selectionHeaderCellText: i18nBundle.getText(SELECTION_COLUMN), highlightHeaderCellText: i18nBundle.getText(HIGHLIGHT_COLUMN), navigationHeaderCellText: i18nBundle.getText(NAVIGATION_COLUMN) }, alternateRowColor, alwaysShowSubComponent, canUseVoiceOver, classes: classNames, fontsReady, highlightField, isFirefox, isTreeTable, loading, markNavigatedRow, renderRowSubComponent, scaleWidthMode, selectionBehavior, selectionMode, showOverlay, subRowsKey, tableRef, tagNamesWhichShouldNotSelectARow, uniqueId, withNavigationHighlight, withRowHighlight, onAutoResize, onColumnsReorder, onGroup, onRowClick, onRowContextMenu, onRowExpandChange, onRowSelect, onSort, onFilter: dedupedOnFilter }, ...reactTableOptions }, useFilters, useGlobalFilter, useColumnOrder, useGroupBy, useSortBy, useExpanded, useRowSelect, useColumnResizing, useColumnsDeps, useRowSelectionColumn, useAutoResize, useSingleRowStateSelection, useSelectionChangeCallback, useRowHighlight, useRowNavigationIndicators, useDynamicColumnWidths, useStyling, useToggleRowExpand, useA11y, usePopIn, useVisibleColumnsWidth, useKeyboardNavigation, useColumnDragAndDrop, ...tableHooks); const { getTableProps, headerGroups, rows, prepareRow, setColumnOrder, dispatch, totalColumnsWidth, visibleColumns, visibleColumnsWidth, setGroupBy, setGlobalFilter } = tableInstanceRef.current; useEffect(() => { if (process.env.NODE_ENV === 'development' && headerGroups.length > 1) { console.error('[AnalyticalTable] Nested column headers are not supported. Please flatten your column definitions.'); } }, [headerGroups.length]); const tableState = tableInstanceRef.current.state; const { popInColumns, triggerScroll } = tableState; const isGrouped = !!tableState.groupBy.length; const noDataTextI18n = i18nBundle.getText(LIST_NO_DATA); const noDataTextFiltered = i18nBundle.getText(NO_DATA_FILTERED); const noDataFiltered = tableState.filters?.length > 0 || tableState.globalFilter; const noDataTextLocal = noDataText ?? (noDataFiltered ? noDataTextFiltered : noDataTextI18n); const [componentRef, analyticalTableRef] = useSyncRef(ref); const [cbRef, scrollToRef] = useScrollToRef(componentRef, dispatch); // @ts-expect-error: is HTMLElement const isRtl = useIsRTL(analyticalTableRef); const columnVirtualizer = useVirtualizer({ count: visibleColumnsWidth.length, getScrollElement: () => tableRef.current, estimateSize: useCallback(index => visibleColumnsWidth[index], [visibleColumnsWidth]), horizontal: true, overscan: isRtl || scaleWidthMode !== AnalyticalTableScaleWidthMode.Default ? Infinity : overscanCountHorizontal, indexAttribute: 'data-column-index', // necessary as otherwise values are rounded which leads to wrong total width calculation leading to unnecessary scrollbar measureElement: !scaleXFactor || scaleXFactor === 1 ? el => el.getBoundingClientRect().width : undefined }); // force re-measure if `visibleColumns` change useEffect(() => { if (isInitialized.current && visibleColumns.length) { columnVirtualizer.measure(); } else { isInitialized.current = true; } }, [visibleColumns.length]); // force re-measure if `state.groupBy` or `state.columnOrder` changes useEffect(() => { if (isInitialized.current && (tableState.groupBy || tableState.columnOrder)) { setTimeout(() => { columnVirtualizer.measure(); }, 100); } else { isInitialized.current = true; } }, [tableState.groupBy, tableState.columnOrder]); useEffect(() => { if (triggerScroll && triggerScroll.direction === 'horizontal') { if (triggerScroll.type === 'offset') { columnVirtualizer.scrollToOffset(...triggerScroll.args); } else { columnVirtualizer.scrollToIndex(...triggerScroll.args); } } }, [columnVirtualizer, triggerScroll]); const includeSubCompRowHeight = !!renderRowSubComponent && (subComponentsBehavior === AnalyticalTableSubComponentsBehavior.IncludeHeight || subComponentsBehavior === AnalyticalTableSubComponentsBehavior.IncludeHeightExpandable) && !!tableState.subComponentsHeight && !!Object.keys(tableState.subComponentsHeight); if (tableInstance && {}.hasOwnProperty.call(tableInstance, 'current')) { tableInstance.current = tableInstanceRef.current; } if (typeof tableInstance === 'function') { tableInstance(tableInstanceRef.current); } const prevExtensionsHeight = useRef(0); const titleBarRef = useRef(null); const extensionRef = useRef(null); const headerRef = useRef(null); const extensionsHeight = getCombinedElementsHeight(prevExtensionsHeight, titleBarRef, extensionRef, headerRef); const internalRowHeight = getRowHeight(rowHeight, tableRef); const internalHeaderRowHeight = headerRowHeight ?? internalRowHeight; const popInRowHeight = (() => { if (popInColumns?.length) { return popInColumns.reduce((acc, cur) => cur.popinDisplay === AnalyticalTablePopinDisplay.Block ? acc + internalRowHeight + 16 // 16px for Header : acc + internalRowHeight, internalRowHeight); } else { return internalRowHeight; } })(); const internalVisibleRowCount = tableState.visibleRows ?? visibleRows; const updateTableClientWidth = useCallback(() => { if (tableRef.current) { dispatch({ type: 'TABLE_RESIZE', payload: { tableClientWidth: !scaleXFactor || scaleXFactor === 1 ? tableRef.current.getBoundingClientRect().width : tableRef.current.clientWidth } }); } }, [dispatch, scaleXFactor]); const updateRowsCount = useCallback(() => { if ((visibleRowCountMode === AnalyticalTableVisibleRowCountMode.Auto || visibleRowCountMode === AnalyticalTableVisibleRowCountMode.AutoWithEmptyRows) && analyticalTableRef.current?.parentElement) { const parentElement = analyticalTableRef.current?.parentElement; const tableYPosition = parentElement && getComputedStyle(parentElement).position === 'relative' && analyticalTableRef.current?.offsetTop ? analyticalTableRef.current?.offsetTop : 0; const parentHeight = parentElement?.getBoundingClientRect().height; const tableHeight = parentHeight ? parentHeight - tableYPosition : 0; const bodyHeight = tableHeight - extensionsHeight; let subCompsRowCount = 0; if (includeSubCompRowHeight) { let localBodyHeight = 0; let i = 0; while (localBodyHeight < bodyHeight) { if (tableState.subComponentsHeight[i]) { localBodyHeight += tableState.subComponentsHeight[i].subComponentHeight + popInRowHeight; } else if (rows[i]) { localBodyHeight += popInRowHeight; } else { break; } if (localBodyHeight >= bodyHeight) { break; } subCompsRowCount++; i++; } dispatch({ type: 'VISIBLE_ROWS', payload: { visibleRows: Math.max(1, subCompsRowCount) } }); } else { const rowCount = Math.max(1, Math.floor(bodyHeight / popInRowHeight)); dispatch({ type: 'VISIBLE_ROWS', payload: { visibleRows: rowCount } }); } } }, [extensionsHeight, popInRowHeight, visibleRowCountMode, includeSubCompRowHeight, tableState.subComponentsHeight]); useEffect(() => { setGlobalFilter(globalFilterValue); }, [globalFilterValue, setGlobalFilter]); useEffect(() => { const debouncedWidthObserverFn = debounce(updateTableClientWidth, 60); const tableWidthObserver = new ResizeObserver(debouncedWidthObserverFn); tableWidthObserver.observe(tableRef.current); const debouncedHeightObserverFn = debounce(updateRowsCount, 60); const parentHeightObserver = new ResizeObserver(debouncedHeightObserverFn); if (analyticalTableRef.current?.parentElement) { parentHeightObserver.observe(analyticalTableRef.current?.parentElement); } return () => { debouncedHeightObserverFn.cancel(); debouncedWidthObserverFn.cancel(); tableWidthObserver.disconnect(); parentHeightObserver.disconnect(); }; }, [updateTableClientWidth, updateRowsCount, analyticalTableRef]); useIsomorphicLayoutEffect(() => { dispatch({ type: 'IS_RTL', payload: { isRtl } }); }, [isRtl]); useIsomorphicLayoutEffect(() => { updateTableClientWidth(); }, [updateTableClientWidth]); useIsomorphicLayoutEffect(() => { updateRowsCount(); }, [updateRowsCount]); useEffect(() => { if (tableState.visibleRows !== undefined && visibleRowCountMode === AnalyticalTableVisibleRowCountMode.Fixed) { dispatch({ type: 'VISIBLE_ROWS', payload: { visibleRows: undefined } }); } }, [visibleRowCountMode, tableState.visibleRows, dispatch]); useEffect(() => { if (groupBy) { setGroupBy(groupBy); } }, [groupBy, setGroupBy]); useEffect(() => { if (selectedRowIds) { dispatch({ type: 'SET_SELECTED_ROW_IDS', payload: { selectedRowIds } }); } }, [dispatch, selectedRowIds]); const tableBodyHeight = useMemo(() => { if (typeof tableState.bodyHeight === 'number') { return tableState.bodyHeight; } let rowNum; const noDataAuto = !rows.length && visibleRowCountMode.startsWith('Auto'); if (visibleRowCountMode === AnalyticalTableVisibleRowCountMode.AutoWithEmptyRows || noDataAuto) { rowNum = internalVisibleRowCount; } else { rowNum = rows.length < internalVisibleRowCount ? Math.max(rows.length, minRows) : internalVisibleRowCount; } const rowHeight = visibleRowCountMode === AnalyticalTableVisibleRowCountMode.Auto || visibleRowCountMode === AnalyticalTableVisibleRowCountMode.AutoWithEmptyRows || tableState.interactiveRowsHavePopIn || adjustTableHeightOnPopIn ? popInRowHeight : internalRowHeight; if (includeSubCompRowHeight) { let initialBodyHeightWithSubComps = 0; for (let i = 0; i < rowNum; i++) { if (tableState.subComponentsHeight[i]) { initialBodyHeightWithSubComps += tableState.subComponentsHeight[i].subComponentHeight + rowHeight; } else { initialBodyHeightWithSubComps += rowHeight; } } return initialBodyHeightWithSubComps; } return rowHeight * rowNum; }, [internalRowHeight, rows.length, internalVisibleRowCount, minRows, popInRowHeight, visibleRowCountMode, tableState.interactiveRowsHavePopIn, adjustTableHeightOnPopIn, includeSubCompRowHeight, tableState.subComponentsHeight, tableState.bodyHeight]); // scroll bar detection useEffect(() => { const visibleRowCount = rows.length < internalVisibleRowCount ? Math.max(rows.length, minRows) : internalVisibleRowCount; if (popInRowHeight !== internalRowHeight) { dispatch({ type: 'TABLE_SCROLLING_ENABLED', payload: { isScrollable: visibleRowCount * popInRowHeight > tableBodyHeight || rows.length > visibleRowCount } }); } else { dispatch({ type: 'TABLE_SCROLLING_ENABLED', payload: { isScrollable: rows.length > visibleRowCount } }); } }, [rows.length, minRows, internalVisibleRowCount, popInRowHeight, tableBodyHeight]); const noDataStyles = { height: `${tableBodyHeight}px`, width: totalColumnsWidth ? `${totalColumnsWidth}px` : '100%' }; useEffect(() => { if (columnOrder?.length > 0) { setColumnOrder(columnOrder); } }, [columnOrder]); const inlineStyle = useMemo(() => { const tableStyles = { maxWidth: '100%', overflowX: 'auto', display: 'flex', flexDirection: 'column' }; if (rowHeight) { tableStyles['--_ui5wcr-AnalyticalTableRowHeight'] = `${rowHeight}px`; tableStyles['--_ui5wcr-AnalyticalTableHeaderRowHeight'] = `${rowHeight}px`; } if (headerRowHeight) { tableStyles['--_ui5wcr-AnalyticalTableHeaderRowHeight'] = `${headerRowHeight}px`; } if (tableState.tableClientWidth > 0) { return { ...tableStyles, ...style }; } return { ...tableStyles, ...style, visibility: 'hidden' }; }, [tableState.tableClientWidth, style, rowHeight, headerRowHeight]); useEffect(() => { if (retainColumnWidth && tableState.columnResizing?.isResizingColumn && tableState.tableColResized == null) { dispatch({ type: 'TABLE_COL_RESIZED', payload: true }); } if (tableState.tableColResized && !retainColumnWidth) { dispatch({ type: 'TABLE_COL_RESIZED', payload: undefined }); } }, [tableState.columnResizing, retainColumnWidth, tableState.tableColResized]); useSyncScroll(parentRef, verticalScrollBarRef, tableState.isScrollable, isFirefox); useEffect(() => { columnVirtualizer.measure(); }, [columnVirtualizer, tableState.columnOrder, tableState.columnResizing?.isResizingColumn, columns, tableState.groupBy]); const totalSize = columnVirtualizer.getTotalSize(); const showVerticalEndBorder = tableState.tableClientWidth > totalSize; const tableClasses = clsx(classNames.table, withNavigationHighlight && classNames.hasNavigationIndicator, showVerticalEndBorder && classNames.showVerticalEndBorder); const handleOnLoadMore = e => { const rootNodes = rows.filter(row => row.depth === 0); onLoadMore(enrichEventWithDetails(e, { rowCount: rootNodes.length, totalRowCount: rows.length })); }; const overscan = overscanCount ? overscanCount : Math.floor(visibleRows / 2); const rHeight = popInRowHeight !== internalRowHeight ? popInRowHeight : internalRowHeight; const itemCount = Math.max(minRows, rows.length, visibleRowCountMode === AnalyticalTableVisibleRowCountMode.AutoWithEmptyRows ? internalVisibleRowCount : 0) + (!tableState.isScrollable ? additionalEmptyRowsCount : 0); const rowVirtualizer = useVirtualizer({ count: itemCount, getScrollElement: () => parentRef.current, estimateSize: useCallback(index => { if (renderRowSubComponent && (rows[index]?.isExpanded || alwaysShowSubComponent) && tableState.subComponentsHeight?.[index]?.rowId === rows[index]?.id) { return rHeight + (tableState.subComponentsHeight?.[index]?.subComponentHeight ?? 0); } return rHeight; }, [rHeight, rows, renderRowSubComponent, alwaysShowSubComponent, tableState.subComponentsHeight]), overscan, measureElement, indexAttribute: 'data-virtual-row-index', useAnimationFrameWithResizeObserver: true }); // Re-sync the virtualizer's cached `scrollOffset` with the DOM after data swaps that clamp `scrollTop` without firing a scroll event in the same React batch. useIsomorphicLayoutEffect(() => { const scrollElement = parentRef.current; if (scrollElement && rowVirtualizer.scrollOffset !== null && rowVirtualizer.scrollOffset !== scrollElement.scrollTop) { // Defer to a microtask so the scroll listener's `flushSync(rerender)` doesn't run inside this commit. queueMicrotask(() => scrollElement.dispatchEvent(new Event('scroll'))); } }, [itemCount, rows.length]); // add range to instance for `useAutoResize` plugin hook tableInstanceRef.current.virtualRowsRange = rowVirtualizer.range; if (parentRef.current) { scrollToRef.current = { ...scrollToRef.current, horizontalScrollToOffset: columnVirtualizer.scrollToOffset, horizontalScrollToIndex: columnVirtualizer.scrollToIndex, scrollToOffset: rowVirtualizer.scrollToOffset, scrollToIndex: rowVirtualizer.scrollToIndex }; } return /*#__PURE__*/_jsxs(_Fragment, { children: [/*#__PURE__*/_jsxs("div", { className: className, style: inlineStyle //@ts-expect-error: types are compatible , ref: cbRef, ...rest, children: [header && /*#__PURE__*/_jsx(TitleBar, { ref: titleBarRef, titleBarId: titleBarId, children: header }), extension && /*#__PURE__*/_jsx("div", { ref: extensionRef, children: extension }), /*#__PURE__*/_jsxs(FlexBox, { className: classNames.tableContainerWithScrollBar, "data-component-name": "AnalyticalTableContainerWithScrollbar", children: [loading && (!!rows.length || alwaysShowBusyIndicator) && /*#__PURE__*/_jsx(BusyIndicator, { className: classNames.busyIndicator, active: true, delay: loadingDelay, "data-component-name": "AnalyticalTableBusyIndicator" }), showOverlay && /*#__PURE__*/_jsxs(_Fragment, { children: [/*#__PURE__*/_jsx("span", { id: invalidTableTextId, className: classNames.hiddenA11yText, "aria-hidden": "true", children: invalidTableA11yText }), /*#__PURE__*/_jsx("div", { tabIndex: 0, "aria-labelledby": `${accessibleNameRef ?? titleBarId} ${invalidTableTextId}`, role: "region", "data-component-name": "AnalyticalTableOverlay", className: classNames.overlay })] }), /*#__PURE__*/_jsxs("div", { "aria-label": accessibleName, "aria-labelledby": accessibleNameRef ?? (header && !accessibleName ? titleBarId : undefined), ...getTableProps(), tabIndex: loading || showOverlay ? -1 : 0, role: isTreeTable ? 'treegrid' : 'grid', "aria-rowcount": rows.length, "aria-colcount": visibleColumns.length, "data-per-page": internalVisibleRowCount, "aria-multiselectable": selectionMode === AnalyticalTableSelectionMode.Multiple, "data-component-name": "AnalyticalTableContainer", ref: tableRef, className: tableClasses, children: [/*#__PURE__*/_jsx("div", { className: classNames.tableHeaderBackgroundElement, "aria-hidden": "true" }), /*#__PURE__*/_jsx("div", { className: classNames.tableBodyBackgroundElement, "aria-hidden": "true" }), headerGroups.map(headerGroup => { let headerProps = {}; if (headerGroup.getHeaderGroupProps) { headerProps = headerGroup.getHeaderGroupProps(); } return tableRef.current && /*#__PURE__*/_jsx(ColumnHeaderContainer, { ref: headerRef, resizeInfo: tableState.columnResizing, headerProps: headerProps, headerGroup: headerGroup, isRtl: isRtl, columnVirtualizer: columnVirtualizer, uniqueId: uniqueId, showVerticalEndBorder: showVerticalEndBorder, classNames: classNames }, headerProps.key); }), rows?.length === 0 && /*#__PURE__*/_jsx("div", { style: noDataStyles, "data-component-name": "AnalyticalTableNoDataContainer", role: "row", tabIndex: 0, className: classNames.noDataContainer, children: loading && !alwaysShowBusyIndicator ? /*#__PURE__*/_jsx(TablePlaceholder, { columns: visibleColumns, rows: minRows, style: noDataStyles, pleaseWaitText: i18nBundle.getText(PLEASE_WAIT) }) : /*#__PURE__*/_jsx(NoDataComponent, { noDataText: noDataTextLocal, className: classNames.noData, noDataReason: noDataFiltered ? AnalyticalTableNoDataReason.Filtered : AnalyticalTableNoDataReason.Empty, accessibleRole: "gridcell" }) }), rows?.length > 0 && tableRef.current && /*#__PURE__*/_jsx(VirtualTableBodyContainer, { rowCollapsedFlag: tableState.rowCollapsed, dispatch: dispatch, tableBodyHeight: tableBodyHeight, totalColumnsWidth: columnVirtualizer.getTotalSize(), parentRef: parentRef, classes: classNames, infiniteScroll: infiniteScroll, infiniteScrollThreshold: infiniteScrollThreshold, onLoadMore: handleOnLoadMore, internalRowHeight: internalRowHeight, popInRowHeight: popInRowHeight, rows: rows, handleExternalScroll: onTableScroll, visibleRows: internalVisibleRowCount, isGrouped: isGrouped, isFirefox: isFirefox, children: /*#__PURE__*/_jsx(VirtualTableBody, { scrollContainerRef: scrollContainerRef, classes: classNames, prepareRow: prepareRow, rows: rows, isTreeTable: isTreeTable, internalRowHeight: internalRowHeight, popInRowHeight: popInRowHeight, alternateRowColor: alternateRowColor, visibleColumns: visibleColumns, renderRowSubComponent: renderRowSubComponent, alwaysShowSubComponent: alwaysShowSubComponent, markNavigatedRow: markNavigatedRow, isRtl: isRtl, subComponentsHeight: tableState.subComponentsHeight, dispatch: dispatch, columnVirtualizer: columnVirtualizer, manualGroupBy: reactTableOptions?.manualGroupBy, subRowsKey: subRowsKey, triggerScroll: tableState.triggerScroll, rowVirtualizer: rowVirtualizer }) })] }), !isFirefox && (additionalEmptyRowsCount || tableState.isScrollable) && /*#__PURE__*/_jsx(VerticalScrollbar, { tableBodyHeight: tableBodyHeight, internalRowHeight: internalHeaderRowHeight, tableRef: tableRef, ref: verticalScrollBarRef, scrollContainerRef: scrollContainerRef, classNames: classNames })] }), visibleRowCountMode === AnalyticalTableVisibleRowCountMode.Interactive && /*#__PURE__*/_jsx(VerticalResizer, { popInRowHeight: popInRowHeight, hasPopInColumns: tableState?.popInColumns?.length > 0, analyticalTableRef: analyticalTableRef, dispatch: dispatch, extensionsHeight: extensionsHeight, rowsLength: rows.length, visibleRows: internalVisibleRowCount, handleOnLoadMore: handleOnLoadMore, classNames: classNames })] }), /*#__PURE__*/_jsx("span", { id: cellSelectDescId, className: classNames.hiddenA11yText, children: i18nBundle.getText(SELECT_PRESS_SPACE) }), /*#__PURE__*/_jsx("span", { id: cellUnselectDescId, className: classNames.hiddenA11yText, children: i18nBundle.getText(UNSELECT_PRESS_SPACE) }), /*#__PURE__*/_jsx("span", { id: headerSelectAllDescId, className: classNames.hiddenA11yText, children: i18nBundle.getText(SELECT_ALL_PRESS_SPACE) }), /*#__PURE__*/_jsx("span", { id: headerDeselectAllDescId, className: classNames.hiddenA11yText, children: i18nBundle.getText(UNSELECT_ALL_PRESS_SPACE) }), /*#__PURE__*/_jsx("span", { id: cellExpandDescId, className: classNames.hiddenA11yText, children: i18nBundle.getText(EXPAND_PRESS_SPACE) }), /*#__PURE__*/_jsx("span", { id: cellCollapseDescId, className: classNames.hiddenA11yText, children: i18nBundle.getText(COLLAPSE_PRESS_SPACE) }), /*#__PURE__*/_jsx("span", { id: cellEmptyDescId, className: classNames.hiddenA11yText, children: i18nBundleWc.getText(ARIA_LABEL_EMPTY_CELL) })] }); }); AnalyticalTable.displayName = 'AnalyticalTable'; export { AnalyticalTable };