From 4c4af3d3a80e3acc683a4d0d686c2a1fa247ae1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:07:33 +0100 Subject: [PATCH 1/9] Bump @biomejs/biome from 1.8.3 to 1.9.3 (#3612) * Bump @biomejs/biome from 1.8.3 to 1.9.3 Bumps [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) from 1.8.3 to 1.9.3. - [Release notes](https://github.com/biomejs/biome/releases) - [Changelog](https://github.com/biomejs/biome/blob/main/CHANGELOG.md) - [Commits](https://github.com/biomejs/biome/commits/cli/v1.9.3/packages/@biomejs/biome) --- updated-dependencies: - dependency-name: "@biomejs/biome" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * update config, fix lints --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nicolas Stepien --- .prettierignore | 1 + .vscode/settings.json | 2 +- biome.json | 65 ++++++++++++++++++++--- package.json | 3 +- rollup.config.js | 3 +- test/browser/column/colSpan.test.ts | 2 +- test/browser/keyboardNavigation.test.tsx | 8 +-- test/browser/rowHeight.test.ts | 2 +- test/browser/scrollToCell.test.tsx | 2 +- test/browser/virtualization.test.ts | 6 +-- website/routes/ColumnSpanning.lazy.tsx | 2 +- website/routes/MillionCells.lazy.tsx | 2 +- website/routes/ResizableGrid.lazy.tsx | 2 +- website/routes/VariableRowHeight.lazy.tsx | 2 +- 14 files changed, 77 insertions(+), 25 deletions(-) diff --git a/.prettierignore b/.prettierignore index 242f7524ea..81a70c147d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ *.js *.json +*.css /website/routeTree.gen.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 438daedca0..42a3f5a81f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,7 +4,7 @@ }, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, - "[javascript][json][jsonc]": { + "[javascript][json][jsonc][css]": { "editor.defaultFormatter": "biomejs.biome" }, "typescript.enablePromptUseWorkspaceTsdk": true, diff --git a/biome.json b/biome.json index 621760b362..b80a99ac8f 100644 --- a/biome.json +++ b/biome.json @@ -28,12 +28,8 @@ } }, "css": { - "linter": { - "enabled": false - }, "formatter": { - "enabled": false, - "indentStyle": "space" + "quoteStyle": "single" } }, "linter": { @@ -48,6 +44,7 @@ "noDistractingElements": "warn", "noHeaderScope": "warn", "noInteractiveElementToNoninteractiveRole": "warn", + "noLabelWithoutControl": "off", "noNoninteractiveElementToInteractiveRole": "warn", "noNoninteractiveTabindex": "warn", "noPositiveTabindex": "warn", @@ -59,12 +56,15 @@ "useAriaActivedescendantWithTabindex": "warn", "useAriaPropsForRole": "warn", "useButtonType": "warn", + "useFocusableInteractive": "off", + "useGenericFontNames": "warn", "useHeadingContent": "warn", "useHtmlLang": "warn", "useIframeTitle": "warn", "useKeyWithClickEvents": "off", "useKeyWithMouseEvents": "warn", "useMediaCaption": "off", + "useSemanticElements": "off", "useValidAnchor": "warn", "useValidAriaProps": "warn", "useValidAriaRole": "warn", @@ -88,13 +88,16 @@ "noUselessLabel": "warn", "noUselessLoneBlockStatements": "warn", "noUselessRename": "warn", + "noUselessStringConcat": "warn", "noUselessSwitchCase": "warn", "noUselessTernary": "warn", "noUselessThisAlias": "warn", "noUselessTypeConstraint": "warn", + "noUselessUndefinedInitialization": "warn", "noVoid": "warn", "noWith": "warn", "useArrowFunction": "warn", + "useDateNow": "warn", "useFlatMap": "warn", "useLiteralKeys": "warn", "useOptionalChain": "warn", @@ -113,11 +116,15 @@ "noFlatMapIdentity": "warn", "noGlobalObjectCalls": "warn", "noInnerDeclarations": "warn", + "noInvalidBuiltinInstantiation": "warn", "noInvalidConstructorSuper": "warn", + "noInvalidDirectionInLinearGradient": "warn", + "noInvalidGridAreas": "warn", "noInvalidNewBuiltin": "warn", + "noInvalidPositionAtImportRule": "warn", "noInvalidUseBeforeDeclaration": "warn", "noNewSymbol": "warn", - "noNodejsModules": "off", + "noNodejsModules": "warn", "noNonoctalDecimalEscape": "warn", "noPrecisionLoss": "warn", "noRenderReturnValue": "warn", @@ -125,12 +132,19 @@ "noSetterReturn": "warn", "noStringCaseMismatch": "warn", "noSwitchDeclarations": "warn", + "noUndeclaredDependencies": "warn", "noUndeclaredVariables": "off", + "noUnknownFunction": "warn", + "noUnknownMediaFeatureName": "warn", + "noUnknownProperty": "warn", + "noUnknownUnit": "warn", + "noUnmatchableAnbSelector": "warn", "noUnnecessaryContinue": "warn", "noUnreachable": "warn", "noUnreachableSuper": "warn", "noUnsafeFinally": "warn", "noUnsafeOptionalChaining": "warn", + "noUnusedFunctionParameters": "off", "noUnusedImports": "warn", "noUnusedLabels": "warn", "noUnusedPrivateClassMembers": "warn", @@ -140,6 +154,7 @@ "useArrayLiterals": "warn", "useExhaustiveDependencies": "off", "useHookAtTopLevel": "warn", + "useImportExtensions": "off", "useIsNan": "warn", "useJsxKeyInIterable": "off", "useValidForDirection": "warn", @@ -149,7 +164,8 @@ "noAccumulatingSpread": "warn", "noBarrelFile": "off", "noDelete": "warn", - "noReExportAll": "off" + "noReExportAll": "off", + "useTopLevelRegex": "warn" }, "security": { "noDangerouslySetInnerHtml": "warn", @@ -160,6 +176,7 @@ "noArguments": "warn", "noCommaOperator": "warn", "noDefaultExport": "off", + "noDoneCallback": "warn", "noImplicitBoolean": "off", "noInferrableTypes": "warn", "noNamespace": "warn", @@ -173,13 +190,17 @@ "noUnusedTemplateLiteral": "warn", "noUselessElse": "warn", "noVar": "warn", + "noYodaExpression": "warn", "useAsConstAssertion": "warn", "useBlockStatements": "off", "useCollapsedElseIf": "warn", "useConsistentArrayType": "warn", + "useConsistentBuiltinInstantiation": "warn", "useConst": "warn", "useDefaultParameterLast": "off", + "useDefaultSwitchClause": "warn", "useEnumInitializers": "warn", + "useExplicitLengthCheck": "off", "useExponentiationOperator": "warn", "useExportType": "warn", "useFilenamingConvention": "off", @@ -199,12 +220,14 @@ "useSingleCaseStatement": "off", "useSingleVarDeclarator": "warn", "useTemplate": "warn", + "useThrowNewError": "warn", + "useThrowOnlyError": "warn", "useWhile": "warn" }, "suspicious": { "noApproximativeNumericConstant": "warn", "noArrayIndexKey": "off", - "noAssignInExpressions": "off", + "noAssignInExpressions": "warn", "noAsyncPromiseExecutor": "warn", "noCatchAssign": "warn", "noClassAssign": "warn", @@ -212,19 +235,25 @@ "noCompareNegZero": "warn", "noConfusingLabels": "warn", "noConfusingVoidType": "warn", + "noConsole": "warn", "noConsoleLog": "warn", "noConstEnum": "warn", "noControlCharactersInRegex": "warn", "noDebugger": "warn", "noDoubleEquals": "warn", + "noDuplicateAtImportRules": "warn", "noDuplicateCase": "warn", "noDuplicateClassMembers": "warn", + "noDuplicateFontNames": "warn", "noDuplicateJsxProps": "warn", "noDuplicateObjectKeys": "warn", "noDuplicateParameters": "warn", + "noDuplicateSelectorsKeyframeBlock": "warn", "noDuplicateTestHooks": "warn", + "noEmptyBlock": "warn", "noEmptyBlockStatements": "off", "noEmptyInterface": "warn", + "noEvolvingTypes": "off", "noExplicitAny": "off", "noExportsInTest": "warn", "noExtraNonNullAssertion": "warn", @@ -236,15 +265,19 @@ "noGlobalIsNan": "warn", "noImplicitAnyLet": "off", "noImportAssign": "warn", + "noImportantInKeyframe": "warn", "noLabelVar": "warn", "noMisleadingCharacterClass": "warn", "noMisleadingInstantiator": "warn", + "noMisplacedAssertion": "off", "noMisrefactoredShorthandAssign": "warn", "noPrototypeBuiltins": "warn", + "noReactSpecificProps": "off", "noRedeclare": "warn", "noRedundantUseStrict": "warn", "noSelfCompare": "warn", "noShadowRestrictedNames": "warn", + "noShorthandPropertyOverrides": "warn", "noSkippedTests": "warn", "noSparseArray": "warn", "noSuspiciousSemicolonInJsx": "warn", @@ -253,9 +286,11 @@ "noUnsafeNegation": "warn", "useAwait": "warn", "useDefaultSwitchClauseLast": "warn", + "useErrorMessage": "warn", "useGetterReturn": "warn", "useIsArray": "warn", "useNamespaceKeyword": "warn", + "useNumberToFixedDigitsArgument": "warn", "useValidTypeof": "warn" } } @@ -264,11 +299,25 @@ "enabled": false }, "overrides": [ + { + "include": ["**/*.test.*"], + "linter": { + "rules": { + "performance": { + "useTopLevelRegex": "off" + } + } + } + }, { "include": ["**/*.js"], "linter": { "rules": { + "correctness": { + "noNodejsModules": "off" + }, "suspicious": { + "noConsole": "off", "noConsoleLog": "off" } } diff --git a/package.json b/package.json index 6899bad75d..5534c7dd72 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", "@babel/runtime": "^7.21.5", - "@biomejs/biome": "1.8.3", + "@biomejs/biome": "1.9.3", "@eslint/compat": "^1.1.1", "@faker-js/faker": "^9.0.0", "@ianvs/prettier-plugin-sort-imports": "^4.0.2", @@ -86,6 +86,7 @@ "@wyw-in-js/rollup": "^0.5.0", "@wyw-in-js/vite": "^0.5.0", "babel-plugin-optimize-clsx": "^2.6.2", + "browserslist": "^4.24.0", "eslint": "^9.11.1", "eslint-plugin-jest-dom": "^5.0.1", "eslint-plugin-react": "^7.36.1", diff --git a/rollup.config.js b/rollup.config.js index 9aac4458c7..d4296f6924 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -6,6 +6,7 @@ import nodeResolve from '@rollup/plugin-node-resolve'; import pkg from './package.json' with { type: 'json' }; const extensions = ['.ts', '.tsx']; +const annotationRegexp = /^[@#]__.+__$/; export default { input: './src/index.ts', @@ -42,7 +43,7 @@ export default { // remove all comments except terser annotations // https://github.com/terser/terser#annotations // https://babeljs.io/docs/en/options#shouldprintcomment - shouldPrintComment: (comment) => /^[@#]__.+__$/.test(comment) + shouldPrintComment: (comment) => annotationRegexp.test(comment) }), nodeResolve({ extensions }) ] diff --git a/test/browser/column/colSpan.test.ts b/test/browser/column/colSpan.test.ts index dcdc0d897d..9273d2199a 100644 --- a/test/browser/column/colSpan.test.ts +++ b/test/browser/column/colSpan.test.ts @@ -7,7 +7,7 @@ describe('colSpan', () => { function setupColSpanGrid(colCount = 15) { type Row = number; const columns: Column[] = []; - const rows: readonly Row[] = [...Array(10).keys()]; + const rows: readonly Row[] = Array.from({ length: 10 }, (_, i) => i); for (let i = 0; i < colCount; i++) { const key = String(i); diff --git a/test/browser/keyboardNavigation.test.tsx b/test/browser/keyboardNavigation.test.tsx index 82e4883acf..5be9d45a62 100644 --- a/test/browser/keyboardNavigation.test.tsx +++ b/test/browser/keyboardNavigation.test.tsx @@ -13,7 +13,7 @@ import { type Row = undefined; -const rows: readonly Row[] = Array(100); +const rows: readonly Row[] = new Array(100); const topSummaryRows: readonly Row[] = [undefined]; const bottomSummaryRows: readonly Row[] = [undefined, undefined]; @@ -129,7 +129,7 @@ test('arrow and tab navigation', async () => { }); test('grid enter/exit', async () => { - setup({ columns, rows: Array(5), bottomSummaryRows }); + setup({ columns, rows: new Array(5), bottomSummaryRows }); // no initial selection expect(getSelectedCell()).not.toBeInTheDocument(); @@ -168,7 +168,7 @@ test('grid enter/exit', async () => { }); test('navigation with focusable cell renderer', async () => { - setup({ columns, rows: Array(1), bottomSummaryRows }); + setup({ columns, rows: new Array(1), bottomSummaryRows }); await userEvent.tab(); await userEvent.keyboard('{arrowdown}'); validateCellPosition(0, 1); @@ -209,7 +209,7 @@ test('navigation when header and summary rows have focusable elements', async () } ]; - setup({ columns, rows: Array(2), bottomSummaryRows }); + setup({ columns, rows: new Array(2), bottomSummaryRows }); await userEvent.tab(); // should set focus on the header filter diff --git a/test/browser/rowHeight.test.ts b/test/browser/rowHeight.test.ts index 7c80f32ac1..5256a7fb6f 100644 --- a/test/browser/rowHeight.test.ts +++ b/test/browser/rowHeight.test.ts @@ -8,7 +8,7 @@ type Row = number; function setupGrid(rowHeight: DataGridProps['rowHeight']) { const columns: Column[] = []; - const rows: readonly Row[] = [...Array(50).keys()]; + const rows: readonly Row[] = Array.from({ length: 50 }, (_, i) => i); for (let i = 0; i < 5; i++) { const key = String(i); diff --git a/test/browser/scrollToCell.test.tsx b/test/browser/scrollToCell.test.tsx index 1b57b9cbed..2d24c83a9e 100644 --- a/test/browser/scrollToCell.test.tsx +++ b/test/browser/scrollToCell.test.tsx @@ -9,7 +9,7 @@ import { getGrid } from './utils'; type Row = undefined; -const rows: readonly Row[] = Array(50); +const rows: readonly Row[] = new Array(50); const summaryRows: readonly Row[] = [undefined, undefined]; const columns: Column[] = []; diff --git a/test/browser/virtualization.test.ts b/test/browser/virtualization.test.ts index 24540cea10..cf1c5eb774 100644 --- a/test/browser/virtualization.test.ts +++ b/test/browser/virtualization.test.ts @@ -21,9 +21,9 @@ function setupGrid( summaryRowCount = 0 ) { const columns: Column[] = []; - const rows = Array(rowCount); - const topSummaryRows = Array(summaryRowCount).fill(null); - const bottomSummaryRows = Array(summaryRowCount).fill(null); + const rows = new Array(rowCount); + const topSummaryRows = new Array(summaryRowCount).fill(null); + const bottomSummaryRows = new Array(summaryRowCount).fill(null); for (let i = 0; i < columnCount; i++) { const key = String(i); diff --git a/website/routes/ColumnSpanning.lazy.tsx b/website/routes/ColumnSpanning.lazy.tsx index 48fffc8f86..e393e2e26d 100644 --- a/website/routes/ColumnSpanning.lazy.tsx +++ b/website/routes/ColumnSpanning.lazy.tsx @@ -12,7 +12,7 @@ export const Route = createLazyFileRoute('/ColumnSpanning')({ }); type Row = number; -const rows: readonly Row[] = [...Array(100).keys()]; +const rows: readonly Row[] = Array.from({ length: 100 }, (_, i) => i); const colSpanClassname = css` background-color: #ffb300; diff --git a/website/routes/MillionCells.lazy.tsx b/website/routes/MillionCells.lazy.tsx index 50a88222ae..fb3b2975bc 100644 --- a/website/routes/MillionCells.lazy.tsx +++ b/website/routes/MillionCells.lazy.tsx @@ -11,7 +11,7 @@ export const Route = createLazyFileRoute('/MillionCells')({ }); type Row = number; -const rows: readonly Row[] = [...Array(1000).keys()]; +const rows: readonly Row[] = Array.from({ length: 1000 }, (_, i) => i); function MillionCells() { const direction = useDirection(); diff --git a/website/routes/ResizableGrid.lazy.tsx b/website/routes/ResizableGrid.lazy.tsx index 92dc4c8901..e841b953ec 100644 --- a/website/routes/ResizableGrid.lazy.tsx +++ b/website/routes/ResizableGrid.lazy.tsx @@ -10,7 +10,7 @@ export const Route = createLazyFileRoute('/ResizableGrid')({ }); type Row = number; -const rows: readonly Row[] = [...Array(100).keys()]; +const rows: readonly Row[] = Array.from({ length: 100 }, (_, i) => i); const columns: Column[] = []; diff --git a/website/routes/VariableRowHeight.lazy.tsx b/website/routes/VariableRowHeight.lazy.tsx index 8b9134698a..af02fc4126 100644 --- a/website/routes/VariableRowHeight.lazy.tsx +++ b/website/routes/VariableRowHeight.lazy.tsx @@ -11,7 +11,7 @@ export const Route = createLazyFileRoute('/VariableRowHeight')({ }); type Row = number; -const rows: readonly Row[] = [...Array(500).keys()]; +const rows: readonly Row[] = Array.from({ length: 500 }, (_, i) => i); function VariableRowHeight() { const direction = useDirection(); From 286f14a0f68439c2cbfb425f0d6c2dce6a1451d3 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 9 Oct 2024 16:29:16 -0500 Subject: [PATCH 2/9] Implement download without `react-dom/server` (#3613) * Implement download without `react-dom/server'` * Use `flushSync` * Remove `ExportButton` component * Inline grid element * Address comments * Address comments --- website/exportUtils.tsx | 35 ++------- website/routes/CommonFeatures.lazy.tsx | 102 +++++++++++++------------ 2 files changed, 60 insertions(+), 77 deletions(-) diff --git a/website/exportUtils.tsx b/website/exportUtils.tsx index 9628c4f185..072eca749d 100644 --- a/website/exportUtils.tsx +++ b/website/exportUtils.tsx @@ -1,13 +1,5 @@ -import { cloneElement } from 'react'; -import type { ReactElement } from 'react'; - -import type { DataGridProps } from '../src'; - -export async function exportToCsv( - gridElement: ReactElement>, - fileName: string -) { - const { head, body, foot } = await getGridContent(gridElement); +export function exportToCsv(gridEl: HTMLDivElement, fileName: string) { + const { head, body, foot } = getGridContent(gridEl); const content = [...head, ...body, ...foot] .map((cells) => cells.map(serialiseCellValue).join(',')) .join('\n'); @@ -15,14 +7,11 @@ export async function exportToCsv( downloadFile(fileName, new Blob([content], { type: 'text/csv;charset=utf-8;' })); } -export async function exportToPdf( - gridElement: ReactElement>, - fileName: string -) { - const [{ jsPDF }, autoTable, { head, body, foot }] = await Promise.all([ +export async function exportToPdf(gridEl: HTMLDivElement, fileName: string) { + const { head, body, foot } = getGridContent(gridEl); + const [{ jsPDF }, { default: autoTable }] = await Promise.all([ import('jspdf'), - (await import('jspdf-autotable')).default, - await getGridContent(gridElement) + import('jspdf-autotable') ]); const doc = new jsPDF({ orientation: 'l', @@ -40,15 +29,7 @@ export async function exportToPdf( doc.save(fileName); } -async function getGridContent(gridElement: ReactElement>) { - const { renderToStaticMarkup } = await import('react-dom/server'); - const grid = document.createElement('div'); - grid.innerHTML = renderToStaticMarkup( - cloneElement(gridElement, { - enableVirtualization: false - }) - ); - +function getGridContent(gridEl: HTMLDivElement) { return { head: getRows('.rdg-header-row'), body: getRows('.rdg-row:not(.rdg-summary-row)'), @@ -56,7 +37,7 @@ async function getGridContent(gridElement: ReactElement(selector)).map((gridRow) => { + return Array.from(gridEl.querySelectorAll(selector)).map((gridRow) => { return Array.from(gridRow.querySelectorAll('.rdg-cell')).map( (gridCell) => gridCell.innerText ); diff --git a/website/routes/CommonFeatures.lazy.tsx b/website/routes/CommonFeatures.lazy.tsx index 53c9dbbd24..a019eeacf5 100644 --- a/website/routes/CommonFeatures.lazy.tsx +++ b/website/routes/CommonFeatures.lazy.tsx @@ -1,5 +1,5 @@ -import { useMemo, useState } from 'react'; -import { createPortal } from 'react-dom'; +import { useMemo, useRef, useState } from 'react'; +import { createPortal, flushSync } from 'react-dom'; import { faker } from '@faker-js/faker'; import { createLazyFileRoute } from '@tanstack/react-router'; import { css } from '@linaria/core'; @@ -9,6 +9,7 @@ import DataGrid, { SelectColumn, textEditor, type Column, + type DataGridHandle, type SortColumn } from '../../src'; import { textEditorClassname } from '../../src/editors/textEditor'; @@ -310,6 +311,8 @@ function CommonFeatures() { const [rows, setRows] = useState(createRows); const [sortColumns, setSortColumns] = useState([]); const [selectedRows, setSelectedRows] = useState((): ReadonlySet => new Set()); + const [isExporting, setIsExporting] = useState(false); + const gridRef = useRef(null); const countries = useMemo((): readonly string[] => { return [...new Set(rows.map((r) => r.country))].sort(new Intl.Collator().compare); @@ -342,61 +345,60 @@ function CommonFeatures() { }); }, [rows, sortColumns]); - const gridElement = ( - - ); + function handleExportToCsv() { + flushSync(() => { + setIsExporting(true); + }); + + exportToCsv(gridRef.current!.element!, 'CommonFeatures.csv'); + + flushSync(() => { + setIsExporting(false); + }); + } + + async function handleExportToPdf() { + flushSync(() => { + setIsExporting(true); + }); + + await exportToPdf(gridRef.current!.element!, 'CommonFeatures.pdf'); + + flushSync(() => { + setIsExporting(false); + }); + } return ( <>
- exportToCsv(gridElement, 'CommonFeatures.csv')}> + +
- {gridElement} + ); } - -function ExportButton({ - onExport, - children -}: { - onExport: () => Promise; - children: React.ReactNode; -}) { - const [exporting, setExporting] = useState(false); - return ( - - ); -} From 106b20c0b569c85d5d36016654b32d14dd03a962 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 12:12:20 +0100 Subject: [PATCH 3/9] Bump eslint-plugin-react-hooks from 4.6.2 to 5.0.0 (#3616) Bumps [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/HEAD/packages/eslint-plugin-react-hooks) from 4.6.2 to 5.0.0. - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/eslint-plugin-react-hooks@5.0.0/packages/eslint-plugin-react-hooks) --- updated-dependencies: - dependency-name: eslint-plugin-react-hooks dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5534c7dd72..f747a54137 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "eslint": "^9.11.1", "eslint-plugin-jest-dom": "^5.0.1", "eslint-plugin-react": "^7.36.1", - "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-sonarjs": "^2.0.2", "eslint-plugin-testing-library": "^6.3.0", "jspdf": "^2.5.1", From 9c57299162f3ea2eceb48cb0a002b339a7ef34c9 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien <567105+nstepien@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:01:52 +0100 Subject: [PATCH 4/9] Node 23 (#3618) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be5e64dab7..e87c7f77bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 23 check-latest: true - name: set up react 19 if: matrix.react == 19 From c3068ef0354339ecd913324c214b575378e8983d Mon Sep 17 00:00:00 2001 From: Liooo Date: Wed, 16 Oct 2024 18:47:13 +0900 Subject: [PATCH 5/9] fix typing --- src/Cell.tsx | 2 +- test/browser/renderers.test.tsx | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Cell.tsx b/src/Cell.tsx index f919176cc9..7b723a9e2e 100644 --- a/src/Cell.tsx +++ b/src/Cell.tsx @@ -124,7 +124,7 @@ function Cell( const CellComponent = memo(forwardRef(Cell)) as ( props: CellRendererProps & RefAttributes -) => JSX.Element; +) => React.JSX.Element; export default CellComponent; diff --git a/test/browser/renderers.test.tsx b/test/browser/renderers.test.tsx index 5ec9b8be18..071cd5aac4 100644 --- a/test/browser/renderers.test.tsx +++ b/test/browser/renderers.test.tsx @@ -18,8 +18,8 @@ import { getCells, getHeaderCells, getRows, setup } from './utils'; interface Row { id: number; - col1?: string; - col2?: string; + col1: string; + col2: string; } const noRows: readonly Row[] = []; @@ -131,21 +131,29 @@ test('fallback defined using both provider and renderers with no rows', () => { }); test('fallback defined using renderers prop with a row', () => { - setup({ columns, rows: [{ id: 1 }], renderers: { noRowsFallback: } }); + setup({ + columns, + rows: [{ id: 1, col1: 'col 1 value', col2: 'col 2 value' }], + renderers: { noRowsFallback: } + }); expect(getRows()).toHaveLength(1); expect(screen.queryByText('Local no rows fallback')).not.toBeInTheDocument(); }); test('fallback defined using provider with a row', () => { - setupProvider({ columns, rows: [{ id: 1 }] }); + setupProvider({ columns, rows: [{ id: 1, col1: 'col 1 value', col2: 'col 2 value' }] }); expect(getRows()).toHaveLength(1); expect(screen.queryByText('Global no rows fallback')).not.toBeInTheDocument(); }); test('fallback defined using both provider and renderers with a row', () => { - setupProvider({ columns, rows: [{ id: 1 }], renderers: { noRowsFallback: } }); + setupProvider({ + columns, + rows: [{ id: 1, col1: 'col 1 value', col2: 'col 2 value' }], + renderers: { noRowsFallback: } + }); expect(getRows()).toHaveLength(1); expect(screen.queryByText('Global no rows fallback')).not.toBeInTheDocument(); From 0e614dc2591ad2b9ff8faad229cc9f2c1191435c Mon Sep 17 00:00:00 2001 From: Liooo Date: Sun, 20 Oct 2024 16:16:43 +0900 Subject: [PATCH 6/9] allow passing style to cellRenderer --- src/Cell.tsx | 11 ++++++++++- src/types.ts | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Cell.tsx b/src/Cell.tsx index 7b723a9e2e..c35b8323f0 100644 --- a/src/Cell.tsx +++ b/src/Cell.tsx @@ -40,6 +40,7 @@ function Cell( onContextMenu, onRowChange, selectCell, + style, ...props }: CellRendererProps, ref: React.Ref @@ -93,6 +94,14 @@ function Cell( onRowChange(column, newRow); } + const cellStyle = getCellStyle(column, colSpan); + if (style) { + let key: keyof React.CSSProperties; + for (key in style) { + (cellStyle as any)[key] = style[key]; + } + } + return (
( ref={ref} tabIndex={tabIndex} className={className} - style={getCellStyle(column, colSpan)} + style={cellStyle} onClick={handleClick} onDoubleClick={handleDoubleClick} onContextMenu={handleContextMenu} diff --git a/src/types.ts b/src/types.ts index db108d6f2b..8eff5abe2b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -145,7 +145,7 @@ export interface CellRendererProps extends Pick, 'row' | 'rowIdx' | 'selectCell'>, Omit< React.HTMLAttributes, - 'style' | 'children' | 'onClick' | 'onDoubleClick' | 'onContextMenu' + 'children' | 'onClick' | 'onDoubleClick' | 'onContextMenu' > { column: CalculatedColumn; colSpan: number | undefined; From ca6de7607704a4543682ff2e570b2dd71b4d9fc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 12:22:02 +0100 Subject: [PATCH 7/9] Bump @biomejs/biome from 1.9.3 to 1.9.4 (#3622) Bumps [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) from 1.9.3 to 1.9.4. - [Release notes](https://github.com/biomejs/biome/releases) - [Changelog](https://github.com/biomejs/biome/blob/main/CHANGELOG.md) - [Commits](https://github.com/biomejs/biome/commits/cli/v1.9.4/packages/@biomejs/biome) --- updated-dependencies: - dependency-name: "@biomejs/biome" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f747a54137..92666a98dd 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", "@babel/runtime": "^7.21.5", - "@biomejs/biome": "1.9.3", + "@biomejs/biome": "1.9.4", "@eslint/compat": "^1.1.1", "@faker-js/faker": "^9.0.0", "@ianvs/prettier-plugin-sort-imports": "^4.0.2", From 06044030a72cdae49afc48af90cf110f78f4667b Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Mon, 11 Nov 2024 12:02:26 -0600 Subject: [PATCH 8/9] Update eslint deps (#3615) * Update deps * Add new rule * Add `@tanstack/eslint-plugin-router` * Update router deps * update deps * Add `'@typescript-eslint/no-unnecessary-type-parameters': 1,` * Remove @tanstack/eslint-plugin-router * Add `eslint-plugin-react-hooks-extra` * 1 more * Add `eslint-plugin-react-compiler` * -1 array * Do we need these rules * disable 1 `skipLibCheck`, add back newline * update deps * Use state to focus cell * Remove a few refs * Disable warning --------- Co-authored-by: Nicolas Stepien --- eslint.config.js | 21 ++++++- package.json | 22 ++++--- src/DataGrid.tsx | 74 +++++++++++----------- src/ScrollToCell.tsx | 8 +-- src/hooks/useViewportColumns.ts | 1 + src/utils/index.ts | 4 +- tsconfig.website.json | 3 +- website/routes/ColumnSpanning.lazy.tsx | 77 +++++++++++------------ website/routes/CommonFeatures.lazy.tsx | 17 ++--- website/routes/MillionCells.lazy.tsx | 33 +++++----- website/routes/VariableRowHeight.lazy.tsx | 31 ++++----- 11 files changed, 150 insertions(+), 141 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index aa4f2cf287..9023668536 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,7 +4,9 @@ import tsParser from '@typescript-eslint/parser'; import vitest from '@vitest/eslint-plugin'; import jestDom from 'eslint-plugin-jest-dom'; import react from 'eslint-plugin-react'; +import reactCompiler from 'eslint-plugin-react-compiler'; import reactHooks from 'eslint-plugin-react-hooks'; +import reactHooksExtra from 'eslint-plugin-react-hooks-extra'; import sonarjs from 'eslint-plugin-sonarjs'; import testingLibrary from 'eslint-plugin-testing-library'; @@ -18,7 +20,9 @@ export default [ plugins: { react, + 'react-compiler': reactCompiler, 'react-hooks': fixupPluginRules(reactHooks), + 'react-hooks-extra': reactHooksExtra, sonarjs, '@typescript-eslint': typescriptEslint }, @@ -371,11 +375,22 @@ export default [ 'react/style-prop-object': 0, 'react/void-dom-elements-no-children': 1, + // React Compiler + // https://react.dev/learn/react-compiler#installing-eslint-plugin-react-compiler + 'react-compiler/react-compiler': 1, + // React Hooks // https://www.npmjs.com/package/eslint-plugin-react-hooks 'react-hooks/rules-of-hooks': 1, 'react-hooks/exhaustive-deps': 1, + // React Hooks Extra + // https://eslint-react.xyz/ + 'react-hooks-extra/no-redundant-custom-hook': 1, + 'react-hooks-extra/no-unnecessary-use-callback': 1, + 'react-hooks-extra/no-unnecessary-use-memo': 1, + 'react-hooks-extra/prefer-use-state-lazy-initialization': 1, + // SonarJS rules // https://github.com/SonarSource/eslint-plugin-sonarjs#rules 'sonarjs/no-all-duplicated-branches': 1, @@ -467,13 +482,14 @@ export default [ '@typescript-eslint/no-this-alias': 0, '@typescript-eslint/no-type-alias': 0, '@typescript-eslint/no-unnecessary-boolean-literal-compare': 1, - '@typescript-eslint/no-unnecessary-condition': 1, + '@typescript-eslint/no-unnecessary-condition': [1, { checkTypePredicates: true }], '@typescript-eslint/no-unnecessary-parameter-property-assignment': 1, '@typescript-eslint/no-unnecessary-qualifier': 0, '@typescript-eslint/no-unnecessary-template-expression': 1, '@typescript-eslint/no-unnecessary-type-arguments': 1, '@typescript-eslint/no-unnecessary-type-assertion': 1, '@typescript-eslint/no-unnecessary-type-constraint': 1, + '@typescript-eslint/no-unnecessary-type-parameters': 1, '@typescript-eslint/no-unsafe-argument': 0, '@typescript-eslint/no-unsafe-assignment': 0, '@typescript-eslint/no-unsafe-call': 0, @@ -587,7 +603,7 @@ export default [ plugins: { vitest, 'jest-dom': jestDom, - 'testing-library': fixupPluginRules(testingLibrary) + 'testing-library': testingLibrary }, rules: { @@ -647,6 +663,7 @@ export default [ 'vitest/prefer-to-contain': 1, 'vitest/prefer-to-have-length': 1, 'vitest/prefer-todo': 1, + 'vitest/prefer-vi-mocked': 1, 'vitest/require-hook': 0, 'vitest/require-local-test-context-for-concurrent-snapshots': 0, 'vitest/require-to-throw-message': 0, diff --git a/package.json b/package.json index 92666a98dd..961778cae0 100644 --- a/package.json +++ b/package.json @@ -62,37 +62,39 @@ "@babel/preset-typescript": "^7.18.6", "@babel/runtime": "^7.21.5", "@biomejs/biome": "1.9.4", - "@eslint/compat": "^1.1.1", + "@eslint/compat": "^1.2.2", "@faker-js/faker": "^9.0.0", "@ianvs/prettier-plugin-sort-imports": "^4.0.2", "@linaria/core": "^6.0.0", "@microsoft/api-extractor": "^7.23.0", "@rollup/plugin-babel": "^6.0.3", "@rollup/plugin-node-resolve": "^15.1.0", - "@tanstack/react-router": "^1.57.13", - "@tanstack/router-plugin": "^1.57.13", + "@tanstack/react-router": "^1.70.0", + "@tanstack/router-plugin": "^1.69.1", "@testing-library/dom": "^10.1.0", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", "@types/node": "^22.0.0", "@types/react": "^18.3.9", "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^8.7.0", - "@typescript-eslint/parser": "^8.7.0", + "@typescript-eslint/eslint-plugin": "^8.13.0", + "@typescript-eslint/parser": "^8.13.0", "@vitejs/plugin-react": "^4.3.1", "@vitest/browser": "^2.1.1", "@vitest/coverage-v8": "^2.1.1", - "@vitest/eslint-plugin": "^1.1.4", + "@vitest/eslint-plugin": "^1.1.8", "@wyw-in-js/rollup": "^0.5.0", "@wyw-in-js/vite": "^0.5.0", "babel-plugin-optimize-clsx": "^2.6.2", "browserslist": "^4.24.0", - "eslint": "^9.11.1", + "eslint": "^9.14.0", "eslint-plugin-jest-dom": "^5.0.1", - "eslint-plugin-react": "^7.36.1", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-compiler": "^19.0.0-beta-a7bf2bd-20241110", "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-sonarjs": "^2.0.2", - "eslint-plugin-testing-library": "^6.3.0", + "eslint-plugin-react-hooks-extra": "^1.16.1", + "eslint-plugin-sonarjs": "^2.0.4", + "eslint-plugin-testing-library": "^6.4.0", "jspdf": "^2.5.1", "jspdf-autotable": "^3.5.23", "playwright": "^1.45.1", diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 314ec4a149..a16597e6fc 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -296,6 +296,8 @@ function DataGrid( const [isDragging, setDragging] = useState(false); const [draggedOverRowIdx, setOverRowIdx] = useState(undefined); const [scrollToPosition, setScrollToPosition] = useState(null); + const [shouldFocusCell, setShouldFocusCell] = useState(false); + const [previousRowIdx, setPreviousRowIdx] = useState(-1); const getColumnWidth = useCallback( (column: CalculatedColumn) => { @@ -338,15 +340,13 @@ function DataGrid( const [selectedPosition, setSelectedPosition] = useState( (): SelectCellState | EditCellState => ({ idx: -1, rowIdx: minRowIdx - 1, mode: 'SELECT' }) ); + const [prevSelectedPosition, setPrevSelectedPosition] = useState(selectedPosition); /** * refs */ - const prevSelectedPosition = useRef(selectedPosition); const latestDraggedOverRowIdx = useRef(draggedOverRowIdx); - const lastSelectedRowIdx = useRef(-1); const focusSinkRef = useRef(null); - const shouldFocusCellRef = useRef(false); /** * computed values @@ -458,31 +458,50 @@ function DataGrid( selectCell({ rowIdx: minRowIdx + rowIdx - 1, idx }); }); + /** + * callbacks + */ + const setDraggedOverRowIdx = useCallback((rowIdx?: number) => { + setOverRowIdx(rowIdx); + latestDraggedOverRowIdx.current = rowIdx; + }, []); + + const focusCellOrCellContent = useCallback(() => { + const cell = getCellToScroll(gridRef.current!); + if (cell === null) return; + + scrollIntoView(cell); + // Focus cell content when available instead of the cell itself + const elementToFocus = cell.querySelector('[tabindex="0"]') ?? cell; + elementToFocus.focus({ preventScroll: true }); + }, [gridRef]); + /** * effects */ useLayoutEffect(() => { if ( !selectedCellIsWithinSelectionBounds || - isSamePosition(selectedPosition, prevSelectedPosition.current) + isSamePosition(selectedPosition, prevSelectedPosition) ) { - prevSelectedPosition.current = selectedPosition; + setPrevSelectedPosition(selectedPosition); return; } - prevSelectedPosition.current = selectedPosition; + setPrevSelectedPosition(selectedPosition); - if (selectedPosition.idx === -1) { - focusSinkRef.current!.focus({ preventScroll: true }); + if (focusSinkRef.current !== null && selectedPosition.idx === -1) { + focusSinkRef.current.focus({ preventScroll: true }); scrollIntoView(focusSinkRef.current); } - }); + }, [selectedCellIsWithinSelectionBounds, selectedPosition, prevSelectedPosition]); useLayoutEffect(() => { - if (!shouldFocusCellRef.current) return; - shouldFocusCellRef.current = false; - focusCellOrCellContent(); - }); + if (shouldFocusCell) { + setShouldFocusCell(false); + focusCellOrCellContent(); + } + }, [shouldFocusCell, focusCellOrCellContent]); useImperativeHandle(ref, () => ({ element: gridRef.current, @@ -499,14 +518,6 @@ function DataGrid( selectCell })); - /** - * callbacks - */ - const setDraggedOverRowIdx = useCallback((rowIdx?: number) => { - setOverRowIdx(rowIdx); - latestDraggedOverRowIdx.current = rowIdx; - }, []); - /** * event handlers */ @@ -536,9 +547,8 @@ function DataGrid( if (isRowSelectionDisabled?.(row) === true) return; const newSelectedRows = new Set(selectedRows); const rowKey = rowKeyGetter(row); - const previousRowIdx = lastSelectedRowIdx.current; const rowIdx = rows.indexOf(row); - lastSelectedRowIdx.current = rowIdx; + setPreviousRowIdx(rowIdx); if (checked) { newSelectedRows.add(rowKey); @@ -758,7 +768,7 @@ function DataGrid( // Avoid re-renders if the selected cell state is the same scrollIntoView(getCellToScroll(gridRef.current!)); } else { - shouldFocusCellRef.current = true; + setShouldFocusCell(true); setSelectedPosition({ ...position, mode: 'SELECT' }); } @@ -870,16 +880,6 @@ function DataGrid( return isDraggedOver ? selectedPosition.idx : undefined; } - function focusCellOrCellContent() { - const cell = getCellToScroll(gridRef.current!); - if (cell === null) return; - - scrollIntoView(cell); - // Focus cell content when available instead of the cell itself - const elementToFocus = cell.querySelector('[tabindex="0"]') ?? cell; - elementToFocus.focus({ preventScroll: true }); - } - function renderDragHandle() { if ( onFill == null || @@ -925,7 +925,7 @@ function DataGrid( const colSpan = getColSpan(column, lastFrozenColumnIndex, { type: 'ROW', row }); const closeEditor = (shouldFocusCell: boolean) => { - shouldFocusCellRef.current = shouldFocusCell; + setShouldFocusCell(shouldFocusCell); setSelectedPosition(({ idx, rowIdx }) => ({ idx, rowIdx, mode: 'SELECT' })); }; @@ -1061,6 +1061,7 @@ function DataGrid( // Reset the positions if the current values are no longer valid. This can happen if a column or row is removed if (selectedPosition.idx > maxColIdx || selectedPosition.rowIdx > maxRowIdx) { setSelectedPosition({ idx: -1, rowIdx: minRowIdx - 1, mode: 'SELECT' }); + // eslint-disable-next-line react-compiler/react-compiler setDraggedOverRowIdx(undefined); } @@ -1182,6 +1183,7 @@ function DataGrid( ); })} + {/* eslint-disable-next-line react-compiler/react-compiler */} {getViewportRows()} {bottomSummaryRows?.map((row, rowIdx) => { @@ -1245,7 +1247,7 @@ function DataGrid( )}
diff --git a/src/ScrollToCell.tsx b/src/ScrollToCell.tsx index 7fca2ce353..5c8a228eec 100644 --- a/src/ScrollToCell.tsx +++ b/src/ScrollToCell.tsx @@ -10,11 +10,11 @@ export interface PartialPosition { export default function ScrollToCell({ scrollToPosition: { idx, rowIdx }, - gridElement, + gridRef, setScrollToCellPosition }: { scrollToPosition: PartialPosition; - gridElement: HTMLDivElement; + gridRef: React.RefObject; setScrollToCellPosition: (cell: null) => void; }) { const ref = useRef(null); @@ -31,7 +31,7 @@ export default function ScrollToCell({ } const observer = new IntersectionObserver(removeScrollToCell, { - root: gridElement, + root: gridRef.current!, threshold: 1.0 }); @@ -40,7 +40,7 @@ export default function ScrollToCell({ return () => { observer.disconnect(); }; - }, [gridElement, setScrollToCellPosition]); + }, [gridRef, setScrollToCellPosition]); return (
({ const updateStartIdx = (colIdx: number, colSpan: number | undefined) => { if (colSpan !== undefined && colIdx + colSpan > colOverscanStartIdx) { + // eslint-disable-next-line react-compiler/react-compiler startIdx = colIdx; return true; } diff --git a/src/utils/index.ts b/src/utils/index.ts index 35e428b772..f6bd990888 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,4 @@ -import type { CalculatedColumn, CalculatedColumnOrColumnGroup } from '../types'; +import type { CalculatedColumn, CalculatedColumnOrColumnGroup, Maybe } from '../types'; export * from './colSpanUtils'; export * from './domUtils'; @@ -11,7 +11,7 @@ export * from './styleUtils'; export const { min, max, floor, sign, abs } = Math; export function assertIsValidKeyGetter( - keyGetter: unknown + keyGetter: Maybe<(row: NoInfer) => K> ): asserts keyGetter is (row: R) => K { if (typeof keyGetter !== 'function') { throw new Error('Please specify the rowKeyGetter prop to use selection'); diff --git a/tsconfig.website.json b/tsconfig.website.json index 8c78a220cf..8094a617b1 100644 --- a/tsconfig.website.json +++ b/tsconfig.website.json @@ -1,8 +1,7 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "lib": ["ESNext", "DOM", "DOM.Iterable", "DOM.AsyncIterable"], - "skipLibCheck": true + "lib": ["ESNext", "DOM", "DOM.Iterable", "DOM.AsyncIterable"] }, "include": ["website/**/*"], "references": [{ "path": "tsconfig.src.json" }] diff --git a/website/routes/ColumnSpanning.lazy.tsx b/website/routes/ColumnSpanning.lazy.tsx index e393e2e26d..1862c94cad 100644 --- a/website/routes/ColumnSpanning.lazy.tsx +++ b/website/routes/ColumnSpanning.lazy.tsx @@ -1,4 +1,3 @@ -import { useMemo } from 'react'; import { createLazyFileRoute } from '@tanstack/react-router'; import { css } from '@linaria/core'; @@ -20,49 +19,45 @@ const colSpanClassname = css` text-align: center; `; -function ColumnSpanning() { - const direction = useDirection(); - - const columns = useMemo((): readonly Column[] => { - const columns: Column[] = []; +const columns: Column[] = []; - for (let i = 0; i < 30; i++) { - const key = String(i); - columns.push({ - key, - name: key, - frozen: i < 5, - resizable: true, - renderCell: renderCoordinates, - colSpan(args) { - if (args.type === 'ROW') { - if (key === '2' && args.row === 2) return 3; - if (key === '4' && args.row === 4) return 6; // Will not work as colspan includes both frozen and regular columns - if (key === '0' && args.row === 5) return 5; - if (key === '27' && args.row === 8) return 3; - if (key === '6' && args.row < 8) return 2; - } - if (args.type === 'HEADER' && key === '8') { - return 3; - } - return undefined; - }, - cellClass(row) { - if ( - (key === '0' && row === 5) || - (key === '2' && row === 2) || - (key === '27' && row === 8) || - (key === '6' && row < 8) - ) { - return colSpanClassname; - } - return undefined; - } - }); +for (let i = 0; i < 30; i++) { + const key = String(i); + columns.push({ + key, + name: key, + frozen: i < 5, + resizable: true, + renderCell: renderCoordinates, + colSpan(args) { + if (args.type === 'ROW') { + if (key === '2' && args.row === 2) return 3; + if (key === '4' && args.row === 4) return 6; // Will not work as colspan includes both frozen and regular columns + if (key === '0' && args.row === 5) return 5; + if (key === '27' && args.row === 8) return 3; + if (key === '6' && args.row < 8) return 2; + } + if (args.type === 'HEADER' && key === '8') { + return 3; + } + return undefined; + }, + cellClass(row) { + if ( + (key === '0' && row === 5) || + (key === '2' && row === 2) || + (key === '27' && row === 8) || + (key === '6' && row < 8) + ) { + return colSpanClassname; + } + return undefined; } + }); +} - return columns; - }, []); +function ColumnSpanning() { + const direction = useDirection(); return ( (); for (let i = 0; i < 1000; i++) { + const country = faker.location.country(); + countrySet.add(country); + rows.push({ id: i, title: `Task #${i + 1}`, client: faker.company.name(), area: faker.person.jobArea(), - country: faker.location.country(), + country, contact: faker.internet.exampleEmail(), assignee: faker.person.fullName(), progress: Math.random() * 100, @@ -270,6 +276,8 @@ function createRows(): readonly Row[] { }); } + countries = [...countrySet].sort(new Intl.Collator().compare); + return rows; } @@ -313,12 +321,7 @@ function CommonFeatures() { const [selectedRows, setSelectedRows] = useState((): ReadonlySet => new Set()); const [isExporting, setIsExporting] = useState(false); const gridRef = useRef(null); - - const countries = useMemo((): readonly string[] => { - return [...new Set(rows.map((r) => r.country))].sort(new Intl.Collator().compare); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - const columns = useMemo(() => getColumns(countries, direction), [countries, direction]); + const columns = useMemo(() => getColumns(countries, direction), [direction]); const summaryRows = useMemo((): readonly SummaryRow[] => { return [ diff --git a/website/routes/MillionCells.lazy.tsx b/website/routes/MillionCells.lazy.tsx index fb3b2975bc..eafaeb467f 100644 --- a/website/routes/MillionCells.lazy.tsx +++ b/website/routes/MillionCells.lazy.tsx @@ -1,4 +1,3 @@ -import { useMemo } from 'react'; import { createLazyFileRoute } from '@tanstack/react-router'; import DataGrid from '../../src'; @@ -13,27 +12,23 @@ export const Route = createLazyFileRoute('/MillionCells')({ type Row = number; const rows: readonly Row[] = Array.from({ length: 1000 }, (_, i) => i); +const columns: Column[] = []; + +for (let i = 0; i < 1000; i++) { + const key = String(i); + columns.push({ + key, + name: key, + frozen: i < 5, + width: 80, + resizable: true, + renderCell: renderCoordinates + }); +} + function MillionCells() { const direction = useDirection(); - const columns = useMemo((): readonly Column[] => { - const columns: Column[] = []; - - for (let i = 0; i < 1000; i++) { - const key = String(i); - columns.push({ - key, - name: key, - frozen: i < 5, - width: 80, - resizable: true, - renderCell: renderCoordinates - }); - } - - return columns; - }, []); - return ( i); +const columns: Column[] = []; + +for (let i = 0; i < 30; i++) { + const key = String(i); + columns.push({ + key, + name: key, + frozen: i < 5, + resizable: true, + renderCell: renderCoordinates + }); +} + function VariableRowHeight() { const direction = useDirection(); - const columns = useMemo((): readonly Column[] => { - const columns: Column[] = []; - - for (let i = 0; i < 30; i++) { - const key = String(i); - columns.push({ - key, - name: key, - frozen: i < 5, - resizable: true, - renderCell: renderCoordinates - }); - } - - return columns; - }, []); - return ( Date: Tue, 12 Nov 2024 06:51:12 +0900 Subject: [PATCH 9/9] use object spread operator --- src/Cell.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Cell.tsx b/src/Cell.tsx index c35b8323f0..67a27d00c6 100644 --- a/src/Cell.tsx +++ b/src/Cell.tsx @@ -94,14 +94,6 @@ function Cell( onRowChange(column, newRow); } - const cellStyle = getCellStyle(column, colSpan); - if (style) { - let key: keyof React.CSSProperties; - for (key in style) { - (cellStyle as any)[key] = style[key]; - } - } - return (
( ref={ref} tabIndex={tabIndex} className={className} - style={cellStyle} + style={{ + ...getCellStyle(column, colSpan), + ...style + }} onClick={handleClick} onDoubleClick={handleDoubleClick} onContextMenu={handleContextMenu}