From 1f1bd6206633b38f9d3dcec262f4d56de33a4cc2 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Sun, 22 Feb 2026 03:37:20 +0000 Subject: [PATCH 1/2] useGridDimensions: useSyncExternalStore implementation --- src/DataGrid.tsx | 5 +- src/hooks/useGridDimensions.ts | 123 ++++++++++----- test/failOnConsole.ts | 7 - vite.config.ts | 3 +- website/root.tsx | 32 +--- website/routeTree.gen.ts | 192 ----------------------- website/router.tsx | 26 +++ website/routes/AllFeatures.tsx | 3 +- website/routes/Animation.tsx | 3 +- website/routes/CellNavigation.tsx | 3 +- website/routes/ColumnGrouping.tsx | 4 +- website/routes/ColumnSpanning.tsx | 3 +- website/routes/ColumnsReordering.tsx | 3 +- website/routes/CommonFeatures.tsx | 3 +- website/routes/ContextMenu.tsx | 3 +- website/routes/CustomizableRenderers.tsx | 3 +- website/routes/HeaderFilters.tsx | 3 +- website/routes/InfiniteScrolling.tsx | 3 +- website/routes/MasterDetail.tsx | 3 +- website/routes/MillionCells.tsx | 4 +- website/routes/NoRows.tsx | 3 +- website/routes/ResizableGrid.tsx | 4 +- website/routes/RowGrouping.tsx | 3 +- website/routes/RowsReordering.tsx | 3 +- website/routes/ScrollToCell.tsx | 3 +- website/routes/TreeView.tsx | 3 +- website/routes/VariableRowHeight.tsx | 4 +- website/routes/index.tsx | 4 +- 28 files changed, 164 insertions(+), 292 deletions(-) create mode 100644 website/router.tsx diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index ebaeb25d86..376e21b5c7 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -340,7 +340,7 @@ export function DataGrid(props: DataGridPr [columnWidths] ); - const [gridRef, gridWidth, gridHeight, horizontalScrollbarHeight] = useGridDimensions(); + const [gridRef, gridWidth, gridHeight] = useGridDimensions(); const { columns, colSpanColumns, @@ -461,8 +461,6 @@ export function DataGrid(props: DataGridPr const maxColIdx = columns.length - 1; const selectedCellIsWithinSelectionBounds = isCellWithinSelectionBounds(selectedPosition); const selectedCellIsWithinViewportBounds = isCellWithinViewportBounds(selectedPosition); - const scrollHeight = - headerRowHeight + totalRowHeight + summaryRowsHeight + horizontalScrollbarHeight; /** * The identity of the wrapper function is stable so it won't break memoization @@ -1189,7 +1187,6 @@ export function DataGrid(props: DataGridPr gridTemplateColumns, gridTemplateRows: templateRows, '--rdg-header-row-height': `${headerRowHeight}px`, - '--rdg-scroll-height': `${scrollHeight}px`, ...layoutCssVars }} dir={direction} diff --git a/src/hooks/useGridDimensions.ts b/src/hooks/useGridDimensions.ts index ddd2ac67e3..b12de2d6bd 100644 --- a/src/hooks/useGridDimensions.ts +++ b/src/hooks/useGridDimensions.ts @@ -1,46 +1,99 @@ -import { useLayoutEffect, useRef, useState } from 'react'; -import { flushSync } from 'react-dom'; +import { useCallback, useId, useLayoutEffect, useRef, useSyncExternalStore } from 'react'; + +const initialSize: ResizeObserverSize = { + inlineSize: 1, + blockSize: 1 +}; + +const targetToIdMap = new Map(); +const idToTargetMap = new Map(); +// use an unmanaged WeakMap so we preserve the cache even when +// the component partially unmounts via Suspense or Activity +const sizeMap = new WeakMap(); +const subscribers = new Map void>(); + +// don't break in Node.js (SSR), jsdom, and environments that don't support ResizeObserver +const resizeObserver = + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + globalThis.ResizeObserver == null ? null : new ResizeObserver(resizeObserverCallback); + +function resizeObserverCallback(entries: ResizeObserverEntry[]) { + for (const entry of entries) { + const target = entry.target as HTMLDivElement; + + if (!targetToIdMap.has(target)) continue; + + const id = targetToIdMap.get(target)!; + + updateSize(target, id, entry.contentBoxSize[0]); + } +} + +function updateSize(target: HTMLDivElement, id: string, size: ResizeObserverSize) { + if (sizeMap.has(target)) { + const prevSize = sizeMap.get(target)!; + if (prevSize.inlineSize === size.inlineSize && prevSize.blockSize === size.blockSize) { + return; + } + } + + sizeMap.set(target, size); + subscribers.get(id)?.(); +} + +function getServerSnapshot(): ResizeObserverSize { + return initialSize; +} export function useGridDimensions() { + const id = useId(); const gridRef = useRef(null); - const [inlineSize, setInlineSize] = useState(1); - const [blockSize, setBlockSize] = useState(1); - const [horizontalScrollbarHeight, setHorizontalScrollbarHeight] = useState(0); + + const subscribe = useCallback( + (onStoreChange: () => void) => { + subscribers.set(id, onStoreChange); + + return () => { + subscribers.delete(id); + }; + }, + [id] + ); + + const getSnapshot = useCallback((): ResizeObserverSize => { + if (idToTargetMap.has(id)) { + const target = idToTargetMap.get(id)!; + if (sizeMap.has(target)) { + return sizeMap.get(target)!; + } + } + return initialSize; + }, [id]); + + // We use `useSyncExternalStore` instead of `useState` to avoid tearing, + // which can lead to flashing scrollbars. + const { inlineSize, blockSize } = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); useLayoutEffect(() => { - const { ResizeObserver } = window; - - // don't break in Node.js (SSR), jsdom, and browsers that don't support ResizeObserver - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (ResizeObserver == null) return; - - const { clientWidth, clientHeight, offsetWidth, offsetHeight } = gridRef.current!; - const { width, height } = gridRef.current!.getBoundingClientRect(); - const initialHorizontalScrollbarHeight = offsetHeight - clientHeight; - const initialWidth = width - offsetWidth + clientWidth; - const initialHeight = height - initialHorizontalScrollbarHeight; - - setInlineSize(initialWidth); - setBlockSize(initialHeight); - setHorizontalScrollbarHeight(initialHorizontalScrollbarHeight); - - const resizeObserver = new ResizeObserver((entries) => { - const size = entries[0].contentBoxSize[0]; - const { clientHeight, offsetHeight } = gridRef.current!; - - // we use flushSync here to avoid flashing scrollbars - flushSync(() => { - setInlineSize(size.inlineSize); - setBlockSize(size.blockSize); - setHorizontalScrollbarHeight(offsetHeight - clientHeight); + const target = gridRef.current!; + + targetToIdMap.set(target, id); + idToTargetMap.set(id, target); + resizeObserver?.observe(target); + + if (!sizeMap.has(target)) { + updateSize(target, id, { + inlineSize: target.clientWidth, + blockSize: target.clientHeight }); - }); - resizeObserver.observe(gridRef.current!); + } return () => { - resizeObserver.disconnect(); + targetToIdMap.delete(target); + idToTargetMap.delete(id); + resizeObserver?.unobserve(target); }; - }, []); + }, [id]); - return [gridRef, inlineSize, blockSize, horizontalScrollbarHeight] as const; + return [gridRef, inlineSize, blockSize] as const; } diff --git a/test/failOnConsole.ts b/test/failOnConsole.ts index 791e26fc25..d797ed0e2e 100644 --- a/test/failOnConsole.ts +++ b/test/failOnConsole.ts @@ -5,13 +5,6 @@ beforeAll(() => { globalThis.console = { ...console, error(...params) { - if ( - params[0] instanceof Error && - params[0].message === 'ResizeObserver loop completed with undelivered notifications.' - ) { - return; - } - consoleErrorOrConsoleWarnWereCalled = true; console.log(...params); }, diff --git a/vite.config.ts b/vite.config.ts index 6871bdbfd5..901bcf8a3e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -89,8 +89,7 @@ export default defineConfig( target: 'react', generatedRouteTree: 'website/routeTree.gen.ts', routesDirectory: 'website/routes', - autoCodeSplitting: true, - verboseFileRoutes: false + autoCodeSplitting: true }), react({ exclude: ['./.cache/**/*', './node_modules/**/*', './website/routeTree.gen.ts'] diff --git a/website/root.tsx b/website/root.tsx index 1f1d9d7e8a..2dc79dba6d 100644 --- a/website/root.tsx +++ b/website/root.tsx @@ -2,37 +2,9 @@ import './root.css'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; -import { - createHashHistory, - createRouter, - ErrorComponent, - RouterProvider -} from '@tanstack/react-router'; +import { RouterProvider } from '@tanstack/react-router'; -import { routeTree } from './routeTree.gen'; - -const router = createRouter({ - routeTree, - history: createHashHistory(), - caseSensitive: true, - defaultErrorComponent: ErrorComponent, - defaultNotFoundComponent: NotFound, - defaultPendingMinMs: 0, - defaultPreload: 'intent', - defaultStructuralSharing: true, - scrollRestoration: true -}); - -// Register the router instance for type safety -declare module '@tanstack/react-router' { - interface Register { - router: typeof router; - } -} - -function NotFound() { - return 'Nothing to see here'; -} +import { router } from './router'; createRoot(document.getElementById('root')!).render( diff --git a/website/routeTree.gen.ts b/website/routeTree.gen.ts index 673c81a1d2..b0b8edb0b3 100644 --- a/website/routeTree.gen.ts +++ b/website/routeTree.gen.ts @@ -8,8 +8,6 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -import type { CreateFileRoute, FileRoutesByPath } from '@tanstack/react-router' - import { Route as rootRouteImport } from './routes/__root' import { Route as VariableRowHeightRouteImport } from './routes/VariableRowHeight' import { Route as TreeViewRouteImport } from './routes/TreeView' @@ -457,196 +455,6 @@ declare module '@tanstack/react-router' { } } -declare module './routes/index' { - const createFileRoute: CreateFileRoute< - '/', - FileRoutesByPath['/']['parentRoute'], - FileRoutesByPath['/']['id'], - FileRoutesByPath['/']['path'], - FileRoutesByPath['/']['fullPath'] - > -} -declare module './routes/AllFeatures' { - const createFileRoute: CreateFileRoute< - '/AllFeatures', - FileRoutesByPath['/AllFeatures']['parentRoute'], - FileRoutesByPath['/AllFeatures']['id'], - FileRoutesByPath['/AllFeatures']['path'], - FileRoutesByPath['/AllFeatures']['fullPath'] - > -} -declare module './routes/Animation' { - const createFileRoute: CreateFileRoute< - '/Animation', - FileRoutesByPath['/Animation']['parentRoute'], - FileRoutesByPath['/Animation']['id'], - FileRoutesByPath['/Animation']['path'], - FileRoutesByPath['/Animation']['fullPath'] - > -} -declare module './routes/CellNavigation' { - const createFileRoute: CreateFileRoute< - '/CellNavigation', - FileRoutesByPath['/CellNavigation']['parentRoute'], - FileRoutesByPath['/CellNavigation']['id'], - FileRoutesByPath['/CellNavigation']['path'], - FileRoutesByPath['/CellNavigation']['fullPath'] - > -} -declare module './routes/ColumnGrouping' { - const createFileRoute: CreateFileRoute< - '/ColumnGrouping', - FileRoutesByPath['/ColumnGrouping']['parentRoute'], - FileRoutesByPath['/ColumnGrouping']['id'], - FileRoutesByPath['/ColumnGrouping']['path'], - FileRoutesByPath['/ColumnGrouping']['fullPath'] - > -} -declare module './routes/ColumnSpanning' { - const createFileRoute: CreateFileRoute< - '/ColumnSpanning', - FileRoutesByPath['/ColumnSpanning']['parentRoute'], - FileRoutesByPath['/ColumnSpanning']['id'], - FileRoutesByPath['/ColumnSpanning']['path'], - FileRoutesByPath['/ColumnSpanning']['fullPath'] - > -} -declare module './routes/ColumnsReordering' { - const createFileRoute: CreateFileRoute< - '/ColumnsReordering', - FileRoutesByPath['/ColumnsReordering']['parentRoute'], - FileRoutesByPath['/ColumnsReordering']['id'], - FileRoutesByPath['/ColumnsReordering']['path'], - FileRoutesByPath['/ColumnsReordering']['fullPath'] - > -} -declare module './routes/CommonFeatures' { - const createFileRoute: CreateFileRoute< - '/CommonFeatures', - FileRoutesByPath['/CommonFeatures']['parentRoute'], - FileRoutesByPath['/CommonFeatures']['id'], - FileRoutesByPath['/CommonFeatures']['path'], - FileRoutesByPath['/CommonFeatures']['fullPath'] - > -} -declare module './routes/ContextMenu' { - const createFileRoute: CreateFileRoute< - '/ContextMenu', - FileRoutesByPath['/ContextMenu']['parentRoute'], - FileRoutesByPath['/ContextMenu']['id'], - FileRoutesByPath['/ContextMenu']['path'], - FileRoutesByPath['/ContextMenu']['fullPath'] - > -} -declare module './routes/CustomizableRenderers' { - const createFileRoute: CreateFileRoute< - '/CustomizableRenderers', - FileRoutesByPath['/CustomizableRenderers']['parentRoute'], - FileRoutesByPath['/CustomizableRenderers']['id'], - FileRoutesByPath['/CustomizableRenderers']['path'], - FileRoutesByPath['/CustomizableRenderers']['fullPath'] - > -} -declare module './routes/HeaderFilters' { - const createFileRoute: CreateFileRoute< - '/HeaderFilters', - FileRoutesByPath['/HeaderFilters']['parentRoute'], - FileRoutesByPath['/HeaderFilters']['id'], - FileRoutesByPath['/HeaderFilters']['path'], - FileRoutesByPath['/HeaderFilters']['fullPath'] - > -} -declare module './routes/InfiniteScrolling' { - const createFileRoute: CreateFileRoute< - '/InfiniteScrolling', - FileRoutesByPath['/InfiniteScrolling']['parentRoute'], - FileRoutesByPath['/InfiniteScrolling']['id'], - FileRoutesByPath['/InfiniteScrolling']['path'], - FileRoutesByPath['/InfiniteScrolling']['fullPath'] - > -} -declare module './routes/MasterDetail' { - const createFileRoute: CreateFileRoute< - '/MasterDetail', - FileRoutesByPath['/MasterDetail']['parentRoute'], - FileRoutesByPath['/MasterDetail']['id'], - FileRoutesByPath['/MasterDetail']['path'], - FileRoutesByPath['/MasterDetail']['fullPath'] - > -} -declare module './routes/MillionCells' { - const createFileRoute: CreateFileRoute< - '/MillionCells', - FileRoutesByPath['/MillionCells']['parentRoute'], - FileRoutesByPath['/MillionCells']['id'], - FileRoutesByPath['/MillionCells']['path'], - FileRoutesByPath['/MillionCells']['fullPath'] - > -} -declare module './routes/NoRows' { - const createFileRoute: CreateFileRoute< - '/NoRows', - FileRoutesByPath['/NoRows']['parentRoute'], - FileRoutesByPath['/NoRows']['id'], - FileRoutesByPath['/NoRows']['path'], - FileRoutesByPath['/NoRows']['fullPath'] - > -} -declare module './routes/ResizableGrid' { - const createFileRoute: CreateFileRoute< - '/ResizableGrid', - FileRoutesByPath['/ResizableGrid']['parentRoute'], - FileRoutesByPath['/ResizableGrid']['id'], - FileRoutesByPath['/ResizableGrid']['path'], - FileRoutesByPath['/ResizableGrid']['fullPath'] - > -} -declare module './routes/RowGrouping' { - const createFileRoute: CreateFileRoute< - '/RowGrouping', - FileRoutesByPath['/RowGrouping']['parentRoute'], - FileRoutesByPath['/RowGrouping']['id'], - FileRoutesByPath['/RowGrouping']['path'], - FileRoutesByPath['/RowGrouping']['fullPath'] - > -} -declare module './routes/RowsReordering' { - const createFileRoute: CreateFileRoute< - '/RowsReordering', - FileRoutesByPath['/RowsReordering']['parentRoute'], - FileRoutesByPath['/RowsReordering']['id'], - FileRoutesByPath['/RowsReordering']['path'], - FileRoutesByPath['/RowsReordering']['fullPath'] - > -} -declare module './routes/ScrollToCell' { - const createFileRoute: CreateFileRoute< - '/ScrollToCell', - FileRoutesByPath['/ScrollToCell']['parentRoute'], - FileRoutesByPath['/ScrollToCell']['id'], - FileRoutesByPath['/ScrollToCell']['path'], - FileRoutesByPath['/ScrollToCell']['fullPath'] - > -} -declare module './routes/TreeView' { - const createFileRoute: CreateFileRoute< - '/TreeView', - FileRoutesByPath['/TreeView']['parentRoute'], - FileRoutesByPath['/TreeView']['id'], - FileRoutesByPath['/TreeView']['path'], - FileRoutesByPath['/TreeView']['fullPath'] - > -} -declare module './routes/VariableRowHeight' { - const createFileRoute: CreateFileRoute< - '/VariableRowHeight', - FileRoutesByPath['/VariableRowHeight']['parentRoute'], - FileRoutesByPath['/VariableRowHeight']['id'], - FileRoutesByPath['/VariableRowHeight']['path'], - FileRoutesByPath['/VariableRowHeight']['fullPath'] - > -} - const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, AllFeaturesRoute: AllFeaturesRoute, diff --git a/website/router.tsx b/website/router.tsx new file mode 100644 index 0000000000..ad95476652 --- /dev/null +++ b/website/router.tsx @@ -0,0 +1,26 @@ +import { createHashHistory, createRouter, ErrorComponent } from '@tanstack/react-router'; + +import { routeTree } from './routeTree.gen'; + +export const router = createRouter({ + routeTree, + history: createHashHistory(), + caseSensitive: true, + defaultErrorComponent: ErrorComponent, + defaultNotFoundComponent: NotFound, + defaultPendingMinMs: 0, + defaultPreload: 'intent', + defaultStructuralSharing: true, + scrollRestoration: true +}); + +// Register the router instance for type safety +declare module '@tanstack/react-router' { + interface Register { + router: typeof router; + } +} + +function NotFound() { + return 'Nothing to see here'; +} diff --git a/website/routes/AllFeatures.tsx b/website/routes/AllFeatures.tsx index a603269e59..6c907a8d28 100644 --- a/website/routes/AllFeatures.tsx +++ b/website/routes/AllFeatures.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { faker } from '@faker-js/faker'; +import { createFileRoute } from '@tanstack/react-router'; import { css } from 'ecij'; import clsx from 'clsx'; @@ -8,7 +9,7 @@ import type { CalculatedColumn, CellCopyArgs, CellPasteArgs, Column, FillEvent } import { textEditorClassname } from '../../src/editors/renderTextEditor'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/AllFeatures')({ component: AllFeatures, loader() { rows ??= createRows(); diff --git a/website/routes/Animation.tsx b/website/routes/Animation.tsx index 7532f160fa..fa42635ac7 100644 --- a/website/routes/Animation.tsx +++ b/website/routes/Animation.tsx @@ -1,10 +1,11 @@ import { useState } from 'react'; +import { createFileRoute } from '@tanstack/react-router'; import { css } from 'ecij'; import { DataGrid, type Column } from '../../src'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/Animation')({ component: Animation }); diff --git a/website/routes/CellNavigation.tsx b/website/routes/CellNavigation.tsx index ffc391329f..cb885461a4 100644 --- a/website/routes/CellNavigation.tsx +++ b/website/routes/CellNavigation.tsx @@ -1,10 +1,11 @@ import { useId, useState } from 'react'; +import { createFileRoute } from '@tanstack/react-router'; import { DataGrid } from '../../src'; import type { CellKeyboardEvent, CellKeyDownArgs, Column } from '../../src'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/CellNavigation')({ component: CellNavigation }); diff --git a/website/routes/ColumnGrouping.tsx b/website/routes/ColumnGrouping.tsx index 6c2bcde5ca..8fa17987c7 100644 --- a/website/routes/ColumnGrouping.tsx +++ b/website/routes/ColumnGrouping.tsx @@ -1,8 +1,10 @@ +import { createFileRoute } from '@tanstack/react-router'; + import { DataGrid, type ColumnOrColumnGroup } from '../../src'; import { renderCoordinates } from '../renderers'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/ColumnGrouping')({ component: ColumnGrouping }); diff --git a/website/routes/ColumnSpanning.tsx b/website/routes/ColumnSpanning.tsx index 35339790b2..ee26541988 100644 --- a/website/routes/ColumnSpanning.tsx +++ b/website/routes/ColumnSpanning.tsx @@ -1,10 +1,11 @@ +import { createFileRoute } from '@tanstack/react-router'; import { css } from 'ecij'; import { DataGrid, type Column } from '../../src'; import { renderCoordinates } from '../renderers'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/ColumnSpanning')({ component: ColumnSpanning }); diff --git a/website/routes/ColumnsReordering.tsx b/website/routes/ColumnsReordering.tsx index 17004ec25f..03a981303d 100644 --- a/website/routes/ColumnsReordering.tsx +++ b/website/routes/ColumnsReordering.tsx @@ -1,10 +1,11 @@ import { useCallback, useMemo, useState } from 'react'; +import { createFileRoute } from '@tanstack/react-router'; import { DataGrid, type Column, type ColumnWidths, type SortColumn } from '../../src'; import { startViewTransition } from '../utils'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/ColumnsReordering')({ component: ColumnsReordering }); diff --git a/website/routes/CommonFeatures.tsx b/website/routes/CommonFeatures.tsx index 0d934a526c..b24c187f42 100644 --- a/website/routes/CommonFeatures.tsx +++ b/website/routes/CommonFeatures.tsx @@ -1,6 +1,7 @@ import { useMemo, useRef, useState } from 'react'; import { createPortal, flushSync } from 'react-dom'; import { faker } from '@faker-js/faker'; +import { createFileRoute } from '@tanstack/react-router'; import { css } from 'ecij'; import { @@ -17,7 +18,7 @@ import { textEditorClassname } from '../../src/editors/renderTextEditor'; import { exportToCsv, exportToPdf } from '../utils'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/CommonFeatures')({ component: CommonFeatures }); diff --git a/website/routes/ContextMenu.tsx b/website/routes/ContextMenu.tsx index 58655488e1..df004e35e2 100644 --- a/website/routes/ContextMenu.tsx +++ b/website/routes/ContextMenu.tsx @@ -1,12 +1,13 @@ import { useLayoutEffect, useReducer, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { faker } from '@faker-js/faker'; +import { createFileRoute } from '@tanstack/react-router'; import { css } from 'ecij'; import { DataGrid, type Column } from '../../src'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/ContextMenu')({ component: ContextMenuDemo }); diff --git a/website/routes/CustomizableRenderers.tsx b/website/routes/CustomizableRenderers.tsx index ec4a2aa47d..0707751ef8 100644 --- a/website/routes/CustomizableRenderers.tsx +++ b/website/routes/CustomizableRenderers.tsx @@ -1,4 +1,5 @@ import { useMemo, useState } from 'react'; +import { createFileRoute } from '@tanstack/react-router'; import { css } from 'ecij'; import { Row as BaseRow, Cell, DataGrid, renderTextEditor, SelectColumn } from '../../src'; @@ -12,7 +13,7 @@ import type { } from '../../src'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/CustomizableRenderers')({ component: CustomizableRenderers }); diff --git a/website/routes/HeaderFilters.tsx b/website/routes/HeaderFilters.tsx index f4b26fd95b..ae66c0dec9 100644 --- a/website/routes/HeaderFilters.tsx +++ b/website/routes/HeaderFilters.tsx @@ -1,12 +1,13 @@ import { createContext, useContext, useMemo, useState } from 'react'; import { faker } from '@faker-js/faker'; +import { createFileRoute } from '@tanstack/react-router'; import { css } from 'ecij'; import { DataGrid, type Column, type RenderHeaderCellProps } from '../../src'; import type { Omit } from '../../src/types'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/HeaderFilters')({ component: HeaderFilters }); diff --git a/website/routes/InfiniteScrolling.tsx b/website/routes/InfiniteScrolling.tsx index 2b7ec28646..ecea0424ae 100644 --- a/website/routes/InfiniteScrolling.tsx +++ b/website/routes/InfiniteScrolling.tsx @@ -1,11 +1,12 @@ import { useState } from 'react'; import { faker } from '@faker-js/faker'; +import { createFileRoute } from '@tanstack/react-router'; import { css } from 'ecij'; import { DataGrid, type Column } from '../../src'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/InfiniteScrolling')({ component: InfiniteScrolling }); diff --git a/website/routes/MasterDetail.tsx b/website/routes/MasterDetail.tsx index b5d41d0945..0a81104457 100644 --- a/website/routes/MasterDetail.tsx +++ b/website/routes/MasterDetail.tsx @@ -1,12 +1,13 @@ import { useMemo, useState } from 'react'; import { faker } from '@faker-js/faker'; +import { createFileRoute } from '@tanstack/react-router'; import { css } from 'ecij'; import { DataGrid, type Column, type Direction, type RowsChangeData } from '../../src'; import { CellExpanderFormatter } from '../components'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/MasterDetail')({ component: MasterDetail }); diff --git a/website/routes/MillionCells.tsx b/website/routes/MillionCells.tsx index 8b798e19f8..fbd9d16705 100644 --- a/website/routes/MillionCells.tsx +++ b/website/routes/MillionCells.tsx @@ -1,8 +1,10 @@ +import { createFileRoute } from '@tanstack/react-router'; + import { DataGrid, type Column } from '../../src'; import { renderCoordinates } from '../renderers'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/MillionCells')({ component: MillionCells }); diff --git a/website/routes/NoRows.tsx b/website/routes/NoRows.tsx index ccc9c3c61c..0d04b8ddcc 100644 --- a/website/routes/NoRows.tsx +++ b/website/routes/NoRows.tsx @@ -1,10 +1,11 @@ import { useState } from 'react'; +import { createFileRoute } from '@tanstack/react-router'; import { css } from 'ecij'; import { DataGrid, SelectColumn, type Column } from '../../src'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/NoRows')({ component: NoRows }); diff --git a/website/routes/ResizableGrid.tsx b/website/routes/ResizableGrid.tsx index 3859fc100f..cd5fe647c6 100644 --- a/website/routes/ResizableGrid.tsx +++ b/website/routes/ResizableGrid.tsx @@ -1,8 +1,10 @@ +import { createFileRoute } from '@tanstack/react-router'; + import { DataGrid, type Column } from '../../src'; import { renderCoordinates } from '../renderers'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/ResizableGrid')({ component: ResizableGrid }); diff --git a/website/routes/RowGrouping.tsx b/website/routes/RowGrouping.tsx index d260902718..0980c91644 100644 --- a/website/routes/RowGrouping.tsx +++ b/website/routes/RowGrouping.tsx @@ -1,11 +1,12 @@ import { useState } from 'react'; import { faker } from '@faker-js/faker'; +import { createFileRoute } from '@tanstack/react-router'; import { css } from 'ecij'; import { SelectColumn, TreeDataGrid, type Column } from '../../src'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/RowGrouping')({ component: RowGrouping }); diff --git a/website/routes/RowsReordering.tsx b/website/routes/RowsReordering.tsx index 8382faf053..9d0f250828 100644 --- a/website/routes/RowsReordering.tsx +++ b/website/routes/RowsReordering.tsx @@ -1,11 +1,12 @@ import { useCallback, useState } from 'react'; +import { createFileRoute } from '@tanstack/react-router'; import { DataGrid, renderTextEditor, type Column, type RenderRowProps } from '../../src'; import { DraggableRowRenderer } from '../components'; import { startViewTransition } from '../utils'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/RowsReordering')({ component: RowsReordering }); diff --git a/website/routes/ScrollToCell.tsx b/website/routes/ScrollToCell.tsx index b442f37333..479d21b019 100644 --- a/website/routes/ScrollToCell.tsx +++ b/website/routes/ScrollToCell.tsx @@ -1,4 +1,5 @@ import { useRef, useState } from 'react'; +import { createFileRoute } from '@tanstack/react-router'; import { css } from 'ecij'; import clsx from 'clsx'; @@ -6,7 +7,7 @@ import { DataGrid, type Column, type DataGridHandle } from '../../src'; import { renderCoordinates } from '../renderers'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/ScrollToCell')({ component: ScrollToCell }); diff --git a/website/routes/TreeView.tsx b/website/routes/TreeView.tsx index 47fd20fa4d..323924ce5f 100644 --- a/website/routes/TreeView.tsx +++ b/website/routes/TreeView.tsx @@ -1,11 +1,12 @@ import { useMemo, useReducer, useState } from 'react'; +import { createFileRoute } from '@tanstack/react-router'; import { css } from 'ecij'; import { DataGrid, type Column } from '../../src'; import { CellExpanderFormatter, ChildRowDeleteButton } from '../components'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/TreeView')({ component: TreeView }); diff --git a/website/routes/VariableRowHeight.tsx b/website/routes/VariableRowHeight.tsx index 6277e7e3aa..23b05f8e92 100644 --- a/website/routes/VariableRowHeight.tsx +++ b/website/routes/VariableRowHeight.tsx @@ -1,8 +1,10 @@ +import { createFileRoute } from '@tanstack/react-router'; + import { DataGrid, type Column } from '../../src'; import { renderCoordinates } from '../renderers'; import { useDirection } from '../directionContext'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/VariableRowHeight')({ component: VariableRowHeight }); diff --git a/website/routes/index.tsx b/website/routes/index.tsx index 6191d381ab..a194cf7cb5 100644 --- a/website/routes/index.tsx +++ b/website/routes/index.tsx @@ -1,6 +1,6 @@ -import { redirect } from '@tanstack/react-router'; +import { redirect, createFileRoute } from '@tanstack/react-router'; -export const Route = createFileRoute({ +export const Route = createFileRoute('/')({ beforeLoad() { // eslint-disable-next-line @typescript-eslint/only-throw-error throw redirect({ to: '/CommonFeatures' }); From 1c32fe53602382207658eb0f29c007b2eb6a811e Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 24 Feb 2026 18:53:35 +0000 Subject: [PATCH 2/2] increase actionTimeout --- vite.config.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/vite.config.ts b/vite.config.ts index 901bcf8a3e..346f5c188a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,6 @@ import { tanstackRouter } from '@tanstack/router-plugin/vite'; import react from '@vitejs/plugin-react'; -import { playwright } from '@vitest/browser-playwright'; +import { playwright, type PlaywrightProviderOptions } from '@vitest/browser-playwright'; import { ecij } from 'ecij/plugin'; import { defineConfig, type ViteUserConfig } from 'vitest/config'; import type { BrowserCommand, BrowserInstanceOption } from 'vitest/node'; @@ -43,14 +43,18 @@ const viewport = { width: 1920, height: 1080 } as const; // vitest modifies the instance objects, so we cannot rely on static objects function getInstances(): BrowserInstanceOption[] { + const opts: PlaywrightProviderOptions = { + actionTimeout: 2000, + contextOptions: { + viewport + } + }; + return [ { browser: 'chromium', provider: playwright({ - actionTimeout: 1000, - contextOptions: { - viewport - }, + ...opts, launchOptions: { channel: 'chromium' } @@ -58,12 +62,7 @@ function getInstances(): BrowserInstanceOption[] { }, { browser: 'firefox', - provider: playwright({ - actionTimeout: 1000, - contextOptions: { - viewport - } - }), + provider: playwright(opts), // TODO: remove when FF tests are stable fileParallelism: false }