diff --git a/.vscode/settings.json b/.vscode/settings.json index 6a898b7f25..0e546f4539 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,7 +10,7 @@ "editor.formatOnSaveMode": "file", "js/ts.experimental.useTsgo": true, "js/ts.tsdk.path": "./node_modules/typescript", - // ESLint is only used for markdown; JS/TS is linted by the oxc extension + // ESLint is only used for Markdown; JS/TS is linted by the Oxc extension "eslint.validate": ["markdown"], "eslint.problems.shortenToSingleLine": true, "files.readonlyInclude": { diff --git a/AGENTS.md b/AGENTS.md index cfc1e0744e..39253499f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,11 +9,11 @@ src/ index.ts # public API surface; all exports go through here DataGrid.tsx # main component (generic: ) TreeDataGrid.tsx # wraps DataGrid, adds row grouping (role="treegrid") - types.ts # shared type definitions (e.g. Column, CalculatedColumn, render props, events) + types.ts # shared type definitions (e.g., Column, CalculatedColumn, render props, events) hooks/ # shared custom React hooks - utils/ # pure utilities (e.g. keyboard, DOM, events, colSpan, style) + utils/ # pure utilities (e.g., keyboard, DOM, events, colSpan, style) style/ # build-time CSS via ecij tagged templates; layers.css declares @layer order - cellRenderers/ # default cell renderers (e.g. checkbox, toggleGroup, value) + cellRenderers/ # default cell renderers (e.g., checkbox, toggleGroup, value) editors/ # default editors (renderTextEditor) test/ browser/ # vitest browser-mode tests (Playwright: Chromium, Firefox, WebKit) @@ -30,10 +30,10 @@ website/ # demo site (Vite + TanStack Router) - **TypeScript strict** with `exactOptionalPropertyTypes`, `verbatimModuleSyntax`, `erasableSyntaxOnly`. Distinguish missing properties from `undefined` values. - **`Maybe`** (`T | undefined | null`) — used for all nullable column/render props. Do not use bare `T | undefined`. - **`NoInfer<>`** — wrap callback parameters to prevent reverse type inference into component generics. -- **CSS layers** — all styles live in nested `@layer rdg.` sub-layers (e.g. `rdg.Cell`, `rdg.Row`; declared in `src/style/layers.css`). Use `ecij` `css` tagged templates (build-time extraction, not runtime CSS-in-JS). Co-locate styles in component files; `src/style/` is for shared styles. -- **Dual classnames** — components apply both a semantic class (`rdg-cell`) and a generated hash. Preserve both. +- **CSS layers** — all styles live in nested `@layer rdg.` sub-layers (e.g., `rdg.Cell`, `rdg.Row`). When a sub-layer overrides another sub-layer's styles, declare both in `src/style/layers.css`, which sets their order; undeclared sub-layers come after the declared ones. Use `ecij` `css` tagged templates (build-time extraction, not runtime CSS-in-JS). Co-locate styles in component files; `src/style/` is for shared styles. +- **Dual class names** — components apply both a semantic class (`rdg-cell`) and a generated hash. Preserve both. - **Light/dark mode** — handled via CSS `light-dark()` + `color-scheme`, not JS. -- **Accessibility first** — ARIA attributes (e.g. `aria-colindex`, `aria-rowindex`, `aria-selected`, roles) are required. Tests query by role. +- **Accessibility first** — ARIA attributes (e.g., `aria-colindex`, `aria-rowindex`, `aria-selected`, roles) are required. Tests query by role. - **Build** — `vp pack` bundles the library to `lib/`, then lints the packed package with publint; `ecij` plugin prefixes classes with `rdg-{version}-` (dots→dashes) to avoid cross-version conflicts. - **Workflows** — pin every action to a full commit SHA with its version in a comment (Dependabot updates both), pass expressions to `run` steps through `env` instead of inlining `${{ }}`, grant each job only the `permissions` it uses, and check out with `persist-credentials: false`. Jobs that install dependencies only get read permissions: publishing, pushing, and editing PRs happen in separate jobs that receive build output as artifacts and never install the project's dependencies. - **Releases** — the header of `.github/workflows/publish.yml` explains how to publish. Its read-only `build` job hands `lib/` to the `publish` job, so the project's dependencies never run with the `id-token: write` permission that npm's trusted publishing relies on, and a separate `release` job creates the GitHub release. @@ -43,14 +43,14 @@ website/ # demo site (Vite + TanStack Router) - Browser tests use `vite-plus/test/browser` + Playwright. `test/setupBrowser.ts` configures `page.render()` via `vitest-browser-react` and registers custom locators via `locators.extend()` — prefer `page.getGrid()`, `page.getCell({ name })`, `page.getRow()`, `page.getHeaderCell()`, `page.getActiveCell()`, etc. over raw `page.getByRole()`. - Test helpers in `test/browser/utils.tsx`: `setup()`, `getRowWithCell()`, `getCellsAtRowIndex()`, `validateCellPosition()`, `scrollGrid()`, `safeTab()`, `testCount()`, `testRowCount()`. - `test/failOnConsole.ts` fails tests on unexpected console warnings/errors. -- **Never run visual regression tests** — screenshots are environment-dependent so visual regression tests must run in CI only. +- **Never run visual regression tests** — screenshots are environment-dependent, so visual regression tests must run in CI only. ## Tooling The project uses [Vite+](https://viteplus.dev/guide/), whose global `vp` CLI wraps Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Run `vp` directly, not through `npx`. Its docs are in `node_modules/vite-plus/docs`. -- `vp ` runs a built-in command, e.g. `vp dev`, `vp check`, `vp test`, `vp pack`, `vp build`. -- `vp run ` runs a task from `vite.config.ts`, e.g. `vp run typecheck`, `vp run eslint`, `vp run ci`. +- `vp ` runs a built-in command, e.g., `vp dev`, `vp check`, `vp test`, `vp pack`, `vp build`. +- `vp run ` runs a task from `vite.config.ts`, e.g., `vp run typecheck`, `vp run eslint`, `vp run ci`. - Run `vp install` after pulling changes. ## Validation @@ -62,4 +62,4 @@ While iterating, run individual checks instead. The static checks take seconds, - `vp run typecheck` type checks with `tsc --build`; `vp check` does not type check. - `vp check --fix` formats and lints, fixing what it can. Without `--fix`, a formatting error stops it before linting. - `vp run eslint` lints Markdown files. -- `vp test ` runs specific test files, e.g. `vp test test/browser/sorting.test.tsx`. Add `--project "browser (chromium)"` to test in a single browser, or `--project node` to run the SSR test. The full suite takes about a minute. +- `vp test ` runs specific test files, e.g., `vp test test/browser/sorting.test.tsx`. Add `--project "browser (chromium)"` to test in a single browser, or `--project node` to run the SSR test. The full suite takes about a minute. diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 6685f21a6f..0000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,419 +0,0 @@ -# Changelog - -## v7.0.0-canary.48 - -- Fixed an edge case where clicking outside the grid wouldn't close and commit an open editor quickly enough, resulting in the previous rows state being used by parent components in `click` handlers. ([PR](https://github.com/Comcast/react-data-grid/pull/2415)) -- Support for filters has been removed, along with: ([PR](https://github.com/Comcast/react-data-grid/pull/2412)) - - The `` props: `headerFiltersHeight`, `filters`, `onFiltersChange`, `enableFilterRow` - - `Column.filterRenderer` - - The exports: `FilterRendererProps`, `Filters` -- Optional `Column` props can now also be `null` in addition to `undefined`. ([PR](https://github.com/Comcast/react-data-grid/pull/2409)) - -## v7.0.0-canary.47 - -- Added a third, optional generic on `` to specify the row key type. ([PR](https://github.com/Comcast/react-data-grid/pull/2311)) -- Added the `useRowSelection` hook for custom cell renderers to access `isRowSelected` and `onRowSelectionChange`. ([PR](https://github.com/Comcast/react-data-grid/pull/2338)) -- `HeaderRendererProps.allRowsSelected` tweaks, affecting the `SelectColumn`: ([PR](https://github.com/Comcast/react-data-grid/pull/2402)) - - It will now be `false` when `rows.length === 0` instead of `true`. - - `selectedRows` must contain all the row keys for `allRowsSelected` to be `true`, instead of naively comparing the `size` and `length`. -- Optional props on `` can now also be `null` in addition to `undefined`. ([PR](https://github.com/Comcast/react-data-grid/pull/2406)) - -## v7.0.0-canary.46 - -- Optimize onRowsChange: only update changed rows ([PR](https://github.com/Comcast/react-data-grid/pull/2390)) - -## v7.0.0-canary.45 - -- Fixed issue where the scrollbars would flicker in edge cases ([PR](https://github.com/Comcast/react-data-grid/pull/2389)) - -## v7.0.0-canary.44 - -- Use aria-selected to style selected cell/rows ([PR](https://github.com/Comcast/react-data-grid/pull/2386)) -- Add support for variable row heights ([PR](https://github.com/Comcast/react-data-grid/pull/2384)) - -## v7.0.0-canary.43 - -- Fix `aria-readonly` ([PR](https://github.com/Comcast/react-data-grid/pull/2380)) - -## v7.0.0-canary.42 - -- Add missing `aria-colspan` ([PR](https://github.com/Comcast/react-data-grid/pull/2374)) -- Add `aria-readonly` to readonly cells ([PR](https://github.com/Comcast/react-data-grid/pull/2379)) - -## v7.0.0-canary.41 - -- Fix summary row colSpan ([PR](https://github.com/Comcast/react-data-grid/pull/2372)) -- HeaderCell: Add missing event parameter to onPointerUp, fix #2305 ([PR](https://github.com/Comcast/react-data-grid/pull/2371)) - -## v7.0.0-canary.40 - -- Add `column.colSpan` prop to merge cells ([PR](https://github.com/Comcast/react-data-grid/pull/2356)) -- Removed prefixed css properties from the generated style file ([PR](https://github.com/Comcast/react-data-grid/pull/2370)) - -## v7.0.0-canary.39 - -- Add `enableVirtualization` prop ([PR](https://github.com/Comcast/react-data-grid/pull/2355)). Grid renders all the rows/columns when it is set to false. - -## v7.0.0-canary.38 - -- Fix summary row position ([PR](https://github.com/Comcast/react-data-grid/pull/2335)) - -## v7.0.0-canary.37 - -- Add `summaryRowHeight` prop ([PR](https://github.com/Comcast/react-data-grid/pull/2325)) -- Rollup type definitions ([PR](https://github.com/Comcast/react-data-grid/pull/2301)) - -## v7.0.0-canary.36 - -- Fixed column headers not showing when grid is horizontally scrolled ([PR](https://github.com/Comcast/react-data-grid/pull/2297)) - -## v7.0.0-canary.35 - -- Migrate to css-in-js with linaria ([PR](https://github.com/Comcast/react-data-grid/pull/2256)). RDG now internally uses [linaria](https://github.com/callstack/linaria) to manage styles. The stylesheets are automatically injected and there is no stylesheet to manually import anymore. -- Expose data grid root element reference ([PR](https://github.com/Comcast/react-data-grid/pull/2258)). -- (Bug fix) Tabbing into the grid should initiate keyboard navigation ([PR](https://github.com/Comcast/react-data-grid/pull/2289)). - -## v7.0.0-canary.34 - -- Only show the vertical scrollbar when necessary ([PR](https://github.com/Comcast/react-data-grid/pull/2231)) -- Fix `editorPortalTarget` default value for server-side rendering ([PR](https://github.com/Comcast/react-data-grid/pull/2245)) -- Use grid layout for rows, split column metrics from compute columns ([PR](https://github.com/Comcast/react-data-grid/pull/2272)). Resizing columns no longer recreates columns so less re-renders -- Augment `onRowsChange` with `indexes` and `column` data ([PR](https://github.com/Comcast/react-data-grid/pull/2278)) - -## v7.0.0-canary.33 - -- (Bug) Commit changes before exiting grid ([PR](https://github.com/Comcast/react-data-grid/pull/2224)) - -## v7.0.0-canary.32 - -- Fix importing rdg with webpack 4 ([PR](https://github.com/Comcast/react-data-grid/pull/2221)) - -## v7.0.0-canary.31 - -- (Breaking) Remove some exports from the grid ([PR](https://github.com/Comcast/react-data-grid/pull/2210)) -- Use a pseudo-element for header resizer ([PR](https://github.com/Comcast/react-data-grid/pull/2217)) -- (new) Formatters now receive a new onRowChange prop ([PR](https://github.com/Comcast/react-data-grid/pull/2220)) -- (new) Change `column.name` type from `string` to `string/ReactElement` ([PR](https://github.com/Comcast/react-data-grid/pull/2220)) -- Publish bundles instead of modules, use babel only for transpilation ([PR](https://github.com/Comcast/react-data-grid/pull/2214)) -- Use the new jsx runtime ([PR](https://github.com/Comcast/react-data-grid/pull/2184)). The minimum supported react version is 16.14 now. - -## v7.0.0-canary.30 - -- Forward sorting props to `headerRenderer` ([PR](https://github.com/Comcast/react-data-grid/pull/2204)) -- Disable cell navigation while editing. This behavior can be controlled using the new `column.editorOptions.onNavigation` option ([PR](https://github.com/Comcast/react-data-grid/pull/2196)) - -## v7.0.0-canary.29 - -- Fix crash when `rows` are `undefined` ([PR](https://github.com/Comcast/react-data-grid/pull/2197)) -- Fix a scrolling bug caused by `scroll-behavior: smooth` ([PR](https://github.com/Comcast/react-data-grid/pull/2200)) - -## v7.0.0-canary.28 - -- (Breaking) Removed `onRowsUpdate`, `enableCellCopyPaste`, and `enableCellDragAndDrop` props. Added 2 new props (`onFill` and `onPaste`) ([PR](https://github.com/Comcast/react-data-grid/pull/2194)) -- (Breaking) Rename `enableFilters` to `enableFilterRow` ([PR](https://github.com/Comcast/react-data-grid/pull/2195)) - -## v7.0.0-canary.27 - -- (Breaking) Removed old editor API ([PR](https://github.com/Comcast/react-data-grid/pull/2149)) -- (Breaking) Removed `onCheckCellIsEditable` prop ([PR](https://github.com/Comcast/react-data-grid/pull/2016)). Use `column.editable` instead. - = (Breaking) Replaced rowKey prop with rowKeyGetter ([PR](https://github.com/Comcast/react-data-grid/pull/2190)) - -## v7.0.0-canary.26 - -- Fixed Copy & Paste events not working properly in non-English keyboards ([PR](https://github.com/Comcast/react-data-grid/pull/2097)) - -## v7.0.0-canary.25 - -- Added support for webpack 5 ([PR](https://github.com/Comcast/react-data-grid/pull/2178)) - -## v7.0.0-canary.24 - -- Fixed a bug where grid was getting scrolled on cell focus - -## v7.0.0-canary.23 - -- Minor style fixes - -## v7.0.0-canary.22 - -- Add Grouping ([PR](https://github.com/Comcast/react-data-grid/pull/2106)). Check the new [example](https://comcast.github.io/react-data-grid/#/grouping). -- (Breaking) Removed `height` and `width` props and added new `className` and `style` props. We are now using `ResizeObserver` to calculate all the grid dimensions ([PR](https://github.com/Comcast/react-data-grid/pull/2130)) -- (Breaking) Removed formatterOptions. Grid now handles formatter focus internally so this prop is no longer required ([PR](https://github.com/Comcast/react-data-grid/pull/2138)) -- Added support for React 17 - -## v7.0.0-canary.21 - -- Editor2 bug fixes ([PR](https://github.com/Comcast/react-data-grid/pull/2126)) -- `formatterOptions.focusable` can be a function now so different rows can have different focus behavior for the same column -- Grid now internally uses `ResizeObserver` API to calculate the available width ([PR](https://github.com/Comcast/react-data-grid/pull/2129)) - -## v7.0.0-canary.20 - -- A new editor api that does not require a ref ([PR](https://github.com/Comcast/react-data-grid/pull/2102)) -- A new `columnDefaultOptions` prop. This replaces `minColumnWidth` and `defaultFormatter` props ([PR](https://github.com/Comcast/react-data-grid/pull/2117)) - -## v7.0.0-canary.19 - -- Allow setting ref on `Cell/Row` renderers ([PR](https://github.com/Comcast/react-data-grid/pull/2111)) - -## v7.0.0-canary.18 - -- Accessibility improvements. Added various aria attributes. ([PR](https://github.com/Comcast/react-data-grid/pull/2084)) -- Removed enableCellAutoFocus prop ([PR](https://github.com/Comcast/react-data-grid/pull/2073)) -- Removed InteractionMasks component. This is an internal change to prepare for grouping and it fixes a few selected cell position bugs -- Improved focus for custom formatters using the new formatterOptions ([PR](https://github.com/Comcast/react-data-grid/pull/2104)) - -## v7.0.0-canary.17 - -- Performance improvements ([PR](https://github.com/Comcast/react-data-grid/pull/2015)) -- A new rowClass prop ([PR](https://github.com/Comcast/react-data-grid/pull/2058)) - -## `alpha` to `canary` - -- **Added:** - - **Props:** - - `className` - - `style` - - `onRowsChange` - - `onFill` - - `onPaste` - - `onSelectedCellChange` - - ⚠️ This replaces the `onCellSelected` and `onCellDeSelected` props - - `enableFilterRow` - - ⚠️ This replaces the `enableHeaderFilters` and `column.filterable` props - - `filters` - - `onFiltersChange` - - `rows` - - ⚠️ This replace the `rowGetter` and `rowsCount` props - - `rowClass` - - `defaultColumnOptions` - - ⚠️ This replaces the `minColumnWidth` and `defaultFormatter` props - - `groupBy` - - `rowGrouper` - - More info in [#2106](https://github.com/Comcast/react-data-grid/pull/2106) - - `column.cellClass(row)` function support: - - `column = { ..., cellClass(row) { return string; } }` - - `column.minWidth` - - `column.maxWidth` - - `column.headerCellClass` - - `column.editor` - - New API - - `column.editorOptions` - - More info in [#2102](https://github.com/Comcast/react-data-grid/pull/2102) - - `column.groupFormatter` - - More info in [#2106](https://github.com/Comcast/react-data-grid/pull/2106) - - `scrollToRow` method - - ⚠️ This replaces the `scrollToRowIndex` prop - - Dark mode support - - Dark mode is automatically applied following user preference - - Light and dark modes can be forced by setting the `rdg-light` or `rdg-dark` class names. -- **Removed:** - - **Support:** - - ⚠️ IE11 - - **Packages:** - - ⚠️ `react-data-grid-addons` - - **Props:** - - ⚠️ `width` - - ⚠️ `height` - - ⚠️ `cellContentRenderer` - - ⚠️ `contextMenu` - - Check the [Context Menu](https://comcast.github.io/react-data-grid/#/context-menu) example - - ⚠️ `enableCellSelect` - - ⚠️ `enableCellAutoFocus` - - ⚠️ `getValidFilterValues` - - ⚠️ `onCellCopyPaste` - - ⚠️ `onSelectedCellRangeChange` - - ⚠️ `onCheckCellIsEditable` - - Use `column.editable` instead. - - ⚠️ `onGridKeyDown` - - ⚠️ `onGridKeyUp` - - ⚠️ `onRowDoubleClick` - - ⚠️ `onRowsUpdate` - - Use `onRowsChange`, `onFill`, and `onPaste` instead. - - ⚠️ `onHeaderDrop` - - ⚠️ `draggableHeaderCell` - - Check [#2007](https://github.com/Comcast/react-data-grid/pull/2007) on how to migrate - - ⚠️ `rowGroupRenderer` - - ⚠️ `onRowExpandToggle` - - Check [#2012](https://github.com/Comcast/react-data-grid/pull/2012) on how to migrate - - ⚠️ `rowsContainer` - - ⚠️ Subrow props: `getSubRowDetails`, `onCellExpand`, `onDeleteSubRow`, and `onAddSubRow` - - Check [#1853](https://github.com/Comcast/react-data-grid/pull/1853) on how to migrate - - ⚠️ `cellMetaData` (from `Row` and `Cell` props) - - ⚠️ `value` (from `column.formatter` props) - - **Ref handlers:** - - ⚠️ `openCellEditor`, use `selectCell` instead. - - ⚠️ React elements are no longer supported, please use components instead. - - For example: - ```diff - const column = { - key: 'example', - name: 'Example', - - formatter: - + formatter: (props) => - }; - ``` - - ⚠️ `column.events` - - ⚠️ `column.getCellActions` - - Check [#1845](https://github.com/Comcast/react-data-grid/pull/1845) on how to migrate - - ⚠️ `column.getRowMetaData` - - ⚠️ `column.minColumnWidth` - - ⚠️ `column.filterable` - - ⚠️ `column.draggable` - - ⚠️ `cellRangeSelection.{onStart,onUpdate,onEnd}` - - ⚠️ `fromRowId`, `toRowId`, and `fromRowData` from `onRowsUpdate` argument - - ⚠️ Stopped exporting `HeaderCell` -- **Renamed:** - - ⚠️ `minHeight` to `height` - - ⚠️ `minWidth` to `width` - - ⚠️ `onGridSort` to `onSort` - - ⚠️ `emptyRowsView` to `emptyRowsRenderer` - - ⚠️ `rowKey` to `rowKeyGetter` - - ⚠️ `rowData` to `row` - - ⚠️ `fromRowData` to `fromRow` - - ⚠️ `idx` to `rowIdx` in `Row` renderer -- **Changed:** - - ⚠️ Started publishing ES2019/ESM modules instead of ES5/CommonJS modules. - - Using [`@babel/preset-env`](https://www.npmjs.com/package/@babel/preset-env) with [`core-js`](https://www.npmjs.com/package/core-js) is recommended to enable compatibility for the browsers your project aims to support. - - ⚠️ Improved support for summary rows: - - `summaryRows` types are now independent from `rows` - - Added `column.summaryCellClass` and `column.summaryFormatter` props - - `column.formatter` isn't used anymore to render summary row cells. - - Only visible headers cells are now rendered. [#1837](https://github.com/Comcast/react-data-grid/pull/1837) - - ⚠️ the `rowKeyGetter` prop is now required for row selection. - - ⚠️ `column.cellClass` does not affect header cells anymore. - - ⚠️ `onScroll` will directly pass the UIEvent rather than the scrollLeft and scrollRight only. - - ⚠️ The stylesheets are now automatically injected, there is no stylsheet to manually import anymore. - -## `main` to `alpha` - -- **Added:** - - TypeScript declaration files - - `column.cellContentRenderer` - - More info in this [gist](https://gist.github.com/nstepien/090298c3c2d94324cb332c33d82fdcfb) - - `summaryRows` prop [#1773](https://github.com/Comcast/react-data-grid/pull/1773) - - `sortColumn` and `sortDirection` props - - ⚠️ The internal sort states have been removed. - - Check [#1768](https://github.com/Comcast/react-data-grid/pull/1768) on how to migrate. - - `selectedRows` and `onSelectedRowsChange` props - - ⚠️ Row selection has been reimplemented. - - A new `SelectColumn` is now available to import and add a row selection column. - - Check [#1762](https://github.com/Comcast/react-data-grid/pull/1762) on how to migrate. -- **Removed:** - - **Packages:** - - `react-data-grid-examples` - - Use the website as reference, or clone the repo locally and run `npm install` + `npm start` - - **Props:** - - ⚠️ `onCellsDragged` - - ⚠️ `onDragHandleDoubleClick` - - ⚠️ `onRows` - - ⚠️ `onRowUpdated` - - ⚠️ `rowScrollTimeout` - - ⚠️ `toolbar` - - Check [#1769](https://github.com/Comcast/react-data-grid/pull/1769) on how to migrate - - ⚠️ `isScrolling` (from `column.formatter` props) - - ⚠️ `ContainerEditorWrapper` - - ⚠️ `EditorBase` - - ⚠️ `setScrollLeft` from `Row` and `Cell` renderers - - `setScrollLeft` instance method was previously required on custom Cell and Row renderers and it can be safely removed now. - - More info in [#1793](https://github.com/Comcast/react-data-grid/pull/1793) - - ⚠️ Dropped ImmutableJS support. - - ⚠️ Dropped dynamic height row support. - - This was not officially supported, but it was still possible to implement dynamic rows via custom Row renderer. This was a buggy feature so it has been removed. - - ⚠️ Removed row reordering support. -- **Renamed:** - - `ReactDataGrid` to `DataGrid` -- **Changed:** - - ⚠️ The stylesheets are now bundled separately: - - `react-data-grid/dist/react-data-grid.css` - - ⚠️ Reimplemented stylesheets, renamed various class names [#1780](https://github.com/Comcast/react-data-grid/pull/1780) - - No longer depends on bootstrap - - Resizing a column now immediately resizes all its cells instead of just its header. - - Improved performance across the board. - -### react-data-grid-addons - -- ⚠️ `react-data-grid-addons` has no default export anymore - - Use `import * as DataGridAddons from 'react-data-grid-addons';` instead -- ⚠️ The stylesheets are now bundled separately: - - `react-data-grid-addons/dist/react-data-grid-addons.css` -- **Removed:** - - ⚠️ `AutoComplete` editor - - ⚠️ `Utils` - -## 5.0.5 (Dec 6, 2018) - -- **Bugfix:** fix: draggable resizing col jumps to right ([1421](https://github.com/Comcast/react-data-grid/pull/1421)) -- **Bugfix:** Cell Tooltip - Focus Issues ([1422](https://github.com/Comcast/react-data-grid/pull/1422)) - -## 6.0.1 (Nov 30, 2018) - -- **Bugfix:** Fix formatter exports ([1409](https://github.com/Comcast/react-data-grid/pull/1409)) - -## 6.0.0 (Nov 30, 2018) - -- **Bugfix:** Fix outside click logic for committing changes ([1404](https://github.com/Comcast/react-data-grid/pull/1404)) -- **TechDebt** Add ESLint Rules ([1396](https://github.com/Comcast/react-data-grid/pull/1396) [1397](https://github.com/Comcast/react-data-grid/pull/1397) [1396](https://github.com/Comcast/react-data-grid/pull/1398) [1399](https://github.com/Comcast/react-data-grid/pull/1399)) -- **TechDebt:** Upgrade build tools to latest versions ([1350](https://github.com/Comcast/react-data-grid/pull/1350)) -- **Feature:** Cleanup zIndex logic ([1393](https://github.com/Comcast/react-data-grid/pull/1393)) -- **Breaking** Use react portals for cell editors ([1369](https://github.com/Comcast/react-data-grid/pull/1369)) -- **TechDebt:** Upgrade build tools to latest versions ([1350](https://github.com/Comcast/react-data-grid/pull/1350)) - -## 5.0.4 (Nov 14, 2018) - -- **Bugfix:** Custom Formatters Example - Styling Fix ([1364](https://github.com/Comcast/react-data-grid/pull/1364)) -- **Bugfix:** Fix website publishing and remove ImmutableJS dependency ([1366](https://github.com/Comcast/react-data-grid/pull/1366)) -- **Bugfix:** Remove react-data-grid dependency from the addons package ([1354](https://github.com/Comcast/react-data-grid/pull/1354)) -- **Bugfix:** Fix drag fill in IE 11 ([1359](https://github.com/Comcast/react-data-grid/pull/1359)) -- **Bugfix:** Ensure webpack uses correct common js external ref ([1370](https://github.com/Comcast/react-data-grid/pull/1370)) -- **Feature:** Move DropTargetRowContainer static styles to CSS class for user customization ([1308](https://github.com/Comcast/react-data-grid/pull/1308)) -- **Bugfix:** Replace use of i for column index which is no longer passed in ([1344](https://github.com/Comcast/react-data-grid/pull/1344)) -- **Bugfix:** Remove utils from documentation as an exported module ([1374](https://github.com/Comcast/react-data-grid/pull/1374)) -- **TechDebt:** Upgrade eslint packages ([1376](https://github.com/Comcast/react-data-grid/pull/1376)) -- **Bugfix:** Cleanup DraggableContainer and fix styles ([1379](https://github.com/Comcast/react-data-grid/pull/1379)) - -## 5.0.3 (Nov 1, 2018) - -- **Bugfix:** Fix frozen-columns source url ([1355](https://github.com/Comcast/react-data-grid/pull/1355)) -- **TechDebt:** Defining the ref callback as a bound method ([1353](https://github.com/Comcast/react-data-grid/pull/1353)) -- **Bugfix:** Fix cell focusing logic ([1352](https://github.com/Comcast/react-data-grid/pull/1352)) -- **Bugfix:** RDG Tree Cell Expand Styling Issues ([1316](https://github.com/Comcast/react-data-grid/pull/1316)) - -## 5.0.2 (Oct 30, 2018) - -- **Bugfix:** Fix grouping example ([1311](https://github.com/Comcast/react-data-grid/pull/1311)) -- **Bugfix:** Remove contain layout css ([1346](https://github.com/Comcast/react-data-grid/pull/1346)) -- **Bugfix:** Preserve window scroll position on cell selection ([1349](https://github.com/Comcast/react-data-grid/pull/1346)) - -## 5.0.1 (Oct 22, 2018) - -### React-Data-Grid - -- **Bugfix:** Fix CopyMask columns ([1289](https://github.com/Comcast/react-data-grid/pull/1272)) - -## 5.0.0 (Oct 22, 2018) - -### React-Data-Grid - -- **Feature:** Improve Cell navigation Performance ([1123](https://github.com/Comcast/react-data-grid/pull/1123)) -- **Feature:** Scrolling improvements ([1254](https://github.com/Comcast/react-data-grid/pull/1254)) -- **Feature:** Remove react-data-grid dependency from react-data-grid-addons bundle ([1272](https://github.com/Comcast/react-data-grid/pull/1272)) - -## 4.0.8 (May 10, 2018) - -### React-Data-Grid - -- **Bugfix:** Fix editor refs ([#1183](https://github.com/Comcast/react-data-grid/pull/1183)) - -## 4.0.7 (April 19, 2018) - -### React-Data-Grid - -- **Feature:** Support `React v16` ([#1116](https://github.com/Comcast/react-data-grid/pull/1116)) -- **Feature:** Set sort column and direction with props ([#649](https://github.com/Comcast/react-data-grid/pull/649)) -- **Bugfix:** Filtering should ignore null values ([#1147](https://github.com/Comcast/react-data-grid/pull/1147)) -- **Bugfix:** Resize column doesnt work on firefox when also draggable ([#1121](https://github.com/Comcast/react-data-grid/pull/1121)) - -### React-Data-Grid-Addons - -- **Breaking:** Migrate to `react-context-menu 2.9.2` ([#1081](https://github.com/Comcast/react-data-grid/pull/1081)) diff --git a/README.md b/README.md index d2c6715295..9f48afd745 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ The DataGrid component is designed to handle large datasets efficiently while offering a rich set of features for customization and interactivity. -## Table of contents +## Table of Contents - [Features](#features) - [Links](#links) - [Installation](#installation) -- [Getting started](#getting-started) +- [Getting Started](#getting-started) - [Styling and Customization](#styling-and-customization) - [API Reference](#api-reference) @@ -25,7 +25,7 @@ The DataGrid component is designed to handle large datasets efficiently while of - Great performance thanks to virtualization: columns and rows outside the viewport are not rendered - Strictly typed with TypeScript - [Keyboard accessibility](https://comcast.github.io/react-data-grid/#/CommonFeatures) -- Light and dark mode support out of the box via `color-scheme`. +- Light and dark mode support out of the box via `color-scheme` - [Frozen columns](https://comcast.github.io/react-data-grid/#/CommonFeatures): Freeze columns to keep them visible during horizontal scrolling. - [Column resizing](https://comcast.github.io/react-data-grid/#/CommonFeatures) - [Multi-column sorting](https://comcast.github.io/react-data-grid/#/CommonFeatures) @@ -40,16 +40,16 @@ The DataGrid component is designed to handle large datasets efficiently while of - [No rows fallback](https://comcast.github.io/react-data-grid/#/NoRows) - [Cell formatting](https://comcast.github.io/react-data-grid/#/CommonFeatures) - [Cell editing](https://comcast.github.io/react-data-grid/#/CommonFeatures) -- [Cell copy / pasting](https://comcast.github.io/react-data-grid/#/AllFeatures) +- [Cell copying / pasting](https://comcast.github.io/react-data-grid/#/AllFeatures) - [Cell value dragging / filling](https://comcast.github.io/react-data-grid/#/AllFeatures) -- [Customizable Renderers](https://comcast.github.io/react-data-grid/#/CustomizableRenderers) -- Right-to-left (RTL) support. +- [Customizable renderers](https://comcast.github.io/react-data-grid/#/CustomizableRenderers) +- Right-to-left (RTL) support ## Links - [Examples website](https://comcast.github.io/react-data-grid/) - [Source code](website) -- [Changelog](CHANGELOG.md) +- [Releases](https://github.com/Comcast/react-data-grid/releases) ## Installation @@ -77,17 +77,17 @@ import 'react-data-grid/lib/styles.css'; `react-data-grid` is published as ECMAScript modules for evergreen browsers, bundlers, and server-side rendering. > **Important** -> Vite 8+ by default uses `lightningcss` to minify css which has a [bug minifying light-dark syntax](https://github.com/parcel-bundler/lightningcss/issues/873). You can tweak the `cssMinify` or `cssTarget` [settings](https://main.vite.dev/config/build-options) as a workaround. +> Vite 8+ uses Lightning CSS by default to minify CSS, and Lightning CSS has a [bug minifying the `light-dark()` syntax](https://github.com/parcel-bundler/lightningcss/issues/873). You can tweak the `cssMinify` or `cssTarget` [settings](https://vite.dev/config/build-options) as a workaround. ```ts build: { - cssMinify: 'esbuild', + cssMinify: 'esbuild', // requires the `esbuild` package to be installed // or cssTarget: 'esnext' } ``` -## Getting started +## Getting Started Here is a basic example of how to use `react-data-grid` in your React application: @@ -176,6 +176,9 @@ The DataGrid supports the following CSS variables for customization: /* Checkboxes */ --rdg-checkbox-focus-color: hsl(207deg 100% 69%); + + /* Frozen columns */ + --rdg-frozen-column-shadow-color: light-dark(rgb(0 0 0 / 15%), rgb(0 0 0 / 40%)); } ``` @@ -218,7 +221,7 @@ Apply custom CSS classes to rows using the [`rowClass`](#rowclass-mayberow-r-row #### Cell Classes -Apply custom CSS classes to cells using the [`cellClass`](#cellclass-maybestring--row-trow--maybestring) property in column definitions. You can also use [`headerCellClass`](#headercellclass-maybestring) and [`summaryCellClass`](#summarycellclass-maybestring--row-tsummaryrow--maybestring) for header and summary cells respectively. +Apply custom CSS classes to cells using the [`cellClass`](#cellclass-maybestring--row-trow--maybestring) property in column definitions. You can also use [`headerCellClass`](#headercellclass-maybestring) and [`summaryCellClass`](#summarycellclass-maybestring--row-tsummaryrow--maybestring) for header and summary cells, respectively. #### Column Widths @@ -244,7 +247,7 @@ An array of column definitions and/or column groups. See the [`ColumnOrColumnGro ###### `rows: readonly R[]` -An array of rows, the rows data can be of any type. +An array of rows. The row data can be of any type. :bulb: **Performance:** The grid is optimized for efficient rendering: @@ -254,7 +257,7 @@ An array of rows, the rows data can be of any type. - **Best practice**: When updating rows, create a new array but reuse unchanged row objects. For example: ```tsx - // ✅ Good: Only changed row is re-rendered + // ✅ Good: Only the changed row is re-rendered setRows(rows.map((row, idx) => (idx === targetIdx ? { ...row, updated: true } : row))); // ❌ Avoid: Creates new references for all rows, causing all visible rows to re-render @@ -298,7 +301,7 @@ function MyGrid() { } ``` -:bulb: While optional, setting this prop is recommended for optimal performance as the returned value is used to set the `key` prop on the row elements. +:bulb: While optional, setting this prop is recommended for optimal performance, as the returned value is used to set the `key` prop on the row elements. :warning: **Performance:** Define this function outside your component or memoize it with `useCallback` to prevent unnecessary re-renders. @@ -365,7 +368,7 @@ Height of each summary row in pixels. ###### `columnWidths?: Maybe` -A map of column widths containing both measured and resized widths. If not provided then an internal state is used. +A map of column widths containing both measured and resized widths. If not provided, an internal state is used. ```tsx const [columnWidths, setColumnWidths] = useState((): ColumnWidths => new Map()); @@ -381,7 +384,7 @@ return void>` -Callback triggered when column widths change. If not provided then an internal state is used. +Callback triggered when column widths change. If not provided, an internal state is used. ###### `selectedRows?: Maybe>` @@ -397,7 +400,7 @@ Callback triggered when the selection changes. ```tsx import { useState } from 'react'; -import { DataGrid, SelectColumn } from 'react-data-grid'; +import { DataGrid, SelectColumn, type Column } from 'react-data-grid'; const rows: readonly Row[] = [...]; @@ -442,7 +445,7 @@ Callback triggered when sorting changes. ```tsx import { useState } from 'react'; -import { DataGrid, SelectColumn } from 'react-data-grid'; +import { DataGrid, type Column, type SortColumn } from 'react-data-grid'; const rows: readonly Row[] = [...]; @@ -469,7 +472,7 @@ function MyGrid() { } ``` -More than one column can be sorted via `ctrl (command) + click`. To disable multiple column sorting, change the `onSortColumnsChange` function to +More than one column can be sorted via Ctrl+Click (Cmd+Click on macOS). To disable multi-column sorting, change the `onSortColumnsChange` function to: ```tsx function onSortColumnsChange(sortColumns: SortColumn[]) { @@ -516,17 +519,7 @@ function onCellMouseDown(args: CellMouseArgs, event: CellMouseEvent) { Callback triggered when a cell is clicked. -```tsx -function onCellClick(args: CellMouseArgs, event: CellMouseEvent) { - if (args.column.key === 'id') { - event.preventGridDefault(); - } -} - -; -``` - -This event can be used to open cell editor on single click +This event can be used to open the cell editor on a single click: ```tsx function onCellClick(args: CellMouseArgs, event: CellMouseEvent) { @@ -534,6 +527,8 @@ function onCellClick(args: CellMouseArgs, event: CellMouseEvent) { args.setActivePosition(true); } } + +; ``` ###### `onCellDoubleClick?: CellMouseEventHandler` @@ -567,7 +562,7 @@ function onCellContextMenu(args: CellMouseArgs, event: CellMouseEvent) { ###### `onCellKeyDown?: Maybe<(args: CellKeyDownArgs, event: CellKeyboardEvent) => void>` -A function called when keydown event is triggered on a cell. This event can be used to customize cell navigation and editing behavior. +Callback triggered when a key is pressed in a cell. This event can be used to customize cell navigation and editing behavior. **Examples** @@ -591,7 +586,7 @@ function onCellKeyDown(args: CellKeyDownArgs, event: CellKeyboardEvent) { } ``` -Check [more examples](website/routes/CellNavigation.tsx) +See [more examples](website/routes/CellNavigation.tsx). ###### `onCellCopy?: Maybe<(args: CellCopyArgs, NoInfer>, event: CellClipboardEvent) => void>` @@ -605,19 +600,31 @@ Return the updated row; the grid will call `onRowsChange` with it. ###### `onActivePositionChange?: Maybe<(args: PositionChangeArgs) => void>` -Triggered when the active position changes. +Callback triggered when the active position changes. See the [`PositionChangeArgs`](#positionchangeargstrow-tsummaryrow) type in the Types section below. ###### `onFill?: Maybe<(event: FillEvent) => R>` +Callback triggered when the value of the active cell is dragged to fill other cells. When this prop is set, a drag handle is shown on the active cell if it is editable. Drag the handle up or down to fill the cells it passes over, or double-click it to fill all the cells below. + +The grid calls `onFill` for each editable target cell. Return the updated target row; the grid will call `onRowsChange` with the updated rows. See the [`FillEvent`](#filleventtrow) type. + +```tsx +function onFill({ columnKey, sourceRow, targetRow }: FillEvent): Row { + return { ...targetRow, [columnKey]: sourceRow[columnKey as keyof Row] }; +} + +; +``` + ###### `onScroll?: React.UIEventHandler | undefined` Native DOM `onScroll` prop. ###### `onColumnResize?: Maybe<(column: CalculatedColumn, width: number) => void>` -Callback triggered when column is resized. +Callback triggered when a column is resized. ###### `onColumnsReorder?: Maybe<(sourceColumnKey: string, targetColumnKey: string) => void>` @@ -718,7 +725,7 @@ Custom class name for the header row. ###### `direction?: Maybe<'ltr' | 'rtl'>` -This property sets the text direction of the grid, it defaults to `'ltr'` (left-to-right). Setting `direction` to `'rtl'` has the following effects: +This property sets the text direction of the grid. It defaults to `'ltr'` (left-to-right). Setting `direction` to `'rtl'` has the following effects: - Columns flow from right to left - Start-frozen columns are pinned on the right, and end-frozen columns are pinned on the left @@ -739,11 +746,11 @@ ARIA role for the grid container. Defaults to `grid`. ###### `'aria-label'?: string | undefined` -The label of the grid. We recommend providing a label using `aria-label` or `aria-labelledby` +The label of the grid. We recommend providing a label using `aria-label` or `aria-labelledby`. ###### `'aria-labelledby'?: string | undefined` -The id of the element containing a label for the grid. We recommend providing a label using `aria-label` or `aria-labelledby` +The id of the element containing a label for the grid. We recommend providing a label using `aria-label` or `aria-labelledby`. ###### `'aria-rowcount'?: number | undefined` @@ -757,7 +764,7 @@ If the grid has a caption or description, `aria-describedby` can be set on the g ###### `'data-testid'?: Maybe` -This prop can be used to add a testid for testing. We recommend querying the grid by its `role` and `name`. +This prop can be used to add a test ID for testing. We recommend querying the grid by its `role` and `name`. ```tsx function MyGrid() { @@ -789,7 +796,7 @@ Optional attribute to help with Cypress (or similar) selectors. **Keyboard Navigation:** - → (Right Arrow): Expand a collapsed group row when focused -- ← (Left Arrow): Collapse an expanded group row when focused, or navigate to parent group +- ← (Left Arrow): Collapse an expanded group row when focused, or navigate to the parent group **Unsupported Props:** @@ -884,7 +891,7 @@ Function to generate unique IDs for group rows. If not provided, a default imple ###### `rowHeight?: Maybe) => number)>` -**Note:** Unlike `DataGrid`, the `rowHeight` function receives [`RowHeightArgs`](#rowheightargstrow) which includes a `type` property to distinguish between regular rows and group rows: +**Note:** Unlike `DataGrid`, the `rowHeight` function receives [`RowHeightArgs`](#rowheightargstrow), which includes a `type` property to distinguish between regular rows and group rows: ```tsx function getRowHeight(args: RowHeightArgs): number { @@ -919,33 +926,7 @@ A formatter component for rendering row selection checkboxes. ##### Props -###### `value: boolean` - -Whether the checkbox is checked. - -###### `tabIndex?: number | undefined` - -The tab index for keyboard navigation. - -###### `indeterminate?: boolean | undefined` - -Whether the checkbox is in an indeterminate state. - -###### `disabled?: boolean | undefined` - -Whether the checkbox is disabled. - -###### `onChange: (checked: boolean, shift: boolean) => void` - -Callback when the checkbox state changes. - -###### `'aria-label'?: string | undefined` - -Accessible label for the checkbox. - -###### `'aria-labelledby'?: string | undefined` - -ID of the element that labels the checkbox. +The same props as [`RenderCheckboxProps`](#rendercheckboxprops), with `value: boolean` in place of `checked`. #### `` @@ -1028,6 +1009,8 @@ function CustomSelectCell({ row }: RenderCellProps) { The default header cell renderer. Renders sortable columns with sort indicators. +**Props:** [`RenderHeaderCellProps`](#renderheadercellpropstrow-tsummaryrow) + **Example:** ```tsx @@ -1047,6 +1030,8 @@ const columns: readonly Column[] = [ A basic text editor provided for convenience. +**Props:** [`RenderEditCellProps`](#rendereditcellpropstrow-tsummaryrow) + **Example:** ```tsx @@ -1065,31 +1050,19 @@ const columns: readonly Column[] = [ Renders the sort direction arrow icon. -**Props:** - -- `sortDirection: SortDirection | undefined` - 'ASC', 'DESC', or undefined +**Props:** [`RenderSortIconProps`](#rendersorticonprops) #### `renderSortPriority(props: RenderSortPriorityProps)` Renders the sort priority number for multi-column sorting. -**Props:** - -- `priority: number | undefined` - The sort priority (1, 2, 3, etc.) +**Props:** [`RenderSortPriorityProps`](#rendersortpriorityprops) #### `renderCheckbox(props: RenderCheckboxProps)` Renders a checkbox input with proper styling and accessibility. -**Props:** - -- `checked: boolean` - Whether the checkbox is checked -- `indeterminate?: boolean` - Whether the checkbox is in indeterminate state -- `disabled?: boolean` - Whether the checkbox is disabled -- `onChange: (checked: boolean, shift: boolean) => void` - Change handler -- `tabIndex: number` - Tab index for keyboard navigation -- `aria-label?: string` - Accessible label -- `aria-labelledby?: string` - ID of labeling element +**Props:** [`RenderCheckboxProps`](#rendercheckboxprops) **Example:** @@ -1105,11 +1078,9 @@ import { DataGrid, renderCheckbox } from 'react-data-grid'; #### `renderToggleGroup(props: RenderGroupCellProps)` -The default group cell renderer used by the columns used for grouping (`groupBy` prop). This renders the expand/collapse toggle. - -##### Props +The default group cell renderer for the columns used for grouping (`groupBy` prop). This renders the expand/collapse toggle. -[`RenderGroupCellProps`](#rendergroupcellpropstrow-tsummaryrow) +**Props:** [`RenderGroupCellProps`](#rendergroupcellpropstrow-tsummaryrow) **Example:** @@ -1129,6 +1100,8 @@ const columns: readonly Column[] = [ The default cell renderer that renders the value of `row[column.key]`. +**Props:** [`RenderCellProps`](#rendercellpropstrow-tsummaryrow) + **Example:** ```tsx @@ -1185,7 +1158,7 @@ function AppProvider({ children }) { #### `SelectColumn: Column` A pre-configured column for row selection. -Includes checkbox renderers for header, regular rows, and grouped rows. +Includes checkbox renderers for the header row, regular rows, and group rows. **Example:** @@ -1235,13 +1208,13 @@ The name of the column. Displayed in the header cell by default. ##### `key: string` -A unique key to distinguish each column +A unique key to distinguish each column. ##### `width?: Maybe` -**Default** `auto` +**Default:** `auto` -Width can be any valid css grid column value. If not specified, it will be determined automatically based on grid width and specified widths of other columns. +Width can be any valid CSS grid column value. If not specified, it will be determined automatically based on grid width and specified widths of other columns. ```tsx const columns: Column[] = [ @@ -1279,7 +1252,7 @@ width: 'minmax(100px, max-content)', ##### `minWidth?: Maybe` -**Default**: `50` pixels +**Default:** `50` pixels Minimum column width in pixels. @@ -1313,7 +1286,7 @@ const columns: Column[] = [ } .status-inactive { - color: grey; + color: gray; } .text-right { @@ -1339,7 +1312,7 @@ Render function to render the content of the header cell. ##### `renderSummaryCell?: Maybe<(props: RenderSummaryCellProps) => ReactNode>` -Render function to render the content of summary cells +Render function to render the content of summary cells. ##### `renderGroupCell?: Maybe<(props: RenderGroupCellProps) => ReactNode>` @@ -1347,7 +1320,7 @@ Render function to render the content of group cells when using `TreeDataGrid`. ##### `renderEditCell?: Maybe<(props: RenderEditCellProps) => ReactNode>` -Render function to render the content of edit cells. When set, the column is automatically set to be editable +Render function to render the content of edit cells. When set, the column is automatically set to be editable. ##### `editable?: Maybe boolean)>` @@ -1378,11 +1351,11 @@ const columns: readonly Column[] = [ ##### `frozen?: Maybe` -**Default**: `false` +**Default:** `false` Determines whether the column is frozen, and on which edge. Frozen columns stay in place when the grid is scrolled horizontally. -- `'start'` (or `true` for backwards compatibility) — pins the column to the start edge (left in LTR, right in RTL). +- `'start'` (or `true` for backward compatibility) — pins the column to the start edge (left in LTR, right in RTL). - `'end'` — pins the column to the end edge (right in LTR, left in RTL). - `false` (default) — the column scrolls with the rest of the grid. @@ -1396,27 +1369,27 @@ const columns: readonly Column[] = [ ##### `resizable?: Maybe` -**Default**: `false` +**Default:** `false` -Enable resizing of the column +Enable resizing of the column. ##### `sortable?: Maybe` -**Default**: `false` +**Default:** `false` -Enable sorting of the column +Enable sorting of the column. ##### `draggable?: Maybe` -**Default**: `false` +**Default:** `false` -Enable dragging of the column +Enable dragging of the column. ##### `sortDescendingFirst?: Maybe` -**Default**: `false` +**Default:** `false` -Sets the column sort order to be descending instead of ascending the first time the column is sorted +Sets the column sort order to be descending instead of ascending the first time the column is sorted. ##### `editorOptions` @@ -1424,19 +1397,19 @@ Options for cell editing. ###### `displayCellContent?: Maybe` -**Default**: `false` +**Default:** `false` Render the cell content in addition to the edit cell content. Enable this option when the editor is rendered outside the grid, like a modal for example. ###### `commitOnOutsideClick?: Maybe` -**Default**: `true` +**Default:** `true` Commit changes when clicking outside the cell. ###### `closeOnExternalRowChange?: Maybe` -**Default**: `true` +**Default:** `true` Close the editor when the row value changes externally. @@ -1446,6 +1419,7 @@ Defines a group of columns that share a common header. ```tsx interface ColumnGroup { + /** The name of the column group. It will be displayed in the header cell */ readonly name: string | ReactElement; readonly headerCellClass?: Maybe; readonly children: readonly ColumnOrColumnGroup[]; @@ -1474,14 +1448,27 @@ Union type representing either a `Column` or a `ColumnGroup`. #### `CalculatedColumn` -Extends `Column` with additional computed properties used internally by the grid. This is the type passed to render functions. - -**Additional properties:** +Extends `Column` with additional computed properties used internally by the grid. This is the type passed to render functions. The `Column` properties it redeclares, like `width` and `renderCell`, are resolved to their default values when not set. -- `idx: number` - The column index -- `level: number` - Nesting level when using column groups -- `parent: CalculatedColumnParent | undefined` - Parent column group if nested -- Multiple Column properties have their values set to their default value +```tsx +interface CalculatedColumn extends Column { + /** The column group that contains the column, or `undefined` if the column is not in a group */ + readonly parent: CalculatedColumnParent | undefined; + /** The index of the column in the grid. Column groups are flattened and frozen columns are moved to their edge, so it can differ from the column's index in `columns` */ + readonly idx: number; + /** Header row level, counted from the bottom header row: always `0` for columns, and negative for column groups */ + readonly level: number; + readonly width: number | string; + readonly minWidth: number; + readonly maxWidth: number | undefined; + readonly resizable: boolean; + readonly sortable: boolean; + readonly draggable: boolean; + readonly frozen: ColumnFrozen; + readonly renderCell: (props: RenderCellProps) => ReactNode; + readonly renderHeaderCell: (props: RenderHeaderCellProps) => ReactNode; +} +``` #### `CalculatedColumnParent` @@ -1709,24 +1696,33 @@ type CellMouseEventHandler = Maybe< >; ``` -#### `CellMouseEvent` +#### `CellEvent` (internal) -Extends `React.MouseEvent` with grid-specific methods. +Adds grid-specific methods to a React event. This helper type is not exported; the shape is shown for reference. -##### `event.preventGridDefault(): void` +```tsx +type CellEvent> = E & { + /** Prevents the default grid behavior for this event */ + preventGridDefault: () => void; + /** Returns whether `preventGridDefault` was called */ + isGridDefaultPrevented: () => boolean; +}; +``` -Prevents the default grid behavior for this event. +#### `CellMouseEvent` -##### `event.isGridDefaultPrevented(): boolean` +Extends `React.MouseEvent` with grid-specific methods. -Returns whether `preventGridDefault` was called. +```tsx +type CellMouseEvent = CellEvent>; +``` **Example:** ```tsx import type { CellMouseArgs, CellMouseEvent } from 'react-data-grid'; -function onCellClick(args: CellMouseArgs, event: CellMouseEvent) { +function onCellMouseDown(args: CellMouseArgs, event: CellMouseEvent) { if (args.column.key === 'actions') { event.preventGridDefault(); // Prevent cell focus } @@ -1737,13 +1733,9 @@ function onCellClick(args: CellMouseArgs, event: CellMouseEvent) { Extends `React.KeyboardEvent` with grid-specific methods. -##### `event.preventGridDefault(): void` - -Prevents the default grid behavior for this keyboard event. - -##### `event.isGridDefaultPrevented(): boolean` - -Returns whether `preventGridDefault` was called. +```tsx +type CellKeyboardEvent = CellEvent>; +``` #### `CellClipboardEvent` @@ -1887,18 +1879,17 @@ type SortDirection = 'ASC' | 'DESC'; #### `RowsChangeData` -Data provided to `onRowsChange` callback. +Data provided to the `onRowsChange` callback. ```tsx interface RowsChangeData { + /** Indexes of the rows that changed */ indexes: number[]; + /** The column of the cells that changed */ column: CalculatedColumn; } ``` -- `indexes`: Array of row indexes that changed -- `column`: The column where changes occurred - #### `SelectRowEvent` Event object for row selection changes. @@ -1937,7 +1928,7 @@ Used with the `onFill` prop to handle cell value dragging. #### `GroupRow` (internal) -Represents a grouped row in `TreeDataGrid`. This helper type is not exported; the shape is shown for reference. +Represents a group row in `TreeDataGrid`. This helper type is not exported; the shape is shown for reference. ```tsx interface GroupRow { @@ -1966,7 +1957,7 @@ interface ColumnWidth { } ``` -Used with `columnWidths` and `onColumnWidthsChange` props to control column widths externally. +Used with the `columnWidths` and `onColumnWidthsChange` props to control column widths externally. #### `Position` @@ -1974,8 +1965,10 @@ Represents a cell position in the grid. ```tsx interface Position { - readonly idx: number; // Column index - readonly rowIdx: number; // Row index + /** Column index, or `-1` for the whole row in `TreeDataGrid` */ + readonly idx: number; + /** Row index: `0` is the first row, header rows and top summary rows have negative indexes, and bottom summary rows come after the last row */ + readonly rowIdx: number; } ``` @@ -1996,13 +1989,20 @@ Props for custom checkbox renderers. ```tsx interface RenderCheckboxProps { - checked: boolean; - indeterminate?: boolean; - disabled?: boolean; + /** Whether the checkbox is checked */ + checked?: boolean | undefined; + /** Whether the checkbox is in an indeterminate state, like the select all checkbox when only some rows are selected */ + indeterminate?: boolean | undefined; + /** Whether the checkbox is disabled */ + disabled?: boolean | undefined; + /** Called when the checkbox is toggled. `shift` is `true` if the Shift key was held, to select a range of rows */ onChange: (checked: boolean, shift: boolean) => void; - tabIndex: number; - 'aria-label'?: string; - 'aria-labelledby'?: string; + /** `0` when the checkbox's cell is active and `-1` otherwise, so the grid can move focus from the cell to the checkbox */ + tabIndex?: number | undefined; + /** Accessible label for the checkbox */ + 'aria-label'?: string | undefined; + /** ID of the element that labels the checkbox */ + 'aria-labelledby'?: string | undefined; } ``` @@ -2011,10 +2011,7 @@ interface RenderCheckboxProps { Props for custom sort status renderers. ```tsx -interface RenderSortStatusProps { - sortDirection: SortDirection | undefined; - priority: number | undefined; -} +interface RenderSortStatusProps extends RenderSortIconProps, RenderSortPriorityProps {} ``` #### `RenderSortIconProps` @@ -2023,6 +2020,7 @@ Props for custom sort icon renderers. ```tsx interface RenderSortIconProps { + /** The sort direction of the column, or `undefined` if it is not sorted */ sortDirection: SortDirection | undefined; } ``` @@ -2033,6 +2031,7 @@ Props for custom sort priority renderers. ```tsx interface RenderSortPriorityProps { + /** The column's position in `sortColumns`, starting at 1, when sorting by multiple columns, or `undefined` otherwise */ priority: number | undefined; } ``` @@ -2053,7 +2052,7 @@ interface DataGridHandle { ```tsx import { useRef } from 'react'; -import { DataGrid, DataGridHandle } from 'react-data-grid'; +import { DataGrid, type DataGridHandle } from 'react-data-grid'; function MyGrid() { const gridRef = useRef(null); @@ -2086,7 +2085,7 @@ type DefaultColumnOptions = Pick< #### `Direction` -Grid layout bidirectionality. +Text direction of the grid. ```tsx type Direction = 'ltr' | 'rtl'; diff --git a/src/Cell.tsx b/src/Cell.tsx index 8f631cb607..5513db63a2 100644 --- a/src/Cell.tsx +++ b/src/Cell.tsx @@ -65,7 +65,7 @@ function Cell({ function handleMouseDown(event: MouseEvent) { onMouseDown?.(event); if (!handleMouseEvent(event, onCellMouseDown)) { - // select cell if the event is not prevented + // activate the cell if the event is not prevented setActivePositionWrapper(); } } diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 018f6c4983..939f77d94d 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -114,11 +114,11 @@ type SharedDivProps = Pick< export interface DataGridProps extends SharedDivProps { ref?: Maybe>; /** - * Grid and data Props + * Grid and data props */ /** An array of column definitions */ columns: readonly ColumnOrColumnGroup, NoInfer>[]; - /** A function called for each rendered row that should return a plain key/value pair object */ + /** An array of rows, which can be of any type */ rows: readonly R[]; /** Rows pinned at the top of the grid for summary purposes */ topSummaryRows?: Maybe; @@ -139,12 +139,12 @@ export interface DataGridProps extends Sha rowHeight?: Maybe) => number)>; /** * Height of the header row in pixels - * @default 35 + * @default `rowHeight` when it is a number, otherwise 35 */ headerRowHeight?: Maybe; /** * Height of each summary row in pixels - * @default 35 + * @default `rowHeight` when it is a number, otherwise 35 */ summaryRowHeight?: Maybe; /** A map of column widths */ @@ -193,10 +193,11 @@ export interface DataGridProps extends Sha >; /** Function called whenever the active position is changed */ onActivePositionChange?: Maybe<(args: PositionChangeArgs, NoInfer>) => void>; - /** Callback triggered when column is resized */ + /** Callback triggered when a column is resized */ onColumnResize?: Maybe<(column: CalculatedColumn, width: number) => void>; /** Callback triggered when columns are reordered */ onColumnsReorder?: Maybe<(sourceColumnKey: string, targetColumnKey: string) => void>; + /** Callback triggered when the value of the active cell is dragged to fill other cells */ onFill?: Maybe<(event: FillEvent>) => NoInfer>; /** @@ -224,7 +225,7 @@ export interface DataGridProps extends Sha } /** - * Main API Component to render a data grid of rows and columns + * Main API component to render a data grid of rows and columns * * @example * @@ -233,7 +234,7 @@ export interface DataGridProps extends Sha export function DataGrid(props: DataGridProps) { const { ref, - // Grid and data Props + // Grid and data props columns: rawColumns, rows, topSummaryRows, @@ -492,11 +493,11 @@ export function DataGrid(props: DataGridPr ); /** - * The identity of the wrapper function is stable so it won't break memoization + * The identity of the wrapper function is stable, so it won't break memoization */ const handleColumnResizeLatest = useLatestFunc(handleColumnResize); const handleColumnResizeEndLatest = useLatestFunc(handleColumnResizeEnd); - const onColumnsReorderLastest = useLatestFunc(onColumnsReorder); + const onColumnsReorderLatest = useLatestFunc(onColumnsReorder); const onSortColumnsChangeLatest = useLatestFunc(onSortColumnsChange); const onCellMouseDownLatest = useLatestFunc(onCellMouseDown); const onCellClickLatest = useLatestFunc(onCellClick); @@ -708,7 +709,7 @@ export function DataGrid(props: DataGridPr } function handleColumnResizeEnd() { - // This check is needed as double click on the resize handle triggers onPointerMove + // This check is needed as double-clicking the resize handle triggers onLostPointerCapture without resizing the column if (isColumnResizing) { onColumnWidthsChangeRaw?.(columnWidths); setIsColumnResizing(false); @@ -726,7 +727,7 @@ export function DataGrid(props: DataGridPr } function handleDragHandlePointerMove(event: React.PointerEvent) { - // find dragged over row using the pointer position + // find the dragged-over row using the pointer position const gridEl = gridRef.current!; const headerAndTopSummaryRowsHeight = headerRowsHeight + topSummaryRowsCount * summaryRowHeight; const offset = @@ -807,7 +808,7 @@ export function DataGrid(props: DataGridPr const row = rows[position.rowIdx]; setActivePosition({ ...position, mode: 'EDIT', row, originalRow: row }); } else if (samePosition) { - // Avoid re-renders if the selected cell state is the same + // Avoid re-renders if the active position is the same scrollIntoView(getCellToScroll(gridRef.current!)); } else { const newPosition: ActivePosition = { ...position, mode: 'ACTIVE' }; @@ -837,7 +838,7 @@ export function DataGrid(props: DataGridPr case 'ArrowUp': { const nextRowIdx = rowIdx - 1; return { - // avoid selecting header rows + // avoid focusing header rows idx: idx === -1 && nextRowIdx < -topSummaryRowsCount ? 0 : idx, rowIdx: nextRowIdx }; @@ -847,7 +848,7 @@ export function DataGrid(props: DataGridPr case leftKey: { const nextIdx = idx - 1; return { - // avoid selecting header rows + // avoid focusing header rows idx: rowIdx < -topSummaryRowsCount && nextIdx < 0 ? 0 : nextIdx, rowIdx }; @@ -857,11 +858,11 @@ export function DataGrid(props: DataGridPr case 'Tab': return { idx: idx + (shiftKey ? -1 : 1), rowIdx }; case 'Home': - // If row is selected then move focus to the first header row's cell. + // If the active position is a row, move focus to the first header row's cell. if (activePositionIsRow || ctrlKey) return { idx: 0, rowIdx: minRowIdx }; return { idx: 0, rowIdx }; case 'End': - // If row is selected then move focus to the last row. + // If the active position is a row, move focus to the last row. if (activePositionIsRow) return { idx, rowIdx: maxRowIdx }; return { idx: maxColIdx, rowIdx: ctrlKey ? maxRowIdx : rowIdx }; case 'PageUp': { @@ -1057,10 +1058,9 @@ export function DataGrid(props: DataGridPr function onRowChange(row: R, commitChanges: boolean, shouldFocus: boolean) { if (commitChanges) { - // Prevents two issues when editor is closed by clicking on a different cell - // - // Otherwise commitEditorChanges may be called before the cell state is changed to - // SELECT and this results in onRowChange getting called twice. + // Prevents an issue when the editor is closed by clicking on a different cell: + // otherwise, commitEditorChanges may be called before the cell state is changed to + // ACTIVE, which results in onRowChange getting called twice. flushSync(() => { updateRow(column, activePosition.rowIdx, row); closeEditor(shouldFocus); @@ -1122,7 +1122,7 @@ export function DataGrid(props: DataGridPr } return renderRow(key, { - // aria-rowindex is 1 based + // aria-rowindex is 1-based 'aria-rowindex': headerAndTopSummaryRowsCount + rowIdx + 1, 'aria-selected': isSelectable ? isRowSelected : undefined, rowIdx, @@ -1174,8 +1174,8 @@ export function DataGrid(props: DataGridPr aria-multiselectable={isSelectable ? true : undefined} aria-colcount={columns.length} aria-rowcount={ariaRowCount} - // Scrollable containers without tabIndex are keyboard focusable in Chrome only if there is no focusable element inside - // whereas they are always focusable in Firefox. We need to set tabIndex to have a consistent behavior across browsers. + // Scrollable containers without tabIndex are keyboard focusable in Chrome only if there is no focusable element inside, + // whereas they are always focusable in Firefox. We need to set tabIndex to have consistent behavior across browsers. tabIndex={-1} className={classnames(rootClassname, isDragging && viewportDraggingClassname, className)} style={{ @@ -1219,7 +1219,7 @@ export function DataGrid(props: DataGridPr iterateOverViewportColumnsForRow={iterateOverViewportColumnsForRow} onColumnResize={handleColumnResizeLatest} onColumnResizeEnd={handleColumnResizeEndLatest} - onColumnsReorder={onColumnsReorderLastest} + onColumnsReorder={onColumnsReorderLatest} sortColumns={sortColumns} onSortColumnsChange={onSortColumnsChangeLatest} activeCellIdx={ diff --git a/src/EditCell.tsx b/src/EditCell.tsx index bf5b234a4a..56c2362057 100644 --- a/src/EditCell.tsx +++ b/src/EditCell.tsx @@ -13,14 +13,14 @@ import type { /* * To check for outside `mousedown` events, we listen to all `mousedown` events at their birth, - * i.e. on the window during the capture phase, and at their death, i.e. on the window during the bubble phase. + * i.e., on the window during the capture phase, and at their death, i.e., on the window during the bubble phase. * * We schedule a check at the birth of the event, cancel the check when the event reaches the "inside" container, * and trigger the "outside" callback when the event bubbles back up to the window. * - * The event can be `stopPropagation()`ed halfway through, so they may not always bubble back up to the window, - * so an alternative check must be used. The check must happen after the event can reach the "inside" container, - * and not before it run to completion. `postTask`/`requestAnimationFrame` are the best way we know to achieve this. + * The event can be `stopPropagation()`ed halfway through, so it may not always bubble back up to the window, + * and an alternative check must be used. The check must happen after the event can reach the "inside" container, + * and not before it has run to completion. `postTask`/`requestAnimationFrame` are the best way we know to achieve this. * Usually we want click event handlers from parent components to access the latest committed values, * so `mousedown` is used instead of `click`. * @@ -72,7 +72,7 @@ export default function EditCell({ // We need to prevent the `useLayoutEffect` from cleaning up between re-renders, // as `onWindowCaptureMouseDown` might otherwise miss valid mousedown events. - // To that end we instead access the latest props via useEffectEvent. + // To that end, we instead access the latest props via useEffectEvent. const commitOnOutsideMouseDown = useEffectEvent(() => { onClose(true, false); }); @@ -128,7 +128,7 @@ export default function EditCell({ }; }, [commitOnOutsideClick]); - // TODO: report react compiler bug + // TODO: report React Compiler bug // oxlint-disable-next-line react/invariant function cancelTask() { captureEventRef.current = undefined; diff --git a/src/GroupedColumnHeaderRow.tsx b/src/GroupedColumnHeaderRow.tsx index 191664af09..af101e9428 100644 --- a/src/GroupedColumnHeaderRow.tsx +++ b/src/GroupedColumnHeaderRow.tsx @@ -49,7 +49,7 @@ function GroupedColumnHeaderRow({ return (
{cells} diff --git a/src/HeaderCell.tsx b/src/HeaderCell.tsx index a81d2310f1..9b41d5cc7e 100644 --- a/src/HeaderCell.tsx +++ b/src/HeaderCell.tsx @@ -105,7 +105,7 @@ export default function HeaderCell({ const dragImageRef = useRef(null); const isDragging = draggedColumnKey === column.key; const rowSpan = getHeaderCellRowSpan(column, rowIdx); - // set the tabIndex to 0 when there is no active cell so grid can receive focus + // set the tabIndex to 0 when there is no active cell so the grid can receive focus const { tabIndex, childTabIndex, onFocus } = useRovingTabIndex(shouldFocusGrid || isCellActive); const sortIndex = sortColumns?.findIndex((sort) => sort.columnKey === column.key); const sortColumn = @@ -166,7 +166,7 @@ export default function HeaderCell({ function handleFocus(event: React.FocusEvent) { onFocus?.(event); if (shouldFocusGrid) { - // Select the first header cell if there is no active cell + // Activate the first header cell if there is no active cell setPosition({ idx: 0, rowIdx }); } } diff --git a/src/HeaderRow.tsx b/src/HeaderRow.tsx index 79fffb9d8f..ba71ef5dad 100644 --- a/src/HeaderRow.tsx +++ b/src/HeaderRow.tsx @@ -94,7 +94,7 @@ function HeaderRow({ return (
({ if (!isGroupRow(row)) return; if ( idx === -1 && - // Collapse the current group row if it is focused and is in expanded state + // Collapse the current group row if it is focused and expanded ((event.key === leftKey && row.isExpanded) || - // Expand the current group row if it is focused and is in collapsed state + // Expand the current group row if it is focused and collapsed (event.key === rightKey && !row.isExpanded)) ) { // prevent scrolling diff --git a/src/cellRenderers/SelectCellFormatter.tsx b/src/cellRenderers/SelectCellFormatter.tsx index 7c02a12f3b..29f181390d 100644 --- a/src/cellRenderers/SelectCellFormatter.tsx +++ b/src/cellRenderers/SelectCellFormatter.tsx @@ -7,6 +7,7 @@ type SharedInputProps = Pick< >; interface SelectCellFormatterProps extends SharedInputProps { + /** Whether the checkbox is checked */ value: boolean; } diff --git a/src/hooks/useActivePosition.ts b/src/hooks/useActivePosition.ts index f02ba4dad1..f3d09450e4 100644 --- a/src/hooks/useActivePosition.ts +++ b/src/hooks/useActivePosition.ts @@ -51,7 +51,7 @@ export function useActivePosition({ * Returns whether the given position represents a valid cell or row position in the grid. * Active bounds: any valid position in the grid * Viewport: any valid position in the grid outside of header rows and summary rows - * Row selection is only allowed in TreeDataGrid + * Only TreeDataGrid allows the active position to be on a row (`idx: -1`) */ function validatePosition({ idx, rowIdx }: Position) { // check column position @@ -135,7 +135,7 @@ export function useActivePosition({ useLayoutEffect(() => { // Layout effects clean up when the component is replaced by a suspense fallback, - // or when under , then re-mounts when the suspense boundary cleans, + // or when under , then re-mount when the suspense boundary resolves, // or when Activity switches back to `mode="visible"`. // So we use a ref to: // 1. avoid re-focusing after the effect re-mounts diff --git a/src/hooks/useCalculatedColumns.ts b/src/hooks/useCalculatedColumns.ts index 34c2910eb2..753e481272 100644 --- a/src/hooks/useCalculatedColumns.ts +++ b/src/hooks/useCalculatedColumns.ts @@ -252,7 +252,8 @@ export function useCalculatedColumns({ const [colOverscanStartIdx, colOverscanEndIdx] = useMemo((): [number, number] => { if (!enableVirtualization) { - return [0, columns.length - 1]; + // start-frozen columns are always rendered, so the overscan range starts after them + return [lastStartFrozenColumnIndex + 1, columns.length - 1]; } // get the viewport's left side and right side positions for non-frozen columns const viewportLeft = scrollLeft + totalStartFrozenColumnWidth; @@ -283,7 +284,7 @@ export function useCalculatedColumns({ while (colVisibleEndIdx < lastColIdx) { const { left, width } = columnMetrics.get(columns[colVisibleEndIdx])!; // if the right side of the column is beyond or equal to the right side of the available viewport, - // then it the last column that's at least partially visible, as the previous column's right side is not beyond the viewport. + // then it is the last column that's at least partially visible, as the previous column's right side is not beyond the viewport. if (left + width >= viewportRight) { break; } diff --git a/src/hooks/useGridDimensions.ts b/src/hooks/useGridDimensions.ts index 789dcd4edc..576b3234c9 100644 --- a/src/hooks/useGridDimensions.ts +++ b/src/hooks/useGridDimensions.ts @@ -68,7 +68,7 @@ export function useGridDimensions(gridRef: React.RefObject { // ref.current is null during the initial render, when suspending, or in . - // We use ref as key instead to access stable values regardless of rendering state. + // We use the ref as the key instead to access stable values regardless of the rendering state. return sizeMap.get(gridRef) ?? initialSize; }, [gridRef]); diff --git a/src/hooks/useLatestFunc.ts b/src/hooks/useLatestFunc.ts index 1397202a01..ab98301d07 100644 --- a/src/hooks/useLatestFunc.ts +++ b/src/hooks/useLatestFunc.ts @@ -2,7 +2,7 @@ import { useCallback, useLayoutEffect, useRef } from 'react'; import type { Maybe } from '../types'; -// https://reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often +// https://legacy.reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often // oxlint-disable-next-line typescript/no-explicit-any export function useLatestFunc any>>(fn: T): T { const ref = useRef(fn); diff --git a/src/hooks/useRovingTabIndex.ts b/src/hooks/useRovingTabIndex.ts index 555921c4ce..9e69c73537 100644 --- a/src/hooks/useRovingTabIndex.ts +++ b/src/hooks/useRovingTabIndex.ts @@ -10,7 +10,7 @@ export function useRovingTabIndex(isActive: boolean) { } function onFocus(event: React.FocusEvent) { - // Do not steal focus if event originates from a focusable child + // Do not steal focus if the event originates from a focusable child if (event.target === event.currentTarget) { const elementToFocus = event.currentTarget.querySelector( '[tabindex="0"]' diff --git a/src/hooks/useScrollState.ts b/src/hooks/useScrollState.ts index 8bb48a4f10..d2db5f6171 100644 --- a/src/hooks/useScrollState.ts +++ b/src/hooks/useScrollState.ts @@ -56,9 +56,9 @@ export function useScrollState(gridRef: React.RefObject): ); const getSnapshot = useCallback((): ScrollState => { - // gridRef.current is null during initial render, suspending, or - // to avoid returning a different state in those cases, - // we key the ref object instead of the element itself + // gridRef.current is null during the initial render, when suspending, or in . + // To avoid returning a different state in those cases, + // we key the ref object instead of the element itself. return scrollStateMap.get(gridRef) ?? initialScrollState; }, [gridRef]); diff --git a/src/hooks/useViewportColumns.ts b/src/hooks/useViewportColumns.ts index 6418370934..27ceefda2b 100644 --- a/src/hooks/useViewportColumns.ts +++ b/src/hooks/useViewportColumns.ts @@ -99,7 +99,7 @@ export function useViewportColumns({ ]); // Effective inclusive upper bound for overscan in the unfrozen band. - // When end-frozen columns exist, unfrozen band ends just before them. + // When end-frozen columns exist, the unfrozen band ends just before them. const effectiveOverscanEndIdx = firstEndFrozenColumnIndex > -1 ? min(colOverscanEndIdx, firstEndFrozenColumnIndex - 1) @@ -128,7 +128,7 @@ export function useViewportColumns({ } } - // Always yield end-frozen tail (virtualization must keep these in the DOM) + // Always yield the end-frozen tail (virtualization must keep these in the DOM) if (firstEndFrozenColumnIndex > -1) { for (let colIdx = firstEndFrozenColumnIndex; colIdx < columns.length; colIdx++) { yield columns[colIdx]; diff --git a/src/hooks/useViewportRows.ts b/src/hooks/useViewportRows.ts index 13b8502302..0af33ae1be 100644 --- a/src/hooks/useViewportRows.ts +++ b/src/hooks/useViewportRows.ts @@ -28,8 +28,8 @@ export function useViewportRows({ }; } - // Calcule the height of all the rows upfront. This can cause performance issues - // and we can consider using a similar approach as react-window + // Calculate the height of all the rows upfront. This can cause performance issues, + // and we can consider using a similar approach to react-window // https://github.com/bvaughn/react-window/blob/b0a470cc264e9100afcaa1b78ed59d88f7914ad4/src/VariableSizeList.js#L68 let totalRowHeight = 0; let gridTemplateRows = ''; diff --git a/src/style/core.ts b/src/style/core.ts index e2e0280c31..de7b620b39 100644 --- a/src/style/core.ts +++ b/src/style/core.ts @@ -28,6 +28,7 @@ const root = css` --rdg-row-selected-background-color: light-dark(hsl(207deg 76% 92%), hsl(207deg 76% 42%)); --rdg-row-selected-hover-background-color: light-dark(hsl(207deg 76% 88%), hsl(207deg 76% 38%)); --rdg-checkbox-focus-color: hsl(207deg 100% 69%); + --rdg-frozen-column-shadow-color: light-dark(rgb(0 0 0 / 15%), rgb(0 0 0 / 40%)); &.rdg-dark { color-scheme: dark; @@ -41,7 +42,7 @@ const root = css` accent-color: light-dark(hsl(207deg 100% 29%), hsl(207deg 100% 79%)); - /* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context */ + /* https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Positioned_layout/Stacking_context */ /* We set a stacking context so internal elements don't render on top of external elements. */ /* size containment is not used as it could break "width: min-content" for example, and the grid would infinitely resize on Chromium browsers */ contain: content; @@ -94,54 +95,54 @@ export const viewportDraggingClassname = `rdg-viewport-dragging ${viewportDraggi // Common properties shared by both start- and end-edge frozen-column shadows. // Variants below add only the direction-dependent properties (gradient + scroll-state predicate). -export const frozenColumnShadowClassname = css` - position: sticky; - width: 10px; - pointer-events: none; - z-index: 1; - opacity: 1; - transition: opacity 0.1s; - - &:dir(rtl) { - transform: scaleX(-1); +const frozenColumnShadowClassname = css` + @layer rdg.FrozenColumnShadow { + position: sticky; + width: 10px; + pointer-events: none; + z-index: 1; + opacity: 1; + transition: opacity 0.1s; + + &:dir(rtl) { + transform: scaleX(-1); + } } `; const frozenColumnShadowStartOverrides = css` - background-image: linear-gradient( - to right, - light-dark(rgb(0 0 0 / 15%), rgb(0 0 0 / 40%)), - transparent - ); - - /* TODO: reverse 'opacity' and remove 'not' */ - @container rdg-root not scroll-state(scrollable: inline-start) { - opacity: 0; + @layer rdg.FrozenColumnShadow { + background-image: linear-gradient(to right, var(--rdg-frozen-column-shadow-color), transparent); + + /* TODO: reverse 'opacity' and remove 'not' */ + @container rdg-root not scroll-state(scrollable: inline-start) { + opacity: 0; + } } `; const frozenColumnShadowEndOverrides = css` - background-image: linear-gradient( - to left, - light-dark(rgb(0 0 0 / 15%), rgb(0 0 0 / 40%)), - transparent - ); - - /* TODO: reverse 'opacity' and remove 'not' */ - @container rdg-root not scroll-state(scrollable: inline-end) { - opacity: 0; + @layer rdg.FrozenColumnShadow { + background-image: linear-gradient(to left, var(--rdg-frozen-column-shadow-color), transparent); + + /* TODO: reverse 'opacity' and remove 'not' */ + @container rdg-root not scroll-state(scrollable: inline-end) { + opacity: 0; + } } `; // Add shadow after the last start-frozen cell -export const frozenColumnShadowStartClassname = `${frozenColumnShadowClassname} ${frozenColumnShadowStartOverrides}`; +export const frozenColumnShadowStartClassname = `rdg-frozen-column-shadow rdg-frozen-column-shadow-start ${frozenColumnShadowClassname} ${frozenColumnShadowStartOverrides}`; // Add shadow before the first end-frozen cell (mirror of the start shadow) -export const frozenColumnShadowEndClassname = `${frozenColumnShadowClassname} ${frozenColumnShadowEndOverrides}`; +export const frozenColumnShadowEndClassname = `rdg-frozen-column-shadow rdg-frozen-column-shadow-end ${frozenColumnShadowClassname} ${frozenColumnShadowEndOverrides}`; const topShadowClassname = css` - /* render above header and summary rows */ - z-index: 2; + @layer rdg.FrozenColumnShadow { + /* render above header and summary rows */ + z-index: 2; + } `; export const frozenColumnShadowStartTopClassname = `${frozenColumnShadowStartClassname} ${topShadowClassname}`; diff --git a/src/style/layers.css b/src/style/layers.css index 03e4663f55..26d0b748ba 100644 --- a/src/style/layers.css +++ b/src/style/layers.css @@ -1,3 +1,3 @@ @layer rdg { - @layer Defaults, CheckboxInput, CheckboxIcon, CheckboxLabel, Cell, HeaderCell, SummaryCell, EditCell, Row, HeaderRow, SummaryRow, GroupedRow, Root; + @layer Defaults, Cell, HeaderCell, EditCell, Row, HeaderRow, GroupedRow, Root; } diff --git a/src/types.ts b/src/types.ts index bfbea5270d..fd70bde65f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -43,11 +43,11 @@ export interface Column { readonly renderGroupCell?: Maybe<(props: RenderGroupCellProps) => ReactNode>; /** Render function to render the content of edit cells. When set, the column is automatically set to be editable */ readonly renderEditCell?: Maybe<(props: RenderEditCellProps) => ReactNode>; - /** Enables cell editing. If set and no editor property specified, then a textinput will be used as the cell editor */ + /** Control whether cells can be edited with `renderEditCell` */ readonly editable?: Maybe boolean)>; readonly colSpan?: Maybe<(args: ColSpanArgs) => Maybe>; - /** Determines whether column is frozen, and on which edge. - * `true` is an alias for `'start'` for backwards compatibility. */ + /** Determines whether the column is frozen, and on which edge. + * `true` is an alias for `'start'` for backward compatibility. */ readonly frozen?: Maybe; /** Enable resizing of the column */ readonly resizable?: Maybe; @@ -80,8 +80,11 @@ export interface Column { } export interface CalculatedColumn extends Column { + /** The column group that contains the column, or `undefined` if the column is not in a group */ readonly parent: CalculatedColumnParent | undefined; + /** The index of the column in the grid. Column groups are flattened and frozen columns are moved to their edge, so it can differ from the column's index in `columns` */ readonly idx: number; + /** Header row level, counted from the bottom header row: always `0` for columns, and negative for column groups */ readonly level: number; readonly width: number | string; readonly minWidth: number; @@ -95,7 +98,7 @@ export interface CalculatedColumn extends Column { - /** The name of the column group, it will be displayed in the header cell */ + /** The name of the column group. It will be displayed in the header cell */ readonly name: string | ReactElement; readonly headerCellClass?: Maybe; readonly children: readonly ColumnOrColumnGroup[]; @@ -117,7 +120,9 @@ export type CalculatedColumnOrColumnGroup = | CalculatedColumn; export interface Position { + /** Column index, or `-1` for the whole row in `TreeDataGrid` */ readonly idx: number; + /** Row index: `0` is the first row, header rows and top summary rows have negative indexes, and bottom summary rows come after the last row */ readonly rowIdx: number; } @@ -185,7 +190,9 @@ export interface CellRendererProps extends BaseCellRendererPr } export type CellEvent> = E & { + /** Prevents the default grid behavior for this event */ preventGridDefault: () => void; + /** Returns whether `preventGridDefault` was called */ isGridDefaultPrevented: () => boolean; }; @@ -286,7 +293,9 @@ export interface RenderRowProps extends BaseRenderR } export interface RowsChangeData { + /** Indexes of the rows that changed */ indexes: number[]; + /** The column of the cells that changed */ column: CalculatedColumn; } @@ -345,21 +354,32 @@ export type RowHeightArgs = | { type: 'GROUP'; row: GroupRow }; export interface RenderSortIconProps { + /** The sort direction of the column, or `undefined` if it is not sorted */ sortDirection: SortDirection | undefined; } export interface RenderSortPriorityProps { + /** The column's position in `sortColumns`, starting at 1, when sorting by multiple columns, or `undefined` otherwise */ priority: number | undefined; } export interface RenderSortStatusProps extends RenderSortIconProps, RenderSortPriorityProps {} -export interface RenderCheckboxProps extends Pick< - React.ComponentProps<'input'>, - 'aria-label' | 'aria-labelledby' | 'checked' | 'tabIndex' | 'disabled' -> { +export interface RenderCheckboxProps { + /** Whether the checkbox is checked */ + checked?: boolean | undefined; + /** Whether the checkbox is in an indeterminate state, like the select all checkbox when only some rows are selected */ indeterminate?: boolean | undefined; + /** Whether the checkbox is disabled */ + disabled?: boolean | undefined; + /** Called when the checkbox is toggled. `shift` is `true` if the Shift key was held, to select a range of rows */ onChange: (checked: boolean, shift: boolean) => void; + /** `0` when the checkbox's cell is active and `-1` otherwise, so the grid can move focus from the cell to the checkbox */ + tabIndex?: number | undefined; + /** Accessible label for the checkbox */ + 'aria-label'?: string | undefined; + /** ID of the element that labels the checkbox */ + 'aria-labelledby'?: string | undefined; } export interface Renderers { diff --git a/src/utils/frozenColumnUtils.ts b/src/utils/frozenColumnUtils.ts index f3d41767a0..6e88ecb222 100644 --- a/src/utils/frozenColumnUtils.ts +++ b/src/utils/frozenColumnUtils.ts @@ -1,6 +1,6 @@ import type { ColumnFrozen } from '../types'; -// Shared predicate — `frozen: true` is the backwards-compatible alias for `frozen: 'start'`. +// Shared predicate — `frozen: true` is the backward-compatible alias for `frozen: 'start'`. export function isStartFrozen(frozen: ColumnFrozen): boolean { return frozen === true || frozen === 'start'; } diff --git a/src/utils/index.ts b/src/utils/index.ts index a77cc18d90..16b787884c 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -25,7 +25,7 @@ export function clampColumnWidth( ): number { width = max(width, minWidth); - // ignore maxWidth if it less than minWidth + // ignore maxWidth if it is less than minWidth if (typeof maxWidth === 'number' && maxWidth >= minWidth) { return min(width, maxWidth); } diff --git a/src/utils/keyboardUtils.ts b/src/utils/keyboardUtils.ts index a8d5ebf732..9b0683875c 100644 --- a/src/utils/keyboardUtils.ts +++ b/src/utils/keyboardUtils.ts @@ -1,7 +1,7 @@ import type { Direction, Maybe } from '../types'; import { isElement } from './domUtils'; -// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values +// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values const nonInputKeys = new Set([ // Special keys 'Unidentified', @@ -55,8 +55,8 @@ export function isCtrlKeyHeldDown(e: React.KeyboardEvent): boolean { return (e.ctrlKey || e.metaKey) && e.key !== 'Control'; } -// event.key may differ by keyboard input language, so we use event.keyCode instead -// event.nativeEvent.code cannot be used either as it would break copy/paste for the DVORAK layout +// event.key may differ by keyboard input language, so we use event.keyCode instead. +// event.nativeEvent.code cannot be used either, as it would break copy/paste for the Dvorak layout. const vKey = 86; export function isDefaultCellInput( @@ -72,7 +72,7 @@ export function isDefaultCellInput( * By default, the following navigation keys are enabled while an editor is open, under specific conditions: * - Tab: * - The editor must be an , a