diff --git a/packages/pxweb2-ui/src/lib/components/Table/Table.spec.tsx b/packages/pxweb2-ui/src/lib/components/Table/Table.spec.tsx
index 9aa13c68c..5503f9f02 100644
--- a/packages/pxweb2-ui/src/lib/components/Table/Table.spec.tsx
+++ b/packages/pxweb2-ui/src/lib/components/Table/Table.spec.tsx
@@ -1,5 +1,63 @@
+import React from 'react';
import { render } from '@testing-library/react';
-import { describe, it, expect } from 'vitest';
+import { describe, it, expect, vi } from 'vitest';
+
+type VirtualRowMock = {
+ className?: string;
+ cells: React.ReactNode;
+};
+
+type TableVirtuosoMockProps = {
+ data?: VirtualRowMock[];
+ fixedHeaderContent?: () => React.ReactNode;
+ itemContent: (index: number, item: VirtualRowMock) => React.ReactNode;
+};
+
+function getRowKey(item: VirtualRowMock, index: number): string {
+ if (Array.isArray(item.cells)) {
+ const elementKeys = item.cells
+ .map((cell) => {
+ if (typeof cell !== 'object' || cell === null) {
+ return '';
+ }
+ if ('key' in cell) {
+ return String((cell as React.ReactElement).key);
+ }
+ return '';
+ })
+ .join('-');
+ if (elementKeys.length > 0) {
+ return elementKeys;
+ }
+ }
+
+ if (React.isValidElement(item.cells) && item.cells.key) {
+ return String(item.cells.key);
+ }
+
+ if (item.className && item.className.length > 0) {
+ return item.className + '-' + String(index);
+ }
+
+ return 'row-' + String(index);
+}
+
+vi.mock('react-virtuoso', () => ({
+ TableVirtuoso: ({
+ data = [],
+ fixedHeaderContent,
+ itemContent,
+ }: TableVirtuosoMockProps) => (
+
+ {fixedHeaderContent?.()}
+
+ {data.map((item, index) => (
+ {itemContent(index, item)}
+ ))}
+
+
+ ),
+}));
import Table from './Table';
import { pxTable } from './testData';
diff --git a/packages/pxweb2-ui/src/lib/components/Table/Table.tsx b/packages/pxweb2-ui/src/lib/components/Table/Table.tsx
index 37a77140c..60605c53a 100644
--- a/packages/pxweb2-ui/src/lib/components/Table/Table.tsx
+++ b/packages/pxweb2-ui/src/lib/components/Table/Table.tsx
@@ -1,5 +1,6 @@
-import { memo, useMemo } from 'react';
+import { memo, useLayoutEffect, useMemo, useRef, useState } from 'react';
import cl from 'clsx';
+import { TableComponents, TableVirtuoso } from 'react-virtuoso';
import classes from './Table.module.scss';
import { PxTable } from '../../shared-types/pxTable';
@@ -56,13 +57,62 @@ interface CreateRowMobileParams {
*/
type DataCellCodes = DataCellMeta[];
+type VirtualTableRow = {
+ className?: string;
+ cells: React.ReactNode;
+};
+
+type VirtualTableContext = {
+ tableClassName: string;
+ tableLabel: string;
+};
+
+const tableVirtuosoComponents: TableComponents<
+ VirtualTableRow,
+ VirtualTableContext
+> = {
+ Table: ({ children, style, context }) => (
+
+ ),
+ TableRow: ({ item, ...props }) => (
+
+ ),
+};
+
export const Table = memo(function Table({
pxtable,
isMobile,
className = '',
}: TableProps) {
+ const tableHostRef = useRef(null);
+ const [customScrollParent, setCustomScrollParent] =
+ useState(null);
+
const cssClasses = className.length > 0 ? ' ' + className : '';
+ useLayoutEffect(() => {
+ let parent = tableHostRef.current?.parentElement ?? null;
+ while (parent) {
+ const style = window.getComputedStyle(parent);
+ const hasScrollableOverflow = /(auto|scroll|overlay)/.test(
+ style.overflowY,
+ );
+ if (hasScrollableOverflow && parent.scrollHeight > parent.clientHeight) {
+ setCustomScrollParent(parent);
+ return;
+ }
+ parent = parent.parentElement;
+ }
+
+ setCustomScrollParent(null);
+ }, [pxtable, isMobile]);
+
const tableMeta: columnRowMeta = calculateRowAndColumnMeta(pxtable);
const tableColumnSize: number = tableMeta.columns - tableMeta.columnOffset;
@@ -115,34 +165,57 @@ export const Table = memo(function Table({
headingDataCellCodes[i] = dataCellCodes;
}
+ const headingRows = useMemo(
+ () => createHeading(pxtable, tableMeta, headingDataCellCodes),
+ [pxtable, tableMeta, headingDataCellCodes],
+ );
+
+ const tableRows = useMemo(
+ () =>
+ createRows(
+ pxtable,
+ tableMeta,
+ headingDataCellCodes,
+ isMobile,
+ contentVarIndex,
+ contentsVariableDecimals,
+ ),
+ [
+ pxtable,
+ tableMeta,
+ headingDataCellCodes,
+ isMobile,
+ contentVarIndex,
+ contentsVariableDecimals,
+ ],
+ );
+
+ const rowData = useMemo(
+ () =>
+ tableRows.map((row) => ({
+ className: row.props.className,
+ cells: row.props.children,
+ })),
+ [tableRows],
+ );
+
return (
-
- {createHeading(pxtable, tableMeta, headingDataCellCodes)}
-
- {useMemo(
- () =>
- createRows(
- pxtable,
- tableMeta,
- headingDataCellCodes,
- isMobile,
- contentVarIndex,
- contentsVariableDecimals,
- ),
- [
- pxtable,
- tableMeta,
- headingDataCellCodes,
- isMobile,
- contentVarIndex,
- contentsVariableDecimals,
- ],
- )}
-
-
+
+
headingRows}
+ itemContent={(_index, row) => row.cells}
+ computeItemKey={(index) => index.toString()}
+ />
+
);
});