diff --git a/docs/config.json b/docs/config.json index e0da28e51b..0b68b8d81e 100644 --- a/docs/config.json +++ b/docs/config.json @@ -602,10 +602,18 @@ "to": "framework/react/examples/virtualized-columns", "label": "Virtualized Columns" }, + { + "to": "framework/react/examples/virtualized-columns-experimental", + "label": "Virtualized Columns (Experimental)" + }, { "to": "framework/react/examples/virtualized-rows", "label": "Virtualized Rows" }, + { + "to": "framework/react/examples/virtualized-rows-experimental", + "label": "Virtualized Rows (Experimental)" + }, { "to": "framework/react/examples/virtualized-infinite-scrolling", "label": "Virtualized Infinite Scrolling" diff --git a/examples/react/virtualized-columns-expiremental/.gitignore b/examples/react/virtualized-columns-expiremental/.gitignore new file mode 100644 index 0000000000..d451ff16c1 --- /dev/null +++ b/examples/react/virtualized-columns-expiremental/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local diff --git a/examples/react/virtualized-columns-expiremental/README.md b/examples/react/virtualized-columns-expiremental/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/react/virtualized-columns-expiremental/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/react/virtualized-columns-expiremental/index.html b/examples/react/virtualized-columns-expiremental/index.html new file mode 100644 index 0000000000..fa04f89341 --- /dev/null +++ b/examples/react/virtualized-columns-expiremental/index.html @@ -0,0 +1,14 @@ + + + + + + Vite App + + + +
+ + + + diff --git a/examples/react/virtualized-columns-expiremental/package.json b/examples/react/virtualized-columns-expiremental/package.json new file mode 100644 index 0000000000..c9d262a0ca --- /dev/null +++ b/examples/react/virtualized-columns-expiremental/package.json @@ -0,0 +1,26 @@ +{ + "name": "tanstack-table-example-virtualized-columns-experimental", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite" + }, + "dependencies": { + "@faker-js/faker": "^8.4.1", + "@tanstack/react-table": "^8.20.6", + "@tanstack/react-virtual": "^3.12.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@rollup/plugin-replace": "^5.0.7", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "typescript": "5.4.5", + "vite": "^5.3.2" + } +} diff --git a/examples/react/virtualized-columns-expiremental/src/index.css b/examples/react/virtualized-columns-expiremental/src/index.css new file mode 100644 index 0000000000..98d667b225 --- /dev/null +++ b/examples/react/virtualized-columns-expiremental/src/index.css @@ -0,0 +1,53 @@ +:root { + --virtual-padding-left: 0px; + --virtual-padding-right: 0px; +} + +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border-collapse: collapse; + border-spacing: 0; + font-family: arial, sans-serif; + table-layout: fixed; +} + +thead { + background: lightgray; +} + +tr { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; + text-align: left; +} + +td { + padding: 6px; +} + +.container { + border: 1px solid lightgray; + margin: 1rem auto; +} + +.app { + margin: 1rem auto; + text-align: center; +} + +.left-column-spacer { + width: var(--virtual-padding-left); +} + +.right-column-spacer { + width: var(--virtual-padding-right); +} diff --git a/examples/react/virtualized-columns-expiremental/src/main.tsx b/examples/react/virtualized-columns-expiremental/src/main.tsx new file mode 100644 index 0000000000..b148f30f5a --- /dev/null +++ b/examples/react/virtualized-columns-expiremental/src/main.tsx @@ -0,0 +1,370 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import './index.css' +import { + Cell, + ColumnDef, + Header, + HeaderGroup, + Row, + Table, + flexRender, + getCoreRowModel, + getSortedRowModel, + useReactTable, +} from '@tanstack/react-table' +import { useVirtualizer, Virtualizer } from '@tanstack/react-virtual' +import { makeColumns, makeData, Person } from './makeData' + +// All important CSS styles are included as inline styles for this example. This is not recommended for your code. +function App() { + const columns = React.useMemo[]>( + () => makeColumns(1_000), + [] + ) + + const [data, setData] = React.useState(() => makeData(1_000, columns)) + + const refreshData = React.useCallback(() => { + setData(makeData(1_000, columns)) + }, [columns]) + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + debugTable: true, + }) + + return ( +
+ {process.env.NODE_ENV === 'development' ? ( +

+ Notice: You are currently running React in + development mode. Virtualized rendering performance will be slightly + degraded until this application is built for production. +

+ ) : null} +
({columns.length.toLocaleString()} columns)
+
({data.length.toLocaleString()} rows)
+ + +
+ ) +} + +interface TableContainerProps { + table: Table +} + +function TableContainer({ table }: TableContainerProps) { + const visibleColumns = table.getVisibleLeafColumns() + + //The virtualizers need to know the scrollable container element + const tableContainerRef = React.useRef(null) + + //we are using a slightly different virtualization strategy for columns (compared to virtual rows) in order to support dynamic row heights + const columnVirtualizer = useVirtualizer< + HTMLDivElement, + HTMLTableCellElement + >({ + count: visibleColumns.length, + estimateSize: index => visibleColumns[index].getSize(), //estimate width of each column for accurate scrollbar dragging + getScrollElement: () => tableContainerRef.current, + horizontal: true, + overscan: 3, //how many columns to render on each side off screen each way (adjust this for performance) + onChange: instance => { + const virtualColumns = instance.getVirtualItems() + // different virtualization strategy for columns - instead of absolute and translateY, we add empty columns to the left and right + const virtualPaddingLeft = virtualColumns[0]?.start ?? 0 + const virtualPaddingRight = + instance.getTotalSize() - + (virtualColumns[virtualColumns.length - 1]?.end ?? 0) + + document.documentElement.style.setProperty( + '--virtual-padding-left', + `${virtualPaddingLeft}px` + ) + document.documentElement.style.setProperty( + '--virtual-padding-right', + `${virtualPaddingRight}px` + ) + }, + }) + + return ( +
+ {/* Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights */} + + + +
+
+ ) +} + +interface TableHeadProps { + columnVirtualizer: Virtualizer + table: Table +} + +function TableHead({ table, columnVirtualizer }: TableHeadProps) { + return ( + + {table.getHeaderGroups().map(headerGroup => ( + + ))} + + ) +} + +interface TableHeadRowProps { + columnVirtualizer: Virtualizer + headerGroup: HeaderGroup +} + +function TableHeadRow({ columnVirtualizer, headerGroup }: TableHeadRowProps) { + const virtualColumnIndexes = columnVirtualizer.getVirtualIndexes() + + return ( + + {/* fake empty column to the left for virtualization scroll padding */} + + {virtualColumnIndexes.map(virtualColumnIndex => { + const header = headerGroup.headers[virtualColumnIndex] + return ( + + ) + })} + {/* fake empty column to the right for virtualization scroll padding */} + + + ) +} + +interface TableHeadCellProps { + columnVirtualizer: Virtualizer + header: Header +} + +function TableHeadCell({ + columnVirtualizer: _columnVirtualizer, + header, +}: TableHeadCellProps) { + return ( + +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ + ) +} + +const TableHeadCellMemo = React.memo( + TableHeadCell, + (_prev, next) => next.columnVirtualizer.isScrolling +) as typeof TableHeadCell + +interface TableBodyProps { + columnVirtualizer: Virtualizer + table: Table + tableContainerRef: React.RefObject +} + +function TableBody({ + columnVirtualizer, + table, + tableContainerRef, +}: TableBodyProps) { + const rowRefsMap = React.useRef>(new Map()) + + const { rows } = table.getRowModel() + + //dynamic row height virtualization - alternatively you could use a simpler fixed row height strategy without the need for `measureElement` + const rowVirtualizer = useVirtualizer({ + count: rows.length, + estimateSize: () => 33, //estimate row height for accurate scrollbar dragging + getScrollElement: () => tableContainerRef.current, + //measure dynamic row height, except in firefox because it measures table border height incorrectly + measureElement: + typeof window !== 'undefined' && + navigator.userAgent.indexOf('Firefox') === -1 + ? element => element?.getBoundingClientRect().height + : undefined, + overscan: 5, + onChange: instance => { + instance.getVirtualItems().forEach(virtualRow => { + const rowRef = rowRefsMap.current.get(virtualRow.index) + if (!rowRef) return + rowRef.style.transform = `translateY(${virtualRow.start}px)` + }) + }, + }) + + const virtualRowIndexes = rowVirtualizer.getVirtualIndexes() + + return ( + + {virtualRowIndexes.map(virtualRowIndex => { + const row = rows[virtualRowIndex] as Row + + return ( + + ) + })} + + ) +} + +interface TableBodyRowProps { + columnVirtualizer: Virtualizer + row: Row + rowVirtualizer: Virtualizer + virtualRowIndex: number + rowRefsMap: React.MutableRefObject> +} + +function TableBodyRow({ + columnVirtualizer, + row, + rowVirtualizer, + virtualRowIndex, + rowRefsMap, +}: TableBodyRowProps) { + const visibleCells = row.getVisibleCells() + const virtualColumnIndexes = columnVirtualizer.getVirtualIndexes() + + return ( + { + if (node) { + rowVirtualizer.measureElement(node) + rowRefsMap.current.set(virtualRowIndex, node) + } + }} //measure dynamic row height + key={row.id} + style={{ + display: 'flex', + position: 'absolute', + width: '100%', + }} + > + {/* fake empty column to the left for virtualization scroll padding */} + + {virtualColumnIndexes.map(virtualColumnIndex => { + const cell = visibleCells[virtualColumnIndex] + return ( + + ) + })} + {/* fake empty column to the right for virtualization scroll padding */} + + + ) +} + +// TODO: Can rows be memoized in any way without breaking column virtualization? +// const TableBodyRowMemo = React.memo( +// TableBodyRow, +// (_prev, next) => next.rowVirtualizer.isScrolling +// ) + +interface TableBodyCellProps { + cell: Cell + columnVirtualizer: Virtualizer +} + +function TableBodyCell({ + cell, + columnVirtualizer: _columnVirtualizer, +}: TableBodyCellProps) { + return ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ) +} + +const TableBodyCellMemo = React.memo( + TableBodyCell, + (_prev, next) => next.columnVirtualizer.isScrolling +) as typeof TableBodyCell + +const rootElement = document.getElementById('root') + +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + +) diff --git a/examples/react/virtualized-columns-expiremental/src/makeData.ts b/examples/react/virtualized-columns-expiremental/src/makeData.ts new file mode 100644 index 0000000000..3fde072d12 --- /dev/null +++ b/examples/react/virtualized-columns-expiremental/src/makeData.ts @@ -0,0 +1,19 @@ +import { faker } from '@faker-js/faker' + +export const makeColumns = num => + [...Array(num)].map((_, i) => { + return { + accessorKey: i.toString(), + header: 'Column ' + i.toString(), + size: Math.floor(Math.random() * 150) + 100, + } + }) + +export const makeData = (num, columns) => + [...Array(num)].map(() => ({ + ...Object.fromEntries( + columns.map(col => [col.accessorKey, faker.person.firstName()]) + ), + })) + +export type Person = ReturnType[0] diff --git a/examples/react/virtualized-columns-expiremental/tsconfig.json b/examples/react/virtualized-columns-expiremental/tsconfig.json new file mode 100644 index 0000000000..6d545f543f --- /dev/null +++ b/examples/react/virtualized-columns-expiremental/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/examples/react/virtualized-columns-expiremental/vite.config.js b/examples/react/virtualized-columns-expiremental/vite.config.js new file mode 100644 index 0000000000..2e1361723a --- /dev/null +++ b/examples/react/virtualized-columns-expiremental/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + ], +}) diff --git a/examples/react/virtualized-columns/index.html b/examples/react/virtualized-columns/index.html index 3fc40c9367..fa04f89341 100644 --- a/examples/react/virtualized-columns/index.html +++ b/examples/react/virtualized-columns/index.html @@ -8,6 +8,7 @@
+ diff --git a/examples/react/virtualized-columns/package.json b/examples/react/virtualized-columns/package.json index 91126df109..3bb33f21af 100644 --- a/examples/react/virtualized-columns/package.json +++ b/examples/react/virtualized-columns/package.json @@ -11,7 +11,7 @@ "dependencies": { "@faker-js/faker": "^8.4.1", "@tanstack/react-table": "^8.20.6", - "@tanstack/react-virtual": "^3.8.1", + "@tanstack/react-virtual": "^3.12.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/examples/react/virtualized-columns/src/main.tsx b/examples/react/virtualized-columns/src/main.tsx index 982b6447db..13076d02b0 100644 --- a/examples/react/virtualized-columns/src/main.tsx +++ b/examples/react/virtualized-columns/src/main.tsx @@ -1,19 +1,23 @@ import React from 'react' import ReactDOM from 'react-dom/client' - import './index.css' - import { + Cell, ColumnDef, + Header, + HeaderGroup, + Row, + Table, flexRender, getCoreRowModel, getSortedRowModel, - Row, useReactTable, } from '@tanstack/react-table' - -import { useVirtualizer } from '@tanstack/react-virtual' - +import { + useVirtualizer, + VirtualItem, + Virtualizer, +} from '@tanstack/react-virtual' import { makeColumns, makeData, Person } from './makeData' function App() { @@ -22,7 +26,11 @@ function App() { [] ) - const [data, _setData] = React.useState(() => makeData(1_000, columns)) + const [data, setData] = React.useState(() => makeData(1_000, columns)) + + const refreshData = React.useCallback(() => { + setData(makeData(1_000, columns)) + }, [columns]) const table = useReactTable({ data, @@ -32,15 +40,39 @@ function App() { debugTable: true, }) - const { rows } = table.getRowModel() + //All important CSS styles are included as inline styles for this example. This is not recommended for your code. + return ( +
+ {process.env.NODE_ENV === 'development' ? ( +

+ Notice: You are currently running React in + development mode. Virtualized rendering performance will be slightly + degraded until this application is built for production. +

+ ) : null} +
({columns.length.toLocaleString()} columns)
+
({data.length.toLocaleString()} rows)
+ + +
+ ) +} +interface TableContainerProps { + table: Table +} + +function TableContainer({ table }: TableContainerProps) { const visibleColumns = table.getVisibleLeafColumns() //The virtualizers need to know the scrollable container element const tableContainerRef = React.useRef(null) //we are using a slightly different virtualization strategy for columns (compared to virtual rows) in order to support dynamic row heights - const columnVirtualizer = useVirtualizer({ + const columnVirtualizer = useVirtualizer< + HTMLDivElement, + HTMLTableCellElement + >({ count: visibleColumns.length, estimateSize: index => visibleColumns[index].getSize(), //estimate width of each column for accurate scrollbar dragging getScrollElement: () => tableContainerRef.current, @@ -48,22 +80,7 @@ function App() { overscan: 3, //how many columns to render on each side off screen each way (adjust this for performance) }) - //dynamic row height virtualization - alternatively you could use a simpler fixed row height strategy without the need for `measureElement` - const rowVirtualizer = useVirtualizer({ - count: rows.length, - estimateSize: () => 33, //estimate row height for accurate scrollbar dragging - getScrollElement: () => tableContainerRef.current, - //measure dynamic row height, except in firefox because it measures table border height incorrectly - measureElement: - typeof window !== 'undefined' && - navigator.userAgent.indexOf('Firefox') === -1 - ? element => element?.getBoundingClientRect().height - : undefined, - overscan: 5, - }) - const virtualColumns = columnVirtualizer.getVirtualItems() - const virtualRows = rowVirtualizer.getVirtualItems() //different virtualization strategy for columns - instead of absolute and translateY, we add empty columns to the left and right let virtualPaddingLeft: number | undefined @@ -76,143 +93,256 @@ function App() { (virtualColumns[virtualColumns.length - 1]?.end ?? 0) } - //All important CSS styles are included as inline styles for this example. This is not recommended for your code. return ( -
- {process.env.NODE_ENV === 'development' ? ( -

- Notice: You are currently running React in - development mode. Virtualized rendering performance will be slightly - degraded until this application is built for production. -

+
+ {/* Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights */} + + + +
+
+ ) +} + +interface TableHeadProps { + columnVirtualizer: Virtualizer + table: Table + virtualPaddingLeft: number | undefined + virtualPaddingRight: number | undefined +} + +function TableHead({ + columnVirtualizer, + table, + virtualPaddingLeft, + virtualPaddingRight, +}: TableHeadProps) { + return ( + + {table.getHeaderGroups().map(headerGroup => ( + + ))} + + ) +} + +interface TableHeadRowProps { + columnVirtualizer: Virtualizer + headerGroup: HeaderGroup + virtualPaddingLeft: number | undefined + virtualPaddingRight: number | undefined +} + +function TableHeadRow({ + columnVirtualizer, + headerGroup, + virtualPaddingLeft, + virtualPaddingRight, +}: TableHeadRowProps) { + const virtualColumns = columnVirtualizer.getVirtualItems() + return ( + + {virtualPaddingLeft ? ( + //fake empty column to the left for virtualization scroll padding + ) : null} -
({columns.length.toLocaleString()} columns)
-
({data.length.toLocaleString()} rows)
+ {virtualColumns.map(virtualColumn => { + const header = headerGroup.headers[virtualColumn.index] + return + })} + {virtualPaddingRight ? ( + //fake empty column to the right for virtualization scroll padding + + ) : null} + + ) +} + +interface TableHeadCellProps { + header: Header +} +function TableHeadCell({ header }: TableHeadCellProps) { + return ( +
- {/* Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights */} - - - {table.getHeaderGroups().map(headerGroup => ( - - {virtualPaddingLeft ? ( - //fake empty column to the left for virtualization scroll padding - - ) - })} - {virtualPaddingRight ? ( - //fake empty column to the right for virtualization scroll padding - - ))} - - - {virtualRows.map(virtualRow => { - const row = rows[virtualRow.index] as Row - const visibleCells = row.getVisibleCells() - - return ( - rowVirtualizer.measureElement(node)} //measure dynamic row height - key={row.id} - style={{ - display: 'flex', - position: 'absolute', - transform: `translateY(${virtualRow.start}px)`, //this should always be a `style` as it changes on scroll - width: '100%', - }} - > - {virtualPaddingLeft ? ( - //fake empty column to the left for virtualization scroll padding - - ) - })} - {virtualPaddingRight ? ( - //fake empty column to the right for virtualization scroll padding - - ) - })} - -
- ) : null} - {virtualColumns.map(vc => { - const header = headerGroup.headers[vc.index] - return ( - -
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} - {{ - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null} -
-
- ) : null} -
- ) : null} - {virtualColumns.map(vc => { - const cell = visibleCells[vc.index] - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ) : null} -
+ {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null}
-
+ + ) +} + +interface TableBodyProps { + columnVirtualizer: Virtualizer + table: Table + tableContainerRef: React.RefObject + virtualPaddingLeft: number | undefined + virtualPaddingRight: number | undefined +} + +function TableBody({ + columnVirtualizer, + table, + tableContainerRef, + virtualPaddingLeft, + virtualPaddingRight, +}: TableBodyProps) { + const { rows } = table.getRowModel() + + //dynamic row height virtualization - alternatively you could use a simpler fixed row height strategy without the need for `measureElement` + const rowVirtualizer = useVirtualizer({ + count: rows.length, + estimateSize: () => 33, //estimate row height for accurate scrollbar dragging + getScrollElement: () => tableContainerRef.current, + //measure dynamic row height, except in firefox because it measures table border height incorrectly + measureElement: + typeof window !== 'undefined' && + navigator.userAgent.indexOf('Firefox') === -1 + ? element => element?.getBoundingClientRect().height + : undefined, + overscan: 5, + }) + + const virtualRows = rowVirtualizer.getVirtualItems() + + return ( + + {virtualRows.map(virtualRow => { + const row = rows[virtualRow.index] as Row + + return ( + + ) + })} + + ) +} + +interface TableBodyRowProps { + columnVirtualizer: Virtualizer + row: Row + rowVirtualizer: Virtualizer + virtualPaddingLeft: number | undefined + virtualPaddingRight: number | undefined + virtualRow: VirtualItem +} + +function TableBodyRow({ + columnVirtualizer, + row, + rowVirtualizer, + virtualPaddingLeft, + virtualPaddingRight, + virtualRow, +}: TableBodyRowProps) { + const visibleCells = row.getVisibleCells() + const virtualColumns = columnVirtualizer.getVirtualItems() + return ( + rowVirtualizer.measureElement(node)} //measure dynamic row height + key={row.id} + style={{ + display: 'flex', + position: 'absolute', + transform: `translateY(${virtualRow.start}px)`, //this should always be a `style` as it changes on scroll + width: '100%', + }} + > + {virtualPaddingLeft ? ( + //fake empty column to the left for virtualization scroll padding + + ) : null} + {virtualColumns.map(vc => { + const cell = visibleCells[vc.index] + return + })} + {virtualPaddingRight ? ( + //fake empty column to the right for virtualization scroll padding + + ) : null} + + ) +} + +interface TableBodyCellProps { + cell: Cell +} + +function TableBodyCell({ cell }: TableBodyCellProps) { + return ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ) } diff --git a/examples/react/virtualized-infinite-scrolling/package.json b/examples/react/virtualized-infinite-scrolling/package.json index 7250cfe972..2133608a90 100644 --- a/examples/react/virtualized-infinite-scrolling/package.json +++ b/examples/react/virtualized-infinite-scrolling/package.json @@ -12,7 +12,7 @@ "@faker-js/faker": "^8.4.1", "@tanstack/react-query": "^5.49.0", "@tanstack/react-table": "^8.20.6", - "@tanstack/react-virtual": "^3.8.1", + "@tanstack/react-virtual": "^3.12.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/examples/react/virtualized-rows-experimental/.gitignore b/examples/react/virtualized-rows-experimental/.gitignore new file mode 100644 index 0000000000..d451ff16c1 --- /dev/null +++ b/examples/react/virtualized-rows-experimental/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local diff --git a/examples/react/virtualized-rows-experimental/README.md b/examples/react/virtualized-rows-experimental/README.md new file mode 100644 index 0000000000..b168d3c4b1 --- /dev/null +++ b/examples/react/virtualized-rows-experimental/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` or `yarn` +- `npm run start` or `yarn start` diff --git a/examples/react/virtualized-rows-experimental/index.html b/examples/react/virtualized-rows-experimental/index.html new file mode 100644 index 0000000000..fa04f89341 --- /dev/null +++ b/examples/react/virtualized-rows-experimental/index.html @@ -0,0 +1,14 @@ + + + + + + Vite App + + + +
+ + + + diff --git a/examples/react/virtualized-rows-experimental/package.json b/examples/react/virtualized-rows-experimental/package.json new file mode 100644 index 0000000000..2a5e22a28e --- /dev/null +++ b/examples/react/virtualized-rows-experimental/package.json @@ -0,0 +1,26 @@ +{ + "name": "tanstack-table-example-virtualized-rows-experimental", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview", + "start": "vite" + }, + "dependencies": { + "@faker-js/faker": "^8.4.1", + "@tanstack/react-table": "^8.20.6", + "@tanstack/react-virtual": "^3.12.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@rollup/plugin-replace": "^5.0.7", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "typescript": "5.4.5", + "vite": "^5.3.2" + } +} diff --git a/examples/react/virtualized-rows-experimental/src/index.css b/examples/react/virtualized-rows-experimental/src/index.css new file mode 100644 index 0000000000..6402cb4b8b --- /dev/null +++ b/examples/react/virtualized-rows-experimental/src/index.css @@ -0,0 +1,40 @@ +html { + font-family: sans-serif; + font-size: 14px; +} + +table { + border-collapse: collapse; + border-spacing: 0; + font-family: arial, sans-serif; + table-layout: fixed; +} + +thead { + background: lightgray; +} + +tr { + border-bottom: 1px solid lightgray; +} + +th { + border-bottom: 1px solid lightgray; + border-right: 1px solid lightgray; + padding: 2px 4px; + text-align: left; +} + +td { + padding: 6px; +} + +.container { + border: 1px solid lightgray; + margin: 1rem auto; +} + +.app { + margin: 1rem auto; + text-align: center; +} diff --git a/examples/react/virtualized-rows-experimental/src/main.tsx b/examples/react/virtualized-rows-experimental/src/main.tsx new file mode 100644 index 0000000000..b0e271b262 --- /dev/null +++ b/examples/react/virtualized-rows-experimental/src/main.tsx @@ -0,0 +1,295 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' + +import './index.css' + +import { + flexRender, + getCoreRowModel, + getSortedRowModel, + useReactTable, +} from '@tanstack/react-table' +import { useVirtualizer } from '@tanstack/react-virtual' +import { makeData } from './makeData' +import type { ColumnDef, Row, Table } from '@tanstack/react-table' +import type { Virtualizer } from '@tanstack/react-virtual' +import type { Person } from './makeData' + +// This is a dynamic row height example, which is more complicated, but allows for a more realistic table. +// See https://tanstack.com/virtual/v3/docs/examples/react/table for a simpler fixed row height example. +function App() { + const columns = React.useMemo>>( + () => [ + { + accessorKey: 'id', + header: 'ID', + size: 60, + }, + { + accessorKey: 'firstName', + cell: info => info.getValue(), + }, + { + accessorFn: row => row.lastName, + id: 'lastName', + cell: info => info.getValue(), + header: () => Last Name, + }, + { + accessorKey: 'age', + header: () => 'Age', + size: 50, + }, + { + accessorKey: 'visits', + header: () => Visits, + size: 50, + }, + { + accessorKey: 'status', + header: 'Status', + }, + { + accessorKey: 'progress', + header: 'Profile Progress', + size: 80, + }, + { + accessorKey: 'createdAt', + header: 'Created At', + cell: info => info.getValue().toLocaleString(), + size: 250, + }, + ], + [] + ) + + const [data, _setData] = React.useState(() => makeData(50_000)) + + const refreshData = React.useCallback(() => { + _setData(makeData(50_000)) + }, []) + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + debugTable: true, + }) + + // The virtualizer needs to know the scrollable container element + const tableContainerRef = React.useRef(null) + + // All important CSS styles are included as inline styles for this example. This is not recommended for your code. + return ( +
+ {process.env.NODE_ENV === 'development' ? ( +

+ Notice: You are currently running React in + development mode. Virtualized rendering performance will be slightly + degraded until this application is built for production. +

+ ) : null} + ({data.length} rows) + +
+ {/* Even though we're still using sematic table tags, we must use CSS grid and flexbox for dynamic row heights */} + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => { + return ( + + ) + })} + + ))} + + +
+
+ {flexRender( + header.column.columnDef.header, + header.getContext() + )} + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+
+
+
+ ) +} + +interface TableBodyWrapperProps { + table: Table + tableContainerRef: React.RefObject +} + +function TableBodyWrapper({ table, tableContainerRef }: TableBodyWrapperProps) { + const rowRefsMap = React.useRef>(new Map()) + + const { rows } = table.getRowModel() + + const rowVirtualizer = useVirtualizer({ + count: rows.length, + estimateSize: () => 33, // estimate row height for accurate scrollbar dragging + getScrollElement: () => tableContainerRef.current, + // measure dynamic row height, except in firefox because it measures table border height incorrectly + measureElement: + typeof window !== 'undefined' && + navigator.userAgent.indexOf('Firefox') === -1 + ? element => element?.getBoundingClientRect().height + : undefined, + overscan: 5, + onChange: instance => { + instance.getVirtualItems().forEach(virtualRow => { + const rowRef = rowRefsMap.current.get(virtualRow.index) + if (!rowRef) return + rowRef.style.transform = `translateY(${virtualRow.start}px)` + }) + }, + }) + + return ( + + ) +} + +interface TableBodyProps { + table: Table + rowVirtualizer: Virtualizer + rowRefsMap: React.MutableRefObject> +} + +function TableBody({ rowVirtualizer, table, rowRefsMap }: TableBodyProps) { + const { rows } = table.getRowModel() + const virtualRowIndexes = rowVirtualizer.getVirtualIndexes() + + return ( + + {virtualRowIndexes.map(virtualRowIndex => { + const row = rows[virtualRowIndex] + return ( + + ) + })} + + ) +} + +interface TableBodyRowProps { + row: Row + rowRefsMap: React.MutableRefObject> + rowVirtualizer: Virtualizer + virtualRowIndex: number +} + +function TableBodyRow({ + row, + rowRefsMap, + rowVirtualizer, + virtualRowIndex, +}: TableBodyRowProps) { + return ( + { + if (node && virtualRowIndex) { + rowVirtualizer.measureElement(node) // measure dynamic row height + rowRefsMap.current.set(virtualRowIndex, node) // store ref for virtualizer to apply scrolling transforms + } + }} + key={row.id} + style={{ + display: 'flex', + position: 'absolute', + width: '100%', + }} + > + {row.getVisibleCells().map(cell => { + return ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ) + })} + + ) +} + +// test out when rows don't re-render at all (future TanStack Virtual release can make this unnecessary) +const TableBodyRowMemo = React.memo( + TableBodyRow, + (_prev, next) => next.rowVirtualizer.isScrolling +) as typeof TableBodyRow + +const rootElement = document.getElementById('root') + +if (!rootElement) throw new Error('Failed to find the root element') + +ReactDOM.createRoot(rootElement).render( + + + +) diff --git a/examples/react/virtualized-rows-experimental/src/makeData.ts b/examples/react/virtualized-rows-experimental/src/makeData.ts new file mode 100644 index 0000000000..e5695467f5 --- /dev/null +++ b/examples/react/virtualized-rows-experimental/src/makeData.ts @@ -0,0 +1,50 @@ +import { faker } from '@faker-js/faker' + +export type Person = { + id: number + firstName: string + lastName: string + age: number + visits: number + progress: number + status: 'relationship' | 'complicated' | 'single' + createdAt: Date +} + +const range = (len: number) => { + const arr: number[] = [] + for (let i = 0; i < len; i++) { + arr.push(i) + } + return arr +} + +const newPerson = (index: number): Person => { + return { + id: index + 1, + firstName: faker.person.firstName(), + lastName: faker.person.lastName(), + age: faker.number.int(40), + visits: faker.number.int(1000), + progress: faker.number.int(100), + createdAt: faker.date.anytime(), + status: faker.helpers.shuffle([ + 'relationship', + 'complicated', + 'single', + ])[0]!, + } +} + +export function makeData(...lens: number[]) { + const makeDataLevel = (depth = 0): Person[] => { + const len = lens[depth]! + return range(len).map((d): Person => { + return { + ...newPerson(d), + } + }) + } + + return makeDataLevel() +} diff --git a/examples/react/virtualized-rows-experimental/tsconfig.json b/examples/react/virtualized-rows-experimental/tsconfig.json new file mode 100644 index 0000000000..6d545f543f --- /dev/null +++ b/examples/react/virtualized-rows-experimental/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/examples/react/virtualized-rows-experimental/vite.config.js b/examples/react/virtualized-rows-experimental/vite.config.js new file mode 100644 index 0000000000..2e1361723a --- /dev/null +++ b/examples/react/virtualized-rows-experimental/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import rollupReplace from '@rollup/plugin-replace' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + rollupReplace({ + preventAssignment: true, + values: { + __DEV__: JSON.stringify(true), + 'process.env.NODE_ENV': JSON.stringify('development'), + }, + }), + react(), + ], +}) diff --git a/examples/react/virtualized-rows/index.html b/examples/react/virtualized-rows/index.html index 3fc40c9367..fa04f89341 100644 --- a/examples/react/virtualized-rows/index.html +++ b/examples/react/virtualized-rows/index.html @@ -8,6 +8,7 @@
+ diff --git a/examples/react/virtualized-rows/package.json b/examples/react/virtualized-rows/package.json index 9cf6df0b17..9f3b19b643 100644 --- a/examples/react/virtualized-rows/package.json +++ b/examples/react/virtualized-rows/package.json @@ -11,7 +11,7 @@ "dependencies": { "@faker-js/faker": "^8.4.1", "@tanstack/react-table": "^8.20.6", - "@tanstack/react-virtual": "^3.8.1", + "@tanstack/react-virtual": "^3.12.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/examples/react/virtualized-rows/src/main.tsx b/examples/react/virtualized-rows/src/main.tsx index 630f3775de..9f9f72a2b7 100644 --- a/examples/react/virtualized-rows/src/main.tsx +++ b/examples/react/virtualized-rows/src/main.tsx @@ -9,11 +9,14 @@ import { getCoreRowModel, getSortedRowModel, Row, + Table, useReactTable, } from '@tanstack/react-table' - -import { useVirtualizer } from '@tanstack/react-virtual' - +import { + useVirtualizer, + VirtualItem, + Virtualizer, +} from '@tanstack/react-virtual' import { makeData, Person } from './makeData' //This is a dynamic row height example, which is more complicated, but allows for a more realistic table. @@ -65,7 +68,14 @@ function App() { [] ) - const [data, _setData] = React.useState(() => makeData(50_000)) + // The virtualizer will need a reference to the scrollable container element + const tableContainerRef = React.useRef(null) + + const [data, setData] = React.useState(() => makeData(50_000)) + + const refreshData = React.useCallback(() => { + setData(makeData(50_000)) + }, []) const table = useReactTable({ data, @@ -75,25 +85,7 @@ function App() { debugTable: true, }) - const { rows } = table.getRowModel() - - //The virtualizer needs to know the scrollable container element - const tableContainerRef = React.useRef(null) - - const rowVirtualizer = useVirtualizer({ - count: rows.length, - estimateSize: () => 33, //estimate row height for accurate scrollbar dragging - getScrollElement: () => tableContainerRef.current, - //measure dynamic row height, except in firefox because it measures table border height incorrectly - measureElement: - typeof window !== 'undefined' && - navigator.userAgent.indexOf('Firefox') === -1 - ? element => element?.getBoundingClientRect().height - : undefined, - overscan: 5, - }) - - //All important CSS styles are included as inline styles for this example. This is not recommended for your code. + // All important CSS styles are included as inline styles for this example. This is not recommended for your code. return (
{process.env.NODE_ENV === 'development' ? ( @@ -104,6 +96,7 @@ function App() {

) : null} ({data.length} rows) +
))} - - {rowVirtualizer.getVirtualItems().map(virtualRow => { - const row = rows[virtualRow.index] as Row - return ( - rowVirtualizer.measureElement(node)} //measure dynamic row height - key={row.id} - style={{ - display: 'flex', - position: 'absolute', - transform: `translateY(${virtualRow.start}px)`, //this should always be a `style` as it changes on scroll - width: '100%', - }} - > - {row.getVisibleCells().map(cell => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ) - })} - - ) - })} - +
) } +interface TableBodyProps { + table: Table + tableContainerRef: React.RefObject +} + +function TableBody({ table, tableContainerRef }: TableBodyProps) { + const { rows } = table.getRowModel() + + // Important: Keep the row virtualizer in the lowest component possible to avoid unnecessary re-renders. + const rowVirtualizer = useVirtualizer({ + count: rows.length, + estimateSize: () => 33, //estimate row height for accurate scrollbar dragging + getScrollElement: () => tableContainerRef.current, + //measure dynamic row height, except in firefox because it measures table border height incorrectly + measureElement: + typeof window !== 'undefined' && + navigator.userAgent.indexOf('Firefox') === -1 + ? element => element?.getBoundingClientRect().height + : undefined, + overscan: 5, + }) + + return ( + + {rowVirtualizer.getVirtualItems().map(virtualRow => { + const row = rows[virtualRow.index] as Row + return ( + + ) + })} + + ) +} + +interface TableBodyRowProps { + row: Row + virtualRow: VirtualItem + rowVirtualizer: Virtualizer +} + +function TableBodyRow({ row, virtualRow, rowVirtualizer }: TableBodyRowProps) { + return ( + rowVirtualizer.measureElement(node)} //measure dynamic row height + key={row.id} + style={{ + display: 'flex', + position: 'absolute', + transform: `translateY(${virtualRow.start}px)`, //this should always be a `style` as it changes on scroll + width: '100%', + }} + > + {row.getVisibleCells().map(cell => { + return ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ) + })} + + ) +} + const rootElement = document.getElementById('root') if (!rootElement) throw new Error('Failed to find the root element') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea56f88c08..956d45dbd7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1173,6 +1173,28 @@ importers: specifier: ^5.3.2 version: 5.3.2(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(terser@5.31.1) + examples/lit/sorting-dynamic-data: + dependencies: + '@faker-js/faker': + specifier: ^8.4.1 + version: 8.4.1 + '@tanstack/lit-table': + specifier: ^8.20.5 + version: link:../../../packages/lit-table + lit: + specifier: ^3.1.4 + version: 3.1.4 + devDependencies: + '@rollup/plugin-replace': + specifier: ^5.0.7 + version: 5.0.7(rollup@4.29.1) + typescript: + specifier: 5.4.5 + version: 5.4.5 + vite: + specifier: ^5.3.2 + version: 5.4.11(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(terser@5.31.1) + examples/lit/virtualized-rows: dependencies: '@faker-js/faker': @@ -2387,8 +2409,8 @@ importers: specifier: ^8.20.6 version: link:../../../packages/react-table '@tanstack/react-virtual': - specifier: ^3.8.1 - version: 3.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^3.12.0 + version: 3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -2415,6 +2437,43 @@ importers: specifier: ^5.3.2 version: 5.3.2(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(terser@5.31.1) + examples/react/virtualized-columns-expiremental: + dependencies: + '@faker-js/faker': + specifier: ^8.4.1 + version: 8.4.1 + '@tanstack/react-table': + specifier: ^8.20.6 + version: link:../../../packages/react-table + '@tanstack/react-virtual': + specifier: ^3.12.0 + version: 3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@rollup/plugin-replace': + specifier: ^5.0.7 + version: 5.0.7(rollup@4.29.1) + '@types/react': + specifier: ^18.3.3 + version: 18.3.3 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.0 + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.3.1(vite@5.4.11(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(terser@5.31.1)) + typescript: + specifier: 5.4.5 + version: 5.4.5 + vite: + specifier: ^5.3.2 + version: 5.4.11(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(terser@5.31.1) + examples/react/virtualized-infinite-scrolling: dependencies: '@faker-js/faker': @@ -2427,8 +2486,8 @@ importers: specifier: ^8.20.6 version: link:../../../packages/react-table '@tanstack/react-virtual': - specifier: ^3.8.1 - version: 3.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^3.12.0 + version: 3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -2464,8 +2523,8 @@ importers: specifier: ^8.20.6 version: link:../../../packages/react-table '@tanstack/react-virtual': - specifier: ^3.8.1 - version: 3.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^3.12.0 + version: 3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -2492,6 +2551,43 @@ importers: specifier: ^5.3.2 version: 5.3.2(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(terser@5.31.1) + examples/react/virtualized-rows-experimental: + dependencies: + '@faker-js/faker': + specifier: ^8.4.1 + version: 8.4.1 + '@tanstack/react-table': + specifier: ^8.20.6 + version: link:../../../packages/react-table + '@tanstack/react-virtual': + specifier: ^3.12.0 + version: 3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@rollup/plugin-replace': + specifier: ^5.0.7 + version: 5.0.7(rollup@4.29.1) + '@types/react': + specifier: ^18.3.3 + version: 18.3.3 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.0 + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.3.1(vite@5.4.11(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(terser@5.31.1)) + typescript: + specifier: 5.4.5 + version: 5.4.5 + vite: + specifier: ^5.3.2 + version: 5.4.11(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(terser@5.31.1) + examples/solid/basic: dependencies: '@tanstack/solid-table': @@ -5664,11 +5760,11 @@ packages: react: '>=16' react-dom: '>=16' - '@tanstack/react-virtual@3.8.1': - resolution: {integrity: sha512-dP5a7giEM4BQWLJ7K07ToZv8rF51mzbrBMkf0scg1QNYuFx3utnPUBPUHdzaowZhIez1K2XS78amuzD+YGRA5Q==} + '@tanstack/react-virtual@3.12.0': + resolution: {integrity: sha512-6krceiPN07kpxXmU6m8AY7EL0X1gHLu8m3nJdh4phvktzVNxkQfBmSwnRUpoUjGQO1PAn8wSAhYaL8hY1cS1vw==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 '@tanstack/router-generator@1.43.1': resolution: {integrity: sha512-9dK/vVGO6SupMed1EAHwsIY0sHEu1EBsVYa208/V+zonJLOTNTthuDAYUxjmLTLm18FeqsujKoDl0hFX4rsREw==} @@ -5696,8 +5792,8 @@ packages: '@tanstack/virtual-core@3.11.2': resolution: {integrity: sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==} - '@tanstack/virtual-core@3.8.1': - resolution: {integrity: sha512-uNtAwenT276M9QYCjTBoHZ8X3MUeCRoGK59zPi92hMIxdfS9AyHjkDWJ94WroDxnv48UE+hIeo21BU84jKc8aQ==} + '@tanstack/virtual-core@3.12.0': + resolution: {integrity: sha512-7mDINtua3v/pOnn6WUmuT9dPXYSO7WidFej7JzoAfqEOcbbpt/iZ1WPqd+eg+FnrL9nUJK8radqj4iAU51Zchg==} '@tanstack/virtual-core@3.8.3': resolution: {integrity: sha512-vd2A2TnM5lbnWZnHi9B+L2gPtkSeOtJOAw358JqokIH1+v2J7vUAzFVPwB/wrye12RFOurffXu33plm4uQ+JBQ==} @@ -12488,12 +12584,13 @@ snapshots: dependencies: csstype: 3.1.3 undici: 6.11.1 - vite: 5.3.2(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(terser@5.31.1) + vite: 5.4.11(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(terser@5.31.1) transitivePeerDependencies: - '@types/node' - less - lightningcss - sass + - sass-embedded - stylus - sugarss - terser @@ -13743,9 +13840,9 @@ snapshots: react-dom: 18.3.1(react@18.3.1) use-sync-external-store: 1.2.2(react@18.3.1) - '@tanstack/react-virtual@3.8.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@tanstack/react-virtual@3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@tanstack/virtual-core': 3.8.1 + '@tanstack/virtual-core': 3.12.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -13789,7 +13886,7 @@ snapshots: '@tanstack/virtual-core@3.11.2': {} - '@tanstack/virtual-core@3.8.1': {} + '@tanstack/virtual-core@3.12.0': {} '@tanstack/virtual-core@3.8.3': {} @@ -14142,6 +14239,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitejs/plugin-react@4.3.1(vite@5.4.11(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(terser@5.31.1))': + dependencies: + '@babel/core': 7.24.7 + '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.4.11(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(terser@5.31.1) + transitivePeerDependencies: + - supports-color + '@vitejs/plugin-vue-jsx@4.0.0(vite@5.3.2(@types/node@20.14.9)(less@4.2.0)(sass@1.77.6)(terser@5.31.1))(vue@3.4.31(typescript@5.4.5))': dependencies: '@babel/core': 7.24.7