diff --git a/.circleci/config.yml b/.circleci/config.yml
index adb1bf6c916b..05cc3abd94bc 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -5,8 +5,8 @@ parameters:
description: Whether to force browserstack usage. We have limited resources on browserstack so the pipeline might decide to skip browserstack if this parameter isn't set to true.
type: boolean
default: false
- react-dist-tag:
- description: The dist-tag of react to be used
+ react-version:
+ description: The version of react to be used
type: string
default: stable
workflow:
@@ -20,10 +20,10 @@ parameters:
default-job: &default-job
parameters:
- react-dist-tag:
- description: The dist-tag of react to be used
+ react-version:
+ description: The version of react to be used
type: string
- default: << pipeline.parameters.react-dist-tag >>
+ default: << pipeline.parameters.react-version >>
e2e-base-url:
description: The base url for running end-to-end test
type: string
@@ -33,7 +33,7 @@ default-job: &default-job
PLAYWRIGHT_BROWSERS_PATH: /tmp/pw-browsers
# expose it globally otherwise we have to thread it from each job to the install command
BROWSERSTACK_FORCE: << pipeline.parameters.browserstack-force >>
- REACT_DIST_TAG: << parameters.react-dist-tag >>
+ REACT_VERSION: << parameters.react-version >>
working_directory: /tmp/mui
docker:
- image: cimg/node:18.20
@@ -59,6 +59,13 @@ commands:
description: 'Set to true if you intend to any browser (for example with playwright).'
steps:
+ - run:
+ name: Resolve React version
+ command: |
+ node scripts/useReactVersion.mjs
+ # log a patch for maintainers who want to check out this change
+ git --no-pager diff HEAD
+
- when:
condition: << parameters.browsers >>
steps:
@@ -90,7 +97,17 @@ commands:
pnpm --version
- run:
name: Install js dependencies
- command: pnpm install
+ command: |
+ echo "React version $REACT_VERSION"
+ if [ $REACT_VERSION == "stable" ];
+ then
+ echo "pnpm install"
+ pnpm install
+ else
+ echo "pnpm install --no-frozen-lockfile"
+ pnpm install --no-frozen-lockfile
+ fi
+
- when:
condition: << parameters.browsers >>
steps:
@@ -115,7 +132,7 @@ jobs:
name: Should not have any git not staged
command: git add -A && git diff --exit-code --staged
- run:
- name: Check for duplicated packages
+ name: '`pnpm dedupe` was run?'
command: |
if [[ $(git diff --name-status next | grep pnpm-lock) == "" ]];
then
@@ -146,7 +163,7 @@ jobs:
command: |
curl -Os https://uploader.codecov.io/latest/linux/codecov
chmod +x codecov
- ./codecov -t ${CODECOV_TOKEN} -Z -F "$REACT_DIST_TAG-jsdom"
+ ./codecov -t ${CODECOV_TOKEN} -Z -F "$REACT_VERSION-jsdom"
test_lint:
<<: *default-job
steps:
@@ -330,3 +347,31 @@ workflows:
- test_e2e_website:
requires:
- checkout
+
+ react-next:
+ when:
+ equal: [react-next, << pipeline.parameters.workflow >>]
+ # triggers:
+ # - schedule:
+ # cron: '0 0 * * *'
+ # filters:
+ # branches:
+ # only:
+ # - master
+ jobs:
+ - test_unit:
+ <<: *default-context
+ react-version: next
+ name: test_unit-react@next
+ - test_browser:
+ <<: *default-context
+ react-version: next
+ name: test_browser-react@next
+ - test_regressions:
+ <<: *default-context
+ react-version: next
+ name: test_regressions-react@next
+ - test_e2e:
+ <<: *default-context
+ react-version: next
+ name: test_e2e-react@next
diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json
index 37d8d1c44544..2bcfb7926690 100644
--- a/.codesandbox/ci.json
+++ b/.codesandbox/ci.json
@@ -11,7 +11,8 @@
"packages/x-date-pickers",
"packages/x-date-pickers-pro",
"packages/x-charts",
- "packages/x-tree-view"
+ "packages/x-tree-view",
+ "packages/x-internals"
],
"publishDirectory": {
"@mui/x-license": "packages/x-license/build",
@@ -24,7 +25,8 @@
"@mui/x-charts": "packages/x-charts/build",
"@mui/x-charts-pro": "packages/x-charts-pro/build",
"@mui/x-tree-view": "packages/x-tree-view/build",
- "@mui/x-tree-view-pro": "packages/x-tree-view-pro/build"
+ "@mui/x-tree-view-pro": "packages/x-tree-view-pro/build",
+ "@mui/x-internals": "packages/x-internals/build"
},
"sandboxes": ["/bug-reproductions/x-data-grid"],
"silent": true
diff --git a/.eslintrc.js b/.eslintrc.js
index 3afad759075e..f4762cac6c02 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,6 +1,19 @@
const baseline = require('@mui/monorepo/.eslintrc');
const path = require('path');
+const chartsPackages = ['x-charts', 'x-charts-pro'];
+
+const dataGridPackages = [
+ 'x-data-grid',
+ 'x-data-grid-pro',
+ 'x-data-grid-premium',
+ 'x-data-grid-generator',
+];
+
+const datePickersPackages = ['x-date-pickers', 'x-date-pickers-pro'];
+
+const treeViewPackages = ['x-tree-view', 'x-tree-view-pro'];
+
// Enable React Compiler Plugin rules globally
const ENABLE_REACT_COMPILER_PLUGIN = process.env.ENABLE_REACT_COMPILER_PLUGIN ?? false;
@@ -139,6 +152,18 @@ module.exports = {
...(ENABLE_REACT_COMPILER_PLUGIN ? { 'react-compiler/react-compiler': 'error' } : {}),
// TODO move to @mui/monorepo/.eslintrc, codebase is moving away from default exports
'import/prefer-default-export': 'off',
+ 'import/no-restricted-paths': [
+ 'error',
+ {
+ zones: [...chartsPackages, ...datePickersPackages, ...treeViewPackages].map(
+ (packageName) => ({
+ target: `./packages/${packageName}/src/**/!(*.test.*|*.spec.*)`,
+ from: `./packages/${packageName}/src/internals/index.ts`,
+ message: `Use a more specific import instead. E.g. import { MyInternal } from '../internals/MyInternal';`,
+ }),
+ ),
+ },
+ ],
// TODO move rule into the main repo once it has upgraded
'@typescript-eslint/return-await': 'off',
'no-restricted-imports': 'off',
@@ -167,7 +192,7 @@ module.exports = {
// TODO move to @mui/monorepo/.eslintrc
// TODO Fix props names to not conflict
'react/jsx-no-duplicate-props': [1, { ignoreCase: false }],
- // TOOD move to @mui/monorepo/.eslintrc, these are false positive
+ // TODO move to @mui/monorepo/.eslintrc, these are false positive
'react/no-unstable-nested-components': ['error', { allowAsProps: true }],
},
overrides: [
@@ -257,18 +282,9 @@ module.exports = {
...buildPackageRestrictedImports('@mui/x-tree-view-pro', 'x-tree-view-pro', false),
...buildPackageRestrictedImports('@mui/x-license', 'x-license'),
- ...addReactCompilerRule(['x-charts', 'x-charts-pro'], ENABLE_REACT_COMPILER_PLUGIN_CHARTS),
- ...addReactCompilerRule(
- ['x-data-grid', 'x-data-grid-pro', 'x-data-grid-premium', 'x-data-grid-generator'],
- ENABLE_REACT_COMPILER_PLUGIN_DATA_GRID,
- ),
- ...addReactCompilerRule(
- ['x-date-pickers', 'x-date-pickers-pro'],
- ENABLE_REACT_COMPILER_PLUGIN_DATE_PICKERS,
- ),
- ...addReactCompilerRule(
- ['x-tree-view', 'x-tree-view-pro'],
- ENABLE_REACT_COMPILER_PLUGIN_TREE_VIEW,
- ),
+ ...addReactCompilerRule(chartsPackages, ENABLE_REACT_COMPILER_PLUGIN_CHARTS),
+ ...addReactCompilerRule(dataGridPackages, ENABLE_REACT_COMPILER_PLUGIN_DATA_GRID),
+ ...addReactCompilerRule(datePickersPackages, ENABLE_REACT_COMPILER_PLUGIN_DATE_PICKERS),
+ ...addReactCompilerRule(treeViewPackages, ENABLE_REACT_COMPILER_PLUGIN_TREE_VIEW),
],
};
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index ecf1ede8f2cb..d803a2ae9313 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
+ uses: github/codeql-action/init@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
with:
languages: typescript
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -29,4 +29,4 @@ jobs:
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
+ uses: github/codeql-action/analyze@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index 2fb7dd32df08..2caad354b82e 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -44,6 +44,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: Upload to code-scanning
- uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
+ uses: github/codeql-action/upload-sarif@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
with:
sarif_file: results.sarif
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 62c83f81b4bf..55f098a29783 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,171 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## 7.9.0
+
+_Jul 5, 2024_
+
+We'd like to offer a big thanks to the 7 contributors who made this release possible. Here are some highlights β¨:
+
+- π Add loading overlay variants, including a skeleton loader option to the Data Grid component. See [Loading overlay docs](https://mui.com/x/react-data-grid/overlays/#loading-overlay) for more details.
+- π³ Add `selectItem` and `getItemDOMElement` methods to the TreeView component public API
+- βοΈ Make the `usePickersTranslations` hook public in the pickers component
+- π Bugfixes
+
+
+
+### Data Grid
+
+#### `@mui/x-data-grid@7.9.0`
+
+- [DataGrid] Add skeleton loading overlay support (#13293) @KenanYusuf
+- [DataGrid] Fix pagination when `pagination={undefined}` (#13349) @sai6855
+
+#### `@mui/x-data-grid-pro@7.9.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
+
+Same changes as in `@mui/x-data-grid@7.9.0`.
+
+#### `@mui/x-data-grid-premium@7.9.0` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
+
+Same changes as in `@mui/x-data-grid-pro@7.9.0`.
+
+### Date and Time Pickers
+
+#### `@mui/x-date-pickers@7.9.0`
+
+- [pickers] Make the `usePickersTranslations` hook public (#13657) @flaviendelangle
+
+#### `@mui/x-date-pickers-pro@7.9.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
+
+Same changes as in `@mui/x-date-pickers@7.9.0`.
+
+### Charts
+
+#### `@mui/x-charts@7.9.0`
+
+- [charts] Add Heatmap (unreleased) (#13209) @alexfauquette
+- [charts] Add initial `Zoom&Pan` to the Pro charts (unreleased) (#13405) @JCQuintas
+- [charts] Fix Axis Highlight on horizontal bar charts regression (#13717) @JCQuintas
+- [charts] Improve charts interaction for mobile users (#13692) @JCQuintas
+- [charts] Add documentation on how to disable the tooltip on charts (#13724) @JCQuintas
+
+### Tree View
+
+#### `@mui/x-tree-view@7.9.0`
+
+- [TreeView] Add `selectItem` and `getItemDOMElement` methods to the public API (#13485) @flaviendelangle
+
+### Docs
+
+- [docs] Fix custom "no results overlay" demo in dark mode (#13715) @KenanYusuf
+
+### Core
+
+- [core] Add `react_next` workflow in CircleCI (#13360) @cherniavskii
+- [core] Create a new package to share utils across X packages (#13528) @flaviendelangle
+- [core] Fix dependency setup (#13684) @LukasTy
+- [core] Remove `jscodeshift-add-imports` package (#13720) @LukasTy
+- [code-infra] Cleanup monorepo and `@mui/docs` usage (#13713) @LukasTy
+
+## 7.8.0
+
+_Jun 28, 2024_
+
+We'd like to offer a big thanks to the 10 contributors who made this release possible. Here are some highlights β¨:
+
+- π° Introduce server-side data source for improved server integration in the Data Grid.
+
+ Supports server-side pagination, sorting and filtering on plain and tree data, and automatic caching.
+
+ To enable, provide a `getRows` function to the `unstable_dataSource` prop on the Data Grid component.
+
+ ```tsx
+ const dataSource = {
+ getRows: async (params: GridServerGetRowsParams) => {
+ const data = await fetch(
+ `https://api.example.com/data?${new URLSearchParams({
+ page: params.page,
+ pageSize: params.pageSize,
+ sortModel: JSON.stringify(params.sortModel),
+ filterModel: JSON.stringify(params.filterModel),
+ }).toString()}`,
+ );
+ return {
+ rows: data.rows,
+ totalRows: data.totalRows,
+ };
+ },
+ }
+
+ ```
+
+ See [server-side data documentation](https://mui.com/x/react-data-grid/server-side-data/) for more details.
+
+- π Support Date data on the BarChart component
+- βοΈ Support custom column sort icons on the Data Grid
+- π±οΈ Support modifying the expansion trigger on the Tree View components
+
+
+
+### Data Grid
+
+#### `@mui/x-data-grid@7.8.0`
+
+- [DataGrid] Add `columnHeaderSortIcon` slot (#13563) @arminmeh
+- [DataGrid] Fix dimensions lag issue after autosize (#13587) @MBilalShafi
+- [DataGrid] Fix print export failure when `hideFooter` option is set (#13034) @tarunrajput
+
+#### `@mui/x-data-grid-pro@7.8.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
+
+Same changes as in `@mui/x-data-grid@7.8.0`, plus:
+
+- [DataGridPro] Fix multi-sorting indicator being cut off (#13625) @KenanYusuf
+- [DataGridPro] Server-side tree data support (#12317) @MBilalShafi
+
+#### `@mui/x-data-grid-premium@7.8.0` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
+
+Same changes as in `@mui/x-data-grid-pro@7.8.0`.
+
+### Date and Time Pickers
+
+#### `@mui/x-date-pickers@7.8.0`
+
+- [fields] Fix section clearing behavior on Android (#13652) @LukasTy
+
+#### `@mui/x-date-pickers-pro@7.8.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
+
+Same changes as in `@mui/x-date-pickers@7.8.0`.
+
+### Charts
+
+#### `@mui/x-charts@7.8.0`
+
+- [charts] Fix line chart props not passing correct event handlers (#13609) @JCQuintas
+- [charts] Support BarChart with `Date` data (#13471) @alexfauquette
+- [charts] Support RTL for y-axis (#13614) @alexfauquette
+- [charts] Use default values instead of non-null assertion to prevent error being thrown (#13637) @JCQuintas
+
+### Tree View
+
+#### `@mui/x-tree-view@7.8.0`
+
+- [TreeView] Add `expansionTrigger` prop (#13533) @noraleonte
+- [TreeView] Support experimental features from plugin's dependencies (#13632) @flaviendelangle
+
+### Docs
+
+- [docs] Add callout for `Luxon` `throwOnInvalid` support (#13621) @LukasTy
+- [docs] Add "Overlays" section to the Data Grid documentation (#13624) @KenanYusuf
+
+### Core
+
+- [core] Add eslint rule to restrict import from `../internals` root (#13633) @JCQuintas
+- [docs-infra] Sync `\_app` folder with monorepo (#13582) @Janpot
+- [license] Allow usage of charts and tree view pro package for old premium licenses (#13619) @flaviendelangle
+
## 7.7.1
_Jun 21, 2024_
@@ -44,7 +209,7 @@ Same changes as in `@mui/x-data-grid-pro@7.7.1`.
- [pickers] Always use the same timezone in the field, the view and the layout components (#13481) @flaviendelangle
- [pickers] Fix `AdapterDateFnsV3` generated method types (#13464) @alexey-kozlenkov
- [pickers] Fix controlled `view` behavior (#13552) @LukasTy
-- [TimePicker] Improves RTL verification for the time pickers default views (#13447) @arthurbalduini
+- [TimePicker] Improves RTL verification for the time pickers default views (#13447) @arthurbalduini
#### `@mui/x-date-pickers-pro@7.7.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
diff --git a/babel.config.js b/babel.config.js
index e10b6c313097..9e2bc852d0ed 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -18,6 +18,7 @@ const defaultAlias = {
'@mui/x-charts-pro': resolveAliasPath('./packages/x-charts-pro/src'),
'@mui/x-tree-view': resolveAliasPath('./packages/x-tree-view/src'),
'@mui/x-tree-view-pro': resolveAliasPath('./packages/x-tree-view-pro/src'),
+ '@mui/x-internals': resolveAliasPath('./packages/x-internals/src'),
'@mui/material-nextjs': '@mui/monorepo/packages/mui-material-nextjs/src',
'@mui-internal/api-docs-builder': resolveAliasPath(
'./node_modules/@mui/monorepo/packages/api-docs-builder',
diff --git a/changelogOld/CHANGELOG.v4.md b/changelogOld/CHANGELOG.v4.md
index 662652efd208..2c3c89c28fc7 100644
--- a/changelogOld/CHANGELOG.v4.md
+++ b/changelogOld/CHANGELOG.v4.md
@@ -89,7 +89,7 @@ A big thanks to the 6 contributors who made this release possible. Here are some
- [core] Simplify `useGridColumns` hook (#2343) @oliviertassinari
- [core] Update `doesSupportTouchActionNone` implementation (#2378) @DanailH
- [core] Upgrade dependency with the monorepo (#2377) @oliviertassinari
-- [test] Use `.not.to.equal` in favour of `to.not.equal` (#2340) @oliviertassinari
+- [test] Use `.not.to.equal` in favor of `.to.not.equal` (#2340) @oliviertassinari
## 4.0.0-alpha.37
diff --git a/docs/.link-check-errors.txt b/docs/.link-check-errors.txt
index 10b54359e79c..6ecc503cc78a 100644
--- a/docs/.link-check-errors.txt
+++ b/docs/.link-check-errors.txt
@@ -1,3 +1,2 @@
Broken links found by `docs:link-check` that exist:
-- https://mui.com/x/react-charts/heat-map/
diff --git a/docs/data/charts-component-api-pages.ts b/docs/data/charts-component-api-pages.ts
index 3a0d808b2d06..c1ed7174bf0e 100644
--- a/docs/data/charts-component-api-pages.ts
+++ b/docs/data/charts-component-api-pages.ts
@@ -45,10 +45,6 @@ const apiPages: MuiPage[] = [
pathname: '/x/api/charts/charts-axis-highlight',
title: 'ChartsAxisHighlight',
},
- {
- pathname: '/x/api/charts/charts-axis-tooltip-content',
- title: 'ChartsAxisTooltipContent',
- },
{
pathname: '/x/api/charts/charts-clip-path',
title: 'ChartsClipPath',
@@ -57,10 +53,6 @@ const apiPages: MuiPage[] = [
pathname: '/x/api/charts/charts-grid',
title: 'ChartsGrid',
},
- {
- pathname: '/x/api/charts/charts-item-tooltip-content',
- title: 'ChartsItemTooltipContent',
- },
{
pathname: '/x/api/charts/charts-legend',
title: 'ChartsLegend',
diff --git a/docs/data/charts/gauge/gauge.md b/docs/data/charts/gauge/gauge.md
index 6826a74780f9..30c5df560756 100644
--- a/docs/data/charts/gauge/gauge.md
+++ b/docs/data/charts/gauge/gauge.md
@@ -11,7 +11,7 @@ waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/meter/
Gauge charts let the user evaluate metrics.
-{{"component": "modules/components/ComponentLinkHeader.js", "design": false}}
+{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
## Basics
diff --git a/docs/data/charts/heatmap/BasicHeatmap.js b/docs/data/charts/heatmap/BasicHeatmap.js
new file mode 100644
index 000000000000..1f3410543e9b
--- /dev/null
+++ b/docs/data/charts/heatmap/BasicHeatmap.js
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import '@mui/x-charts-pro/typeOverloads';
+import { UnstableHeatmap } from '@mui/x-charts-pro/Heatmap';
+import { data } from './dumbData';
+
+export default function BasicHeatmap() {
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/charts/heatmap/BasicHeatmap.tsx b/docs/data/charts/heatmap/BasicHeatmap.tsx
new file mode 100644
index 000000000000..1f3410543e9b
--- /dev/null
+++ b/docs/data/charts/heatmap/BasicHeatmap.tsx
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import '@mui/x-charts-pro/typeOverloads';
+import { UnstableHeatmap } from '@mui/x-charts-pro/Heatmap';
+import { data } from './dumbData';
+
+export default function BasicHeatmap() {
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/charts/heatmap/BasicHeatmap.tsx.preview b/docs/data/charts/heatmap/BasicHeatmap.tsx.preview
new file mode 100644
index 000000000000..bfe0e2212184
--- /dev/null
+++ b/docs/data/charts/heatmap/BasicHeatmap.tsx.preview
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/docs/data/charts/heatmap/ColorConfig.js b/docs/data/charts/heatmap/ColorConfig.js
new file mode 100644
index 000000000000..f99083ed8765
--- /dev/null
+++ b/docs/data/charts/heatmap/ColorConfig.js
@@ -0,0 +1,123 @@
+import * as React from 'react';
+import { interpolateBlues } from 'd3-scale-chromatic';
+import '@mui/x-charts-pro/typeOverloads';
+import { UnstableHeatmap } from '@mui/x-charts-pro/Heatmap';
+
+const dataset = [
+ {
+ london: 59,
+ paris: 57,
+ newYork: 86,
+ seoul: 21,
+ month: 'January',
+ },
+ {
+ london: 50,
+ paris: 52,
+ newYork: 78,
+ seoul: 28,
+ month: 'February',
+ },
+ {
+ london: 47,
+ paris: 53,
+ newYork: 106,
+ seoul: 41,
+ month: 'March',
+ },
+ {
+ london: 54,
+ paris: 56,
+ newYork: 92,
+ seoul: 73,
+ month: 'April',
+ },
+ {
+ london: 57,
+ paris: 69,
+ newYork: 92,
+ seoul: 99,
+ month: 'May',
+ },
+ {
+ london: 60,
+ paris: 63,
+ newYork: 103,
+ seoul: 144,
+ month: 'June',
+ },
+ {
+ london: 59,
+ paris: 60,
+ newYork: 105,
+ seoul: 319,
+ month: 'July',
+ },
+ {
+ london: 65,
+ paris: 60,
+ newYork: 106,
+ seoul: 249,
+ month: 'August',
+ },
+ {
+ london: 51,
+ paris: 51,
+ newYork: 95,
+ seoul: 131,
+ month: 'September',
+ },
+ {
+ london: 60,
+ paris: 65,
+ newYork: 97,
+ seoul: 55,
+ month: 'October',
+ },
+ {
+ london: 67,
+ paris: 64,
+ newYork: 76,
+ seoul: 48,
+ month: 'November',
+ },
+ {
+ london: 61,
+ paris: 70,
+ newYork: 103,
+ seoul: 25,
+ month: 'December',
+ },
+];
+
+const data = dataset.flatMap(({ london, paris, newYork, seoul }, monthIndex) => [
+ [0, monthIndex, london],
+ [1, monthIndex, paris],
+ [2, monthIndex, newYork],
+ [3, monthIndex, seoul],
+]);
+
+const xData = ['London', 'Paris', 'NewYork', 'Seoul'];
+const yData = dataset.flatMap(({ month }) => month);
+
+export default function ColorConfig() {
+ return (
+
+ );
+}
diff --git a/docs/data/charts/heatmap/ColorConfig.tsx b/docs/data/charts/heatmap/ColorConfig.tsx
new file mode 100644
index 000000000000..88957b1db0ab
--- /dev/null
+++ b/docs/data/charts/heatmap/ColorConfig.tsx
@@ -0,0 +1,126 @@
+import * as React from 'react';
+import { interpolateBlues } from 'd3-scale-chromatic';
+import '@mui/x-charts-pro/typeOverloads';
+import { UnstableHeatmap } from '@mui/x-charts-pro/Heatmap';
+import { HeatmapValueType } from '@mui/x-charts-pro/models';
+
+const dataset = [
+ {
+ london: 59,
+ paris: 57,
+ newYork: 86,
+ seoul: 21,
+ month: 'January',
+ },
+ {
+ london: 50,
+ paris: 52,
+ newYork: 78,
+ seoul: 28,
+ month: 'February',
+ },
+ {
+ london: 47,
+ paris: 53,
+ newYork: 106,
+ seoul: 41,
+ month: 'March',
+ },
+ {
+ london: 54,
+ paris: 56,
+ newYork: 92,
+ seoul: 73,
+ month: 'April',
+ },
+ {
+ london: 57,
+ paris: 69,
+ newYork: 92,
+ seoul: 99,
+ month: 'May',
+ },
+ {
+ london: 60,
+ paris: 63,
+ newYork: 103,
+ seoul: 144,
+ month: 'June',
+ },
+ {
+ london: 59,
+ paris: 60,
+ newYork: 105,
+ seoul: 319,
+ month: 'July',
+ },
+ {
+ london: 65,
+ paris: 60,
+ newYork: 106,
+ seoul: 249,
+ month: 'August',
+ },
+ {
+ london: 51,
+ paris: 51,
+ newYork: 95,
+ seoul: 131,
+ month: 'September',
+ },
+ {
+ london: 60,
+ paris: 65,
+ newYork: 97,
+ seoul: 55,
+ month: 'October',
+ },
+ {
+ london: 67,
+ paris: 64,
+ newYork: 76,
+ seoul: 48,
+ month: 'November',
+ },
+ {
+ london: 61,
+ paris: 70,
+ newYork: 103,
+ seoul: 25,
+ month: 'December',
+ },
+];
+
+const data = dataset.flatMap(
+ ({ london, paris, newYork, seoul }, monthIndex): HeatmapValueType[] => [
+ [0, monthIndex, london],
+ [1, monthIndex, paris],
+ [2, monthIndex, newYork],
+ [3, monthIndex, seoul],
+ ],
+);
+
+const xData = ['London', 'Paris', 'NewYork', 'Seoul'];
+const yData = dataset.flatMap(({ month }) => month);
+
+export default function ColorConfig() {
+ return (
+
+ );
+}
diff --git a/docs/data/charts/heatmap/CustomItem.js b/docs/data/charts/heatmap/CustomItem.js
new file mode 100644
index 000000000000..af945337ef9e
--- /dev/null
+++ b/docs/data/charts/heatmap/CustomItem.js
@@ -0,0 +1,46 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import '@mui/x-charts-pro/typeOverloads';
+import { UnstableHeatmap } from '@mui/x-charts-pro/Heatmap';
+import { data } from './dumbData';
+
+function CustomCell(props) {
+ const { x, y, width, height, ownerState, ...other } = props;
+
+ return (
+
+
+
+ {ownerState.value}
+
+
+ );
+}
+export default function CustomItem() {
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/charts/heatmap/CustomItem.tsx b/docs/data/charts/heatmap/CustomItem.tsx
new file mode 100644
index 000000000000..68d8357eba5f
--- /dev/null
+++ b/docs/data/charts/heatmap/CustomItem.tsx
@@ -0,0 +1,46 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import '@mui/x-charts-pro/typeOverloads';
+import { UnstableHeatmap } from '@mui/x-charts-pro/Heatmap';
+import { data } from './dumbData';
+
+function CustomCell(props: any) {
+ const { x, y, width, height, ownerState, ...other } = props;
+
+ return (
+
+
+
+ {ownerState.value}
+
+
+ );
+}
+export default function CustomItem() {
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/charts/heatmap/CustomItem.tsx.preview b/docs/data/charts/heatmap/CustomItem.tsx.preview
new file mode 100644
index 000000000000..9c1801cf7cd4
--- /dev/null
+++ b/docs/data/charts/heatmap/CustomItem.tsx.preview
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/docs/data/charts/heatmap/HighlightClasses.js b/docs/data/charts/heatmap/HighlightClasses.js
new file mode 100644
index 000000000000..b27be9e2dd09
--- /dev/null
+++ b/docs/data/charts/heatmap/HighlightClasses.js
@@ -0,0 +1,38 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import '@mui/x-charts-pro/typeOverloads';
+import { UnstableHeatmap, heatmapClasses } from '@mui/x-charts-pro/Heatmap';
+import { data } from './dumbData';
+
+export default function HighlightClasses() {
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/charts/heatmap/HighlightClasses.tsx b/docs/data/charts/heatmap/HighlightClasses.tsx
new file mode 100644
index 000000000000..b27be9e2dd09
--- /dev/null
+++ b/docs/data/charts/heatmap/HighlightClasses.tsx
@@ -0,0 +1,38 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import '@mui/x-charts-pro/typeOverloads';
+import { UnstableHeatmap, heatmapClasses } from '@mui/x-charts-pro/Heatmap';
+import { data } from './dumbData';
+
+export default function HighlightClasses() {
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/charts/heatmap/HighlightHeatmap.js b/docs/data/charts/heatmap/HighlightHeatmap.js
new file mode 100644
index 000000000000..10996aaeaac8
--- /dev/null
+++ b/docs/data/charts/heatmap/HighlightHeatmap.js
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import '@mui/x-charts-pro/typeOverloads';
+import { UnstableHeatmap } from '@mui/x-charts-pro/Heatmap';
+import { data } from './dumbData';
+
+export default function HighlightHeatmap() {
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/charts/heatmap/HighlightHeatmap.tsx b/docs/data/charts/heatmap/HighlightHeatmap.tsx
new file mode 100644
index 000000000000..10996aaeaac8
--- /dev/null
+++ b/docs/data/charts/heatmap/HighlightHeatmap.tsx
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import '@mui/x-charts-pro/typeOverloads';
+import { UnstableHeatmap } from '@mui/x-charts-pro/Heatmap';
+import { data } from './dumbData';
+
+export default function HighlightHeatmap() {
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/charts/heatmap/HighlightHeatmap.tsx.preview b/docs/data/charts/heatmap/HighlightHeatmap.tsx.preview
new file mode 100644
index 000000000000..5d6588033cb7
--- /dev/null
+++ b/docs/data/charts/heatmap/HighlightHeatmap.tsx.preview
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/docs/data/charts/heatmap/dumbData.ts b/docs/data/charts/heatmap/dumbData.ts
new file mode 100644
index 000000000000..4aa53b77c337
--- /dev/null
+++ b/docs/data/charts/heatmap/dumbData.ts
@@ -0,0 +1,24 @@
+import { HeatmapValueType } from '@mui/x-charts-pro/models';
+
+export const data: HeatmapValueType[] = [
+ [0, 0, 10],
+ [0, 1, 20],
+ [0, 2, 40],
+ [0, 3, 90],
+ [0, 4, 70],
+ [1, 0, 30],
+ [1, 1, 50],
+ [1, 2, 10],
+ [1, 3, 70],
+ [1, 4, 40],
+ [2, 0, 50],
+ [2, 1, 20],
+ [2, 2, 90],
+ [2, 3, 20],
+ [2, 4, 70],
+ [3, 0, 40],
+ [3, 1, 50],
+ [3, 2, 20],
+ [3, 3, 70],
+ [3, 4, 90],
+];
diff --git a/docs/data/charts/heatmap/heatmap.md b/docs/data/charts/heatmap/heatmap.md
index 6d6413b064b8..3f662ec18a17 100644
--- a/docs/data/charts/heatmap/heatmap.md
+++ b/docs/data/charts/heatmap/heatmap.md
@@ -5,11 +5,57 @@ productId: x-charts
# Charts - Heatmap [ ](/x/introduction/licensing/#pro-plan 'Pro plan')π§
-Heatmap charts allow to highlight correlation between categories.
+Heatmap charts visually represents data with color variations to highlight patterns and trends across two dimensions.
:::warning
-The Heatmap Chart component isn't available yet, but you can upvote [**this GitHub issue**](https://github.com/mui/mui-x/issues/7926) to see it arrive sooner.
-
-Don't hesitate to leave a comment there to influence what gets built.
-Especially if you already have a use case for this component, or if you're facing a pain point with your current solution.
+The Heatmap Chart component isn't stable. Don't hesitate to open issues to give feedback.
:::
+
+## Basics
+
+The Heatmap requires two axes with `data` properties.
+Those data defined the x and y categories.
+
+The series `data` is an array of 3-tuples.
+The 2 first numbers are respectively the x and y indexes of the cell.
+And the third is its value.
+
+{{"demo": "BasicHeatmap.js"}}
+
+## Customization
+
+### Color mapping
+
+To customize the color mapping, use the `zAxis` configuration.
+You can either use the piecewise or continuous [color mapping](https://mui.com/x/react-charts/styling/#values-color).
+
+{{"demo": "ColorConfig.js"}}
+
+### Highlight
+
+You can chose to highlight the hovered element by setting `highlightScope.highlight` to `'item'`.
+To fade the other item, set `highlightScope.fade` to `'global'`.
+
+{{"demo": "HighlightHeatmap.js"}}
+
+By default highlighted/faded effect is obtained by applying the CSS property `filter: saturate(...)` to cells.
+To modify this styling, use the `heatmapClasses.highlighted` and `heatmapClasses.faded` CSS classes to override the applied style.
+
+In the following demo, we replace the highlight saturation by a border radius and reduce the saturation of the faded cells.
+
+{{"demo": "HighlightClasses.js"}}
+
+### Axes
+
+The Heatmap axes can be customized like any other chart axis.
+The available options are available in the [dedicated page](/x/react-charts/axis/#axis-customization).
+
+### Tooltip π§
+
+## Legend π§
+
+## Labels π§
+
+## Custom item
+
+{{"demo": "CustomItem.js"}}
diff --git a/docs/data/charts/overview/overview.md b/docs/data/charts/overview/overview.md
index 809be9b9a09c..ef86f4a0793f 100644
--- a/docs/data/charts/overview/overview.md
+++ b/docs/data/charts/overview/overview.md
@@ -9,7 +9,7 @@ packageName: '@mui/x-charts'
A fast and extendable library of react chart components for data visualization.
-{{"component": "modules/components/ComponentLinkHeader.js", "design": false}}
+{{"component": "@mui/docs/ComponentLinkHeader", "design": false}}
## Overview
diff --git a/docs/data/charts/pie-demo/OnSeriesItemClick.js b/docs/data/charts/pie-demo/OnSeriesItemClick.js
index 9d8700081026..fadc705a390c 100644
--- a/docs/data/charts/pie-demo/OnSeriesItemClick.js
+++ b/docs/data/charts/pie-demo/OnSeriesItemClick.js
@@ -50,7 +50,7 @@ ${formatObject(identifier)}`}
data: items,
},
]}
- onClick={handleClick}
+ onItemClick={handleClick}
width={400}
height={200}
margin={{ right: 200 }}
diff --git a/docs/data/charts/pie-demo/OnSeriesItemClick.tsx b/docs/data/charts/pie-demo/OnSeriesItemClick.tsx
index 69a1aa4b7246..2a3bb8c59293 100644
--- a/docs/data/charts/pie-demo/OnSeriesItemClick.tsx
+++ b/docs/data/charts/pie-demo/OnSeriesItemClick.tsx
@@ -54,7 +54,7 @@ ${formatObject(identifier)}`}
data: items,
},
]}
- onClick={handleClick}
+ onItemClick={handleClick}
width={400}
height={200}
margin={{ right: 200 }}
diff --git a/docs/data/charts/tooltip/tooltip.md b/docs/data/charts/tooltip/tooltip.md
index fb73719e2df5..ee43c997e6ce 100644
--- a/docs/data/charts/tooltip/tooltip.md
+++ b/docs/data/charts/tooltip/tooltip.md
@@ -17,6 +17,7 @@ The tooltip can be triggered by two kinds of events:
- `'item'`βwhen the user's mouse hovers over an item on the chart, the tooltip will display data about this specific item.
- `'axis'`βthe user's mouse position is associated with a value of the x-axis. The tooltip will display data about all series at this specific x value.
+- `'none'`βdisable the tooltip.
{{"demo": "Interaction.js"}}
diff --git a/docs/data/charts/zoom-and-pan/ZoomBarChart.js b/docs/data/charts/zoom-and-pan/ZoomBarChart.js
new file mode 100644
index 000000000000..afefa3368145
--- /dev/null
+++ b/docs/data/charts/zoom-and-pan/ZoomBarChart.js
@@ -0,0 +1,117 @@
+import * as React from 'react';
+import { BarChartPro } from '@mui/x-charts-pro/BarChartPro';
+
+export default function ZoomBarChart() {
+ return (
+ v.y1),
+ },
+ {
+ label: 'Series B',
+ data: data.map((v) => v.y2),
+ },
+ ]}
+ />
+ );
+}
+
+const data = [
+ {
+ y1: 443.28,
+ y2: 153.9,
+ },
+ {
+ y1: 110.5,
+ y2: 217.8,
+ },
+ {
+ y1: 175.23,
+ y2: 286.32,
+ },
+ {
+ y1: 195.97,
+ y2: 325.12,
+ },
+ {
+ y1: 351.77,
+ y2: 144.58,
+ },
+ {
+ y1: 43.253,
+ y2: 146.51,
+ },
+ {
+ y1: 376.34,
+ y2: 309.69,
+ },
+ {
+ y1: 31.514,
+ y2: 236.38,
+ },
+ {
+ y1: 231.31,
+ y2: 440.72,
+ },
+ {
+ y1: 108.04,
+ y2: 20.29,
+ },
+ {
+ y1: 321.77,
+ y2: 484.17,
+ },
+ {
+ y1: 120.18,
+ y2: 54.962,
+ },
+ {
+ y1: 366.2,
+ y2: 418.5,
+ },
+ {
+ y1: 451.45,
+ y2: 181.32,
+ },
+ {
+ y1: 294.8,
+ y2: 440.9,
+ },
+ {
+ y1: 121.83,
+ y2: 273.52,
+ },
+ {
+ y1: 287.7,
+ y2: 346.7,
+ },
+ {
+ y1: 134.06,
+ y2: 74.528,
+ },
+ {
+ y1: 104.5,
+ y2: 150.9,
+ },
+ {
+ y1: 413.07,
+ y2: 26.483,
+ },
+ {
+ y1: 74.68,
+ y2: 333.2,
+ },
+ {
+ y1: 360.6,
+ y2: 422.0,
+ },
+ {
+ y1: 330.72,
+ y2: 488.06,
+ },
+];
diff --git a/docs/data/charts/zoom-and-pan/ZoomBarChart.tsx b/docs/data/charts/zoom-and-pan/ZoomBarChart.tsx
new file mode 100644
index 000000000000..afefa3368145
--- /dev/null
+++ b/docs/data/charts/zoom-and-pan/ZoomBarChart.tsx
@@ -0,0 +1,117 @@
+import * as React from 'react';
+import { BarChartPro } from '@mui/x-charts-pro/BarChartPro';
+
+export default function ZoomBarChart() {
+ return (
+ v.y1),
+ },
+ {
+ label: 'Series B',
+ data: data.map((v) => v.y2),
+ },
+ ]}
+ />
+ );
+}
+
+const data = [
+ {
+ y1: 443.28,
+ y2: 153.9,
+ },
+ {
+ y1: 110.5,
+ y2: 217.8,
+ },
+ {
+ y1: 175.23,
+ y2: 286.32,
+ },
+ {
+ y1: 195.97,
+ y2: 325.12,
+ },
+ {
+ y1: 351.77,
+ y2: 144.58,
+ },
+ {
+ y1: 43.253,
+ y2: 146.51,
+ },
+ {
+ y1: 376.34,
+ y2: 309.69,
+ },
+ {
+ y1: 31.514,
+ y2: 236.38,
+ },
+ {
+ y1: 231.31,
+ y2: 440.72,
+ },
+ {
+ y1: 108.04,
+ y2: 20.29,
+ },
+ {
+ y1: 321.77,
+ y2: 484.17,
+ },
+ {
+ y1: 120.18,
+ y2: 54.962,
+ },
+ {
+ y1: 366.2,
+ y2: 418.5,
+ },
+ {
+ y1: 451.45,
+ y2: 181.32,
+ },
+ {
+ y1: 294.8,
+ y2: 440.9,
+ },
+ {
+ y1: 121.83,
+ y2: 273.52,
+ },
+ {
+ y1: 287.7,
+ y2: 346.7,
+ },
+ {
+ y1: 134.06,
+ y2: 74.528,
+ },
+ {
+ y1: 104.5,
+ y2: 150.9,
+ },
+ {
+ y1: 413.07,
+ y2: 26.483,
+ },
+ {
+ y1: 74.68,
+ y2: 333.2,
+ },
+ {
+ y1: 360.6,
+ y2: 422.0,
+ },
+ {
+ y1: 330.72,
+ y2: 488.06,
+ },
+];
diff --git a/docs/data/charts/zoom-and-pan/ZoomBarChart.tsx.preview b/docs/data/charts/zoom-and-pan/ZoomBarChart.tsx.preview
new file mode 100644
index 000000000000..b54614a7af00
--- /dev/null
+++ b/docs/data/charts/zoom-and-pan/ZoomBarChart.tsx.preview
@@ -0,0 +1,15 @@
+ v.y1),
+ },
+ {
+ label: 'Series B',
+ data: data.map((v) => v.y2),
+ },
+ ]}
+/>
\ No newline at end of file
diff --git a/docs/data/charts/zoom-and-pan/ZoomLineChart.js b/docs/data/charts/zoom-and-pan/ZoomLineChart.js
new file mode 100644
index 000000000000..e792dfc45797
--- /dev/null
+++ b/docs/data/charts/zoom-and-pan/ZoomLineChart.js
@@ -0,0 +1,117 @@
+import * as React from 'react';
+import { LineChartPro } from '@mui/x-charts-pro/LineChartPro';
+
+export default function ZoomLineChart() {
+ return (
+ v.y1),
+ },
+ {
+ label: 'Series B',
+ data: data.map((v) => v.y2),
+ },
+ ]}
+ />
+ );
+}
+
+const data = [
+ {
+ y1: 443.28,
+ y2: 153.9,
+ },
+ {
+ y1: 110.5,
+ y2: 217.8,
+ },
+ {
+ y1: 175.23,
+ y2: 286.32,
+ },
+ {
+ y1: 195.97,
+ y2: 325.12,
+ },
+ {
+ y1: 351.77,
+ y2: 144.58,
+ },
+ {
+ y1: 43.253,
+ y2: 146.51,
+ },
+ {
+ y1: 376.34,
+ y2: 309.69,
+ },
+ {
+ y1: 31.514,
+ y2: 236.38,
+ },
+ {
+ y1: 231.31,
+ y2: 440.72,
+ },
+ {
+ y1: 108.04,
+ y2: 20.29,
+ },
+ {
+ y1: 321.77,
+ y2: 484.17,
+ },
+ {
+ y1: 120.18,
+ y2: 54.962,
+ },
+ {
+ y1: 366.2,
+ y2: 418.5,
+ },
+ {
+ y1: 451.45,
+ y2: 181.32,
+ },
+ {
+ y1: 294.8,
+ y2: 440.9,
+ },
+ {
+ y1: 121.83,
+ y2: 273.52,
+ },
+ {
+ y1: 287.7,
+ y2: 346.7,
+ },
+ {
+ y1: 134.06,
+ y2: 74.528,
+ },
+ {
+ y1: 104.5,
+ y2: 150.9,
+ },
+ {
+ y1: 413.07,
+ y2: 26.483,
+ },
+ {
+ y1: 74.68,
+ y2: 333.2,
+ },
+ {
+ y1: 360.6,
+ y2: 422.0,
+ },
+ {
+ y1: 330.72,
+ y2: 488.06,
+ },
+];
diff --git a/docs/data/charts/zoom-and-pan/ZoomLineChart.tsx b/docs/data/charts/zoom-and-pan/ZoomLineChart.tsx
new file mode 100644
index 000000000000..e792dfc45797
--- /dev/null
+++ b/docs/data/charts/zoom-and-pan/ZoomLineChart.tsx
@@ -0,0 +1,117 @@
+import * as React from 'react';
+import { LineChartPro } from '@mui/x-charts-pro/LineChartPro';
+
+export default function ZoomLineChart() {
+ return (
+ v.y1),
+ },
+ {
+ label: 'Series B',
+ data: data.map((v) => v.y2),
+ },
+ ]}
+ />
+ );
+}
+
+const data = [
+ {
+ y1: 443.28,
+ y2: 153.9,
+ },
+ {
+ y1: 110.5,
+ y2: 217.8,
+ },
+ {
+ y1: 175.23,
+ y2: 286.32,
+ },
+ {
+ y1: 195.97,
+ y2: 325.12,
+ },
+ {
+ y1: 351.77,
+ y2: 144.58,
+ },
+ {
+ y1: 43.253,
+ y2: 146.51,
+ },
+ {
+ y1: 376.34,
+ y2: 309.69,
+ },
+ {
+ y1: 31.514,
+ y2: 236.38,
+ },
+ {
+ y1: 231.31,
+ y2: 440.72,
+ },
+ {
+ y1: 108.04,
+ y2: 20.29,
+ },
+ {
+ y1: 321.77,
+ y2: 484.17,
+ },
+ {
+ y1: 120.18,
+ y2: 54.962,
+ },
+ {
+ y1: 366.2,
+ y2: 418.5,
+ },
+ {
+ y1: 451.45,
+ y2: 181.32,
+ },
+ {
+ y1: 294.8,
+ y2: 440.9,
+ },
+ {
+ y1: 121.83,
+ y2: 273.52,
+ },
+ {
+ y1: 287.7,
+ y2: 346.7,
+ },
+ {
+ y1: 134.06,
+ y2: 74.528,
+ },
+ {
+ y1: 104.5,
+ y2: 150.9,
+ },
+ {
+ y1: 413.07,
+ y2: 26.483,
+ },
+ {
+ y1: 74.68,
+ y2: 333.2,
+ },
+ {
+ y1: 360.6,
+ y2: 422.0,
+ },
+ {
+ y1: 330.72,
+ y2: 488.06,
+ },
+];
diff --git a/docs/data/charts/zoom-and-pan/ZoomLineChart.tsx.preview b/docs/data/charts/zoom-and-pan/ZoomLineChart.tsx.preview
new file mode 100644
index 000000000000..d26060a66574
--- /dev/null
+++ b/docs/data/charts/zoom-and-pan/ZoomLineChart.tsx.preview
@@ -0,0 +1,15 @@
+ v.y1),
+ },
+ {
+ label: 'Series B',
+ data: data.map((v) => v.y2),
+ },
+ ]}
+/>
\ No newline at end of file
diff --git a/docs/data/charts/zoom-and-pan/ZoomScatterChart.js b/docs/data/charts/zoom-and-pan/ZoomScatterChart.js
new file mode 100644
index 000000000000..43faf6db2f4b
--- /dev/null
+++ b/docs/data/charts/zoom-and-pan/ZoomScatterChart.js
@@ -0,0 +1,186 @@
+import * as React from 'react';
+import { ScatterChartPro } from '@mui/x-charts-pro/ScatterChartPro';
+
+export default function ZoomScatterChart() {
+ return (
+ ({ x: v.x1, y: v.y1, id: v.id })),
+ },
+ {
+ label: 'Series B',
+ data: data.map((v) => ({ x: v.x1, y: v.y2, id: v.id })),
+ },
+ ]}
+ />
+ );
+}
+
+const data = [
+ {
+ id: 'data-0',
+ x1: 329.39,
+ x2: 391.29,
+ y1: 443.28,
+ y2: 153.9,
+ },
+ {
+ id: 'data-1',
+ x1: 96.94,
+ x2: 139.6,
+ y1: 110.5,
+ y2: 217.8,
+ },
+ {
+ id: 'data-2',
+ x1: 336.35,
+ x2: 282.34,
+ y1: 175.23,
+ y2: 286.32,
+ },
+ {
+ id: 'data-3',
+ x1: 159.44,
+ x2: 384.85,
+ y1: 195.97,
+ y2: 325.12,
+ },
+ {
+ id: 'data-4',
+ x1: 188.86,
+ x2: 182.27,
+ y1: 351.77,
+ y2: 144.58,
+ },
+ {
+ id: 'data-5',
+ x1: 143.86,
+ x2: 360.22,
+ y1: 43.253,
+ y2: 146.51,
+ },
+ {
+ id: 'data-6',
+ x1: 202.02,
+ x2: 209.5,
+ y1: 376.34,
+ y2: 309.69,
+ },
+ {
+ id: 'data-7',
+ x1: 384.41,
+ x2: 258.93,
+ y1: 31.514,
+ y2: 236.38,
+ },
+ {
+ id: 'data-8',
+ x1: 256.76,
+ x2: 70.571,
+ y1: 231.31,
+ y2: 440.72,
+ },
+ {
+ id: 'data-9',
+ x1: 143.79,
+ x2: 419.02,
+ y1: 108.04,
+ y2: 20.29,
+ },
+ {
+ id: 'data-10',
+ x1: 103.48,
+ x2: 15.886,
+ y1: 321.77,
+ y2: 484.17,
+ },
+ {
+ id: 'data-11',
+ x1: 272.39,
+ x2: 189.03,
+ y1: 120.18,
+ y2: 54.962,
+ },
+ {
+ id: 'data-12',
+ x1: 23.57,
+ x2: 456.4,
+ y1: 366.2,
+ y2: 418.5,
+ },
+ {
+ id: 'data-13',
+ x1: 219.73,
+ x2: 235.96,
+ y1: 451.45,
+ y2: 181.32,
+ },
+ {
+ id: 'data-14',
+ x1: 54.99,
+ x2: 434.5,
+ y1: 294.8,
+ y2: 440.9,
+ },
+ {
+ id: 'data-15',
+ x1: 134.13,
+ x2: 383.8,
+ y1: 121.83,
+ y2: 273.52,
+ },
+ {
+ id: 'data-16',
+ x1: 12.7,
+ x2: 270.8,
+ y1: 287.7,
+ y2: 346.7,
+ },
+ {
+ id: 'data-17',
+ x1: 176.51,
+ x2: 119.17,
+ y1: 134.06,
+ y2: 74.528,
+ },
+ {
+ id: 'data-18',
+ x1: 65.05,
+ x2: 78.93,
+ y1: 104.5,
+ y2: 150.9,
+ },
+ {
+ id: 'data-19',
+ x1: 162.25,
+ x2: 63.707,
+ y1: 413.07,
+ y2: 26.483,
+ },
+ {
+ id: 'data-20',
+ x1: 68.88,
+ x2: 150.8,
+ y1: 74.68,
+ y2: 333.2,
+ },
+ {
+ id: 'data-21',
+ x1: 95.29,
+ x2: 329.1,
+ y1: 360.6,
+ y2: 422.0,
+ },
+ {
+ id: 'data-22',
+ x1: 390.62,
+ x2: 10.01,
+ y1: 330.72,
+ y2: 488.06,
+ },
+];
diff --git a/docs/data/charts/zoom-and-pan/ZoomScatterChart.tsx b/docs/data/charts/zoom-and-pan/ZoomScatterChart.tsx
new file mode 100644
index 000000000000..43faf6db2f4b
--- /dev/null
+++ b/docs/data/charts/zoom-and-pan/ZoomScatterChart.tsx
@@ -0,0 +1,186 @@
+import * as React from 'react';
+import { ScatterChartPro } from '@mui/x-charts-pro/ScatterChartPro';
+
+export default function ZoomScatterChart() {
+ return (
+ ({ x: v.x1, y: v.y1, id: v.id })),
+ },
+ {
+ label: 'Series B',
+ data: data.map((v) => ({ x: v.x1, y: v.y2, id: v.id })),
+ },
+ ]}
+ />
+ );
+}
+
+const data = [
+ {
+ id: 'data-0',
+ x1: 329.39,
+ x2: 391.29,
+ y1: 443.28,
+ y2: 153.9,
+ },
+ {
+ id: 'data-1',
+ x1: 96.94,
+ x2: 139.6,
+ y1: 110.5,
+ y2: 217.8,
+ },
+ {
+ id: 'data-2',
+ x1: 336.35,
+ x2: 282.34,
+ y1: 175.23,
+ y2: 286.32,
+ },
+ {
+ id: 'data-3',
+ x1: 159.44,
+ x2: 384.85,
+ y1: 195.97,
+ y2: 325.12,
+ },
+ {
+ id: 'data-4',
+ x1: 188.86,
+ x2: 182.27,
+ y1: 351.77,
+ y2: 144.58,
+ },
+ {
+ id: 'data-5',
+ x1: 143.86,
+ x2: 360.22,
+ y1: 43.253,
+ y2: 146.51,
+ },
+ {
+ id: 'data-6',
+ x1: 202.02,
+ x2: 209.5,
+ y1: 376.34,
+ y2: 309.69,
+ },
+ {
+ id: 'data-7',
+ x1: 384.41,
+ x2: 258.93,
+ y1: 31.514,
+ y2: 236.38,
+ },
+ {
+ id: 'data-8',
+ x1: 256.76,
+ x2: 70.571,
+ y1: 231.31,
+ y2: 440.72,
+ },
+ {
+ id: 'data-9',
+ x1: 143.79,
+ x2: 419.02,
+ y1: 108.04,
+ y2: 20.29,
+ },
+ {
+ id: 'data-10',
+ x1: 103.48,
+ x2: 15.886,
+ y1: 321.77,
+ y2: 484.17,
+ },
+ {
+ id: 'data-11',
+ x1: 272.39,
+ x2: 189.03,
+ y1: 120.18,
+ y2: 54.962,
+ },
+ {
+ id: 'data-12',
+ x1: 23.57,
+ x2: 456.4,
+ y1: 366.2,
+ y2: 418.5,
+ },
+ {
+ id: 'data-13',
+ x1: 219.73,
+ x2: 235.96,
+ y1: 451.45,
+ y2: 181.32,
+ },
+ {
+ id: 'data-14',
+ x1: 54.99,
+ x2: 434.5,
+ y1: 294.8,
+ y2: 440.9,
+ },
+ {
+ id: 'data-15',
+ x1: 134.13,
+ x2: 383.8,
+ y1: 121.83,
+ y2: 273.52,
+ },
+ {
+ id: 'data-16',
+ x1: 12.7,
+ x2: 270.8,
+ y1: 287.7,
+ y2: 346.7,
+ },
+ {
+ id: 'data-17',
+ x1: 176.51,
+ x2: 119.17,
+ y1: 134.06,
+ y2: 74.528,
+ },
+ {
+ id: 'data-18',
+ x1: 65.05,
+ x2: 78.93,
+ y1: 104.5,
+ y2: 150.9,
+ },
+ {
+ id: 'data-19',
+ x1: 162.25,
+ x2: 63.707,
+ y1: 413.07,
+ y2: 26.483,
+ },
+ {
+ id: 'data-20',
+ x1: 68.88,
+ x2: 150.8,
+ y1: 74.68,
+ y2: 333.2,
+ },
+ {
+ id: 'data-21',
+ x1: 95.29,
+ x2: 329.1,
+ y1: 360.6,
+ y2: 422.0,
+ },
+ {
+ id: 'data-22',
+ x1: 390.62,
+ x2: 10.01,
+ y1: 330.72,
+ y2: 488.06,
+ },
+];
diff --git a/docs/data/charts/zoom-and-pan/ZoomScatterChart.tsx.preview b/docs/data/charts/zoom-and-pan/ZoomScatterChart.tsx.preview
new file mode 100644
index 000000000000..9f4bf4a61add
--- /dev/null
+++ b/docs/data/charts/zoom-and-pan/ZoomScatterChart.tsx.preview
@@ -0,0 +1,15 @@
+ ({ x: v.x1, y: v.y1, id: v.id })),
+ },
+ {
+ label: 'Series B',
+ data: data.map((v) => ({ x: v.x1, y: v.y2, id: v.id })),
+ },
+ ]}
+/>
\ No newline at end of file
diff --git a/docs/data/charts/zoom-and-pan/zoom-and-pan.md b/docs/data/charts/zoom-and-pan/zoom-and-pan.md
new file mode 100644
index 000000000000..f2b80cd8eab0
--- /dev/null
+++ b/docs/data/charts/zoom-and-pan/zoom-and-pan.md
@@ -0,0 +1,30 @@
+---
+title: Zoom & Pan
+productId: x-charts
+---
+
+# Zoom & Pan [ ](/x/introduction/licensing/#pro-plan 'Pro plan') π§
+
+Enables zooming and panning on specific charts or axis.
+
+Zooming is possible on the **Pro**[ ](/x/introduction/licensing/#pro-plan 'Pro plan') versions of the charts: ` `, ` `, ` `.
+
+:::warning
+Zooming is currently only possible on the `X axis`.
+:::
+
+## Basic usage
+
+To enable zooming and panning, set the `zoom` prop to `true` on the chart component.
+
+Enabling zoom will enable all the interactions, which are made to be as intuitive as possible.
+
+The following actions are supported:
+
+- **Scroll**: Zoom in/out by scrolling the mouse wheel.
+- **Drag**: Pan the chart by dragging the mouse.
+- **Pinch**: Zoom in/out by pinching the chart.
+
+{{"demo": "ZoomScatterChart.js"}}
+{{"demo": "ZoomBarChart.js"}}
+{{"demo": "ZoomLineChart.js"}}
diff --git a/docs/data/data-grid/components/CustomEmptyOverlayGrid.js b/docs/data/data-grid/components/CustomEmptyOverlayGrid.js
deleted file mode 100644
index 7bff6c5033d8..000000000000
--- a/docs/data/data-grid/components/CustomEmptyOverlayGrid.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import * as React from 'react';
-import Box from '@mui/material/Box';
-import { DataGrid } from '@mui/x-data-grid';
-import { useDemoData } from '@mui/x-data-grid-generator';
-import { styled } from '@mui/material/styles';
-
-const StyledGridOverlay = styled('div')(({ theme }) => ({
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'center',
- height: '100%',
- '& .ant-empty-img-1': {
- fill: theme.palette.mode === 'light' ? '#aeb8c2' : '#262626',
- },
- '& .ant-empty-img-2': {
- fill: theme.palette.mode === 'light' ? '#f5f5f7' : '#595959',
- },
- '& .ant-empty-img-3': {
- fill: theme.palette.mode === 'light' ? '#dce0e6' : '#434343',
- },
- '& .ant-empty-img-4': {
- fill: theme.palette.mode === 'light' ? '#fff' : '#1c1c1c',
- },
- '& .ant-empty-img-5': {
- fillOpacity: theme.palette.mode === 'light' ? '0.8' : '0.08',
- fill: theme.palette.mode === 'light' ? '#f5f5f5' : '#fff',
- },
-}));
-
-function CustomNoRowsOverlay() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- No Rows
-
- );
-}
-
-export default function CustomEmptyOverlayGrid() {
- const { data } = useDemoData({
- dataSet: 'Commodity',
- rowLength: 100,
- maxColumns: 6,
- });
-
- return (
-
-
-
- );
-}
diff --git a/docs/data/data-grid/components/CustomEmptyOverlayGrid.tsx b/docs/data/data-grid/components/CustomEmptyOverlayGrid.tsx
deleted file mode 100644
index 7bff6c5033d8..000000000000
--- a/docs/data/data-grid/components/CustomEmptyOverlayGrid.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import * as React from 'react';
-import Box from '@mui/material/Box';
-import { DataGrid } from '@mui/x-data-grid';
-import { useDemoData } from '@mui/x-data-grid-generator';
-import { styled } from '@mui/material/styles';
-
-const StyledGridOverlay = styled('div')(({ theme }) => ({
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'center',
- height: '100%',
- '& .ant-empty-img-1': {
- fill: theme.palette.mode === 'light' ? '#aeb8c2' : '#262626',
- },
- '& .ant-empty-img-2': {
- fill: theme.palette.mode === 'light' ? '#f5f5f7' : '#595959',
- },
- '& .ant-empty-img-3': {
- fill: theme.palette.mode === 'light' ? '#dce0e6' : '#434343',
- },
- '& .ant-empty-img-4': {
- fill: theme.palette.mode === 'light' ? '#fff' : '#1c1c1c',
- },
- '& .ant-empty-img-5': {
- fillOpacity: theme.palette.mode === 'light' ? '0.8' : '0.08',
- fill: theme.palette.mode === 'light' ? '#f5f5f5' : '#fff',
- },
-}));
-
-function CustomNoRowsOverlay() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- No Rows
-
- );
-}
-
-export default function CustomEmptyOverlayGrid() {
- const { data } = useDemoData({
- dataSet: 'Commodity',
- rowLength: 100,
- maxColumns: 6,
- });
-
- return (
-
-
-
- );
-}
diff --git a/docs/data/data-grid/components/CustomLoadingOverlayGrid.tsx b/docs/data/data-grid/components/CustomLoadingOverlayGrid.tsx
deleted file mode 100644
index c278f02c68a4..000000000000
--- a/docs/data/data-grid/components/CustomLoadingOverlayGrid.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import * as React from 'react';
-import { DataGrid, GridSlots } from '@mui/x-data-grid';
-import LinearProgress from '@mui/material/LinearProgress';
-import { useDemoData } from '@mui/x-data-grid-generator';
-
-export default function CustomLoadingOverlayGrid() {
- const { data } = useDemoData({
- dataSet: 'Commodity',
- rowLength: 100,
- maxColumns: 6,
- });
-
- return (
-
-
-
- );
-}
diff --git a/docs/data/data-grid/components/CustomLoadingOverlayGrid.tsx.preview b/docs/data/data-grid/components/CustomLoadingOverlayGrid.tsx.preview
deleted file mode 100644
index 84c2df95e838..000000000000
--- a/docs/data/data-grid/components/CustomLoadingOverlayGrid.tsx.preview
+++ /dev/null
@@ -1,7 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/data-grid/components/components.md b/docs/data/data-grid/components/components.md
index b94195c7f74a..6d78d7487beb 100644
--- a/docs/data/data-grid/components/components.md
+++ b/docs/data/data-grid/components/components.md
@@ -128,23 +128,6 @@ The next demo reuses `GridPagination` but replaces the previous and next page bu
{{"demo": "CustomPaginationGrid.js", "bg": "inline"}}
-### Loading overlay
-
-By default, the loading overlay displays a circular progress.
-This demo replaces it with a linear progress.
-
-{{"demo": "CustomLoadingOverlayGrid.js", "bg": "inline"}}
-
-### No rows overlay
-
-In the following demo, an illustration is added on top of the default "No Rows" message.
-
-{{"demo": "CustomEmptyOverlayGrid.js", "bg": "inline"}}
-
-:::info
-As with the no-rows overlay, the Data Grid also lets you override the no-results overlay using the `NoResultsOverlay` slot.
-:::
-
### Row
The `slotProps.row` prop can be used to pass additional props to the row component.
@@ -166,6 +149,10 @@ As any component slot, every icon can be customized. However, it is not yet poss
{{"demo": "CustomSortIcons.js", "bg": "inline"}}
+### Overlays
+
+See the [Overlays](/x/react-data-grid/overlays/) documentation on how to customize the `loadingOverlay`, `noRowsOverlay`, and `noResultsOverlay`.
+
## Slot props
To override default props or pass custom props to slot components, use the `slotProps` prop.
diff --git a/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.js b/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.js
index 0f9b9382f16e..b52f36d8eb20 100644
--- a/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.js
+++ b/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.js
@@ -9,21 +9,11 @@ const StyledGridOverlay = styled('div')(({ theme }) => ({
alignItems: 'center',
justifyContent: 'center',
height: '100%',
- '& .ant-empty-img-1': {
- fill: theme.palette.mode === 'light' ? '#aeb8c2' : '#262626',
+ '& .no-rows-primary': {
+ fill: theme.palette.mode === 'light' ? '#AEB8C2' : '#3D4751',
},
- '& .ant-empty-img-2': {
- fill: theme.palette.mode === 'light' ? '#f5f5f7' : '#595959',
- },
- '& .ant-empty-img-3': {
- fill: theme.palette.mode === 'light' ? '#dce0e6' : '#434343',
- },
- '& .ant-empty-img-4': {
- fill: theme.palette.mode === 'light' ? '#fff' : '#1c1c1c',
- },
- '& .ant-empty-img-5': {
- fillOpacity: theme.palette.mode === 'light' ? '0.8' : '0.08',
- fill: theme.palette.mode === 'light' ? '#f5f5f5' : '#fff',
+ '& .no-rows-secondary': {
+ fill: theme.palette.mode === 'light' ? '#E8EAED' : '#1D2126',
},
}));
@@ -31,46 +21,31 @@ function CustomNoRowsOverlay() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
- No Rows
+ No rows
);
}
diff --git a/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.tsx b/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.tsx
index 0f9b9382f16e..b52f36d8eb20 100644
--- a/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.tsx
+++ b/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.tsx
@@ -9,21 +9,11 @@ const StyledGridOverlay = styled('div')(({ theme }) => ({
alignItems: 'center',
justifyContent: 'center',
height: '100%',
- '& .ant-empty-img-1': {
- fill: theme.palette.mode === 'light' ? '#aeb8c2' : '#262626',
+ '& .no-rows-primary': {
+ fill: theme.palette.mode === 'light' ? '#AEB8C2' : '#3D4751',
},
- '& .ant-empty-img-2': {
- fill: theme.palette.mode === 'light' ? '#f5f5f7' : '#595959',
- },
- '& .ant-empty-img-3': {
- fill: theme.palette.mode === 'light' ? '#dce0e6' : '#434343',
- },
- '& .ant-empty-img-4': {
- fill: theme.palette.mode === 'light' ? '#fff' : '#1c1c1c',
- },
- '& .ant-empty-img-5': {
- fillOpacity: theme.palette.mode === 'light' ? '0.8' : '0.08',
- fill: theme.palette.mode === 'light' ? '#f5f5f5' : '#fff',
+ '& .no-rows-secondary': {
+ fill: theme.palette.mode === 'light' ? '#E8EAED' : '#1D2126',
},
}));
@@ -31,46 +21,31 @@ function CustomNoRowsOverlay() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
- No Rows
+ No rows
);
}
diff --git a/docs/data/data-grid/layout/layout.md b/docs/data/data-grid/layout/layout.md
index 67ff5ce52e17..1c960eb659dd 100644
--- a/docs/data/data-grid/layout/layout.md
+++ b/docs/data/data-grid/layout/layout.md
@@ -33,9 +33,9 @@ This is not recommended for large datasets as row virtualization will not be abl
### Overlay height
-When `autoHeight` is enabled but there are no rows, grid overlays (such as
-["Loading"](/x/react-data-grid/components/#loading-overlay) or
-["No rows"](/x/react-data-grid/components/#no-rows-overlay))
+When `autoHeight` is enabled, grid overlays (such as
+["Loading"](/x/react-data-grid/overlays/#loading-overlay) or
+["No rows"](/x/react-data-grid/overlays/#no-rows-overlay))
take the height of two rows by default.
To customize the overlay height, use the `--DataGrid-overlayHeight` CSS variable.
diff --git a/docs/data/data-grid/overlays/LoadingOverlay.js b/docs/data/data-grid/overlays/LoadingOverlay.js
new file mode 100644
index 000000000000..333a904448f6
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlay.js
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { DataGrid } from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+
+export default function LoadingOverlay() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 6,
+ maxColumns: 6,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/LoadingOverlay.tsx b/docs/data/data-grid/overlays/LoadingOverlay.tsx
new file mode 100644
index 000000000000..333a904448f6
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlay.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { DataGrid } from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+
+export default function LoadingOverlay() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 6,
+ maxColumns: 6,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/LoadingOverlay.tsx.preview b/docs/data/data-grid/overlays/LoadingOverlay.tsx.preview
new file mode 100644
index 000000000000..0948ca9faeec
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlay.tsx.preview
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/data/data-grid/overlays/LoadingOverlayCustom.js b/docs/data/data-grid/overlays/LoadingOverlayCustom.js
new file mode 100644
index 000000000000..a936b4296f19
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlayCustom.js
@@ -0,0 +1,80 @@
+import * as React from 'react';
+import { DataGrid } from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+import { styled } from '@mui/material/styles';
+import Box from '@mui/material/Box';
+import Typography from '@mui/material/Typography';
+import CircularProgress from '@mui/material/CircularProgress';
+
+const StyledGridOverlay = styled('div')(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '100%',
+ backgroundColor:
+ theme.palette.mode === 'light'
+ ? 'rgba(255, 255, 255, 0.9)'
+ : 'rgba(18, 18, 18, 0.9)',
+}));
+
+function CircularProgressWithLabel(props) {
+ return (
+
+
+
+
+ {`${Math.round(props.value)}%`}
+
+
+
+ );
+}
+
+function CustomLoadingOverlay() {
+ const [progress, setProgress] = React.useState(10);
+
+ React.useEffect(() => {
+ const timer = setInterval(() => {
+ setProgress((prevProgress) => (prevProgress >= 100 ? 0 : prevProgress + 10));
+ }, 800);
+ return () => {
+ clearInterval(timer);
+ };
+ }, []);
+
+ return (
+
+
+ Loading rowsβ¦
+
+ );
+}
+
+export default function LoadingOverlayCustom() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 100,
+ maxColumns: 6,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx b/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx
new file mode 100644
index 000000000000..59209a50e686
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx
@@ -0,0 +1,86 @@
+import * as React from 'react';
+import { DataGrid } from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+import { styled } from '@mui/material/styles';
+import Box from '@mui/material/Box';
+import Typography from '@mui/material/Typography';
+import CircularProgress, {
+ CircularProgressProps,
+} from '@mui/material/CircularProgress';
+
+const StyledGridOverlay = styled('div')(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '100%',
+ backgroundColor:
+ theme.palette.mode === 'light'
+ ? 'rgba(255, 255, 255, 0.9)'
+ : 'rgba(18, 18, 18, 0.9)',
+}));
+
+function CircularProgressWithLabel(
+ props: CircularProgressProps & { value: number },
+) {
+ return (
+
+
+
+ {`${Math.round(props.value)}%`}
+
+
+ );
+}
+
+function CustomLoadingOverlay() {
+ const [progress, setProgress] = React.useState(10);
+
+ React.useEffect(() => {
+ const timer = setInterval(() => {
+ setProgress((prevProgress) => (prevProgress >= 100 ? 0 : prevProgress + 10));
+ }, 800);
+ return () => {
+ clearInterval(timer);
+ };
+ }, []);
+
+ return (
+
+
+ Loading rowsβ¦
+
+ );
+}
+
+export default function LoadingOverlayCustom() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 100,
+ maxColumns: 6,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx.preview b/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx.preview
new file mode 100644
index 000000000000..55daad1518bb
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx.preview
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/docs/data/data-grid/components/CustomLoadingOverlayGrid.js b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.js
similarity index 52%
rename from docs/data/data-grid/components/CustomLoadingOverlayGrid.js
rename to docs/data/data-grid/overlays/LoadingOverlayLinearProgress.js
index fef9a8145a76..5ad0b8a4b3e5 100644
--- a/docs/data/data-grid/components/CustomLoadingOverlayGrid.js
+++ b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.js
@@ -1,9 +1,9 @@
import * as React from 'react';
-import { DataGrid } from '@mui/x-data-grid';
-import LinearProgress from '@mui/material/LinearProgress';
+import Box from '@mui/material/Box';
import { useDemoData } from '@mui/x-data-grid-generator';
+import { DataGrid } from '@mui/x-data-grid';
-export default function CustomLoadingOverlayGrid() {
+export default function LoadingOverlayLinearProgress() {
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 100,
@@ -11,14 +11,17 @@ export default function CustomLoadingOverlayGrid() {
});
return (
-
+
-
+
);
}
diff --git a/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx
new file mode 100644
index 000000000000..5ad0b8a4b3e5
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx
@@ -0,0 +1,27 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { useDemoData } from '@mui/x-data-grid-generator';
+import { DataGrid } from '@mui/x-data-grid';
+
+export default function LoadingOverlayLinearProgress() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 100,
+ maxColumns: 6,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx.preview b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx.preview
new file mode 100644
index 000000000000..f4e0a9ef7417
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlayLinearProgress.tsx.preview
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.js b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.js
new file mode 100644
index 000000000000..0e86a253dfcd
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.js
@@ -0,0 +1,32 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { useDemoData } from '@mui/x-data-grid-generator';
+import { DataGridPro } from '@mui/x-data-grid-pro';
+
+export default function LoadingOverlaySkeleton() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 100,
+ maxColumns: 9,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx
new file mode 100644
index 000000000000..0e86a253dfcd
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx
@@ -0,0 +1,32 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { useDemoData } from '@mui/x-data-grid-generator';
+import { DataGridPro } from '@mui/x-data-grid-pro';
+
+export default function LoadingOverlaySkeleton() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 100,
+ maxColumns: 9,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview
new file mode 100644
index 000000000000..9173142c9f99
--- /dev/null
+++ b/docs/data/data-grid/overlays/LoadingOverlaySkeleton.tsx.preview
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/docs/data/data-grid/overlays/NoResultsOverlay.js b/docs/data/data-grid/overlays/NoResultsOverlay.js
new file mode 100644
index 000000000000..e112ec11cd38
--- /dev/null
+++ b/docs/data/data-grid/overlays/NoResultsOverlay.js
@@ -0,0 +1,35 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { DataGrid, GridToolbar } from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+
+export default function NoResultsOverlay() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 6,
+ maxColumns: 6,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/NoResultsOverlay.tsx b/docs/data/data-grid/overlays/NoResultsOverlay.tsx
new file mode 100644
index 000000000000..e112ec11cd38
--- /dev/null
+++ b/docs/data/data-grid/overlays/NoResultsOverlay.tsx
@@ -0,0 +1,35 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { DataGrid, GridToolbar } from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+
+export default function NoResultsOverlay() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 6,
+ maxColumns: 6,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/NoResultsOverlayCustom.js b/docs/data/data-grid/overlays/NoResultsOverlayCustom.js
new file mode 100644
index 000000000000..15320b97081c
--- /dev/null
+++ b/docs/data/data-grid/overlays/NoResultsOverlayCustom.js
@@ -0,0 +1,85 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { DataGrid, GridToolbar } from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+import { styled } from '@mui/material/styles';
+
+const StyledGridOverlay = styled('div')(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '100%',
+ '& .no-results-primary': {
+ fill: theme.palette.mode === 'light' ? '#AEB8C2' : '#3D4751',
+ },
+ '& .no-results-secondary': {
+ fill: theme.palette.mode === 'light' ? '#E8EAED' : '#1D2126',
+ },
+}));
+
+function CustomNoResultsOverlay() {
+ return (
+
+
+
+
+
+
+
+ No results found.
+
+ );
+}
+
+export default function NoResultsOverlayCustom() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 6,
+ maxColumns: 6,
+ });
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/NoResultsOverlayCustom.tsx b/docs/data/data-grid/overlays/NoResultsOverlayCustom.tsx
new file mode 100644
index 000000000000..15320b97081c
--- /dev/null
+++ b/docs/data/data-grid/overlays/NoResultsOverlayCustom.tsx
@@ -0,0 +1,85 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { DataGrid, GridToolbar } from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+import { styled } from '@mui/material/styles';
+
+const StyledGridOverlay = styled('div')(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '100%',
+ '& .no-results-primary': {
+ fill: theme.palette.mode === 'light' ? '#AEB8C2' : '#3D4751',
+ },
+ '& .no-results-secondary': {
+ fill: theme.palette.mode === 'light' ? '#E8EAED' : '#1D2126',
+ },
+}));
+
+function CustomNoResultsOverlay() {
+ return (
+
+
+
+
+
+
+
+ No results found.
+
+ );
+}
+
+export default function NoResultsOverlayCustom() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 6,
+ maxColumns: 6,
+ });
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/NoRowsOverlay.js b/docs/data/data-grid/overlays/NoRowsOverlay.js
new file mode 100644
index 000000000000..d748985be91a
--- /dev/null
+++ b/docs/data/data-grid/overlays/NoRowsOverlay.js
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { DataGrid } from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+
+export default function NoRowsOverlay() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 0,
+ maxColumns: 6,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/NoRowsOverlay.tsx b/docs/data/data-grid/overlays/NoRowsOverlay.tsx
new file mode 100644
index 000000000000..d748985be91a
--- /dev/null
+++ b/docs/data/data-grid/overlays/NoRowsOverlay.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { DataGrid } from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+
+export default function NoRowsOverlay() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 0,
+ maxColumns: 6,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/NoRowsOverlay.tsx.preview b/docs/data/data-grid/overlays/NoRowsOverlay.tsx.preview
new file mode 100644
index 000000000000..364fac54f80c
--- /dev/null
+++ b/docs/data/data-grid/overlays/NoRowsOverlay.tsx.preview
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/data/data-grid/overlays/NoRowsOverlayCustom.js b/docs/data/data-grid/overlays/NoRowsOverlayCustom.js
new file mode 100644
index 000000000000..bfe5090c0466
--- /dev/null
+++ b/docs/data/data-grid/overlays/NoRowsOverlayCustom.js
@@ -0,0 +1,72 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { DataGrid } from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+import { styled } from '@mui/material/styles';
+
+const StyledGridOverlay = styled('div')(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '100%',
+ '& .no-rows-primary': {
+ fill: theme.palette.mode === 'light' ? '#AEB8C2' : '#3D4751',
+ },
+ '& .no-rows-secondary': {
+ fill: theme.palette.mode === 'light' ? '#E8EAED' : '#1D2126',
+ },
+}));
+
+function CustomNoRowsOverlay() {
+ return (
+
+
+
+
+
+
+
+ No rows
+
+ );
+}
+
+export default function NoRowsOverlayCustom() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 100,
+ maxColumns: 6,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/overlays/NoRowsOverlayCustom.tsx b/docs/data/data-grid/overlays/NoRowsOverlayCustom.tsx
new file mode 100644
index 000000000000..bfe5090c0466
--- /dev/null
+++ b/docs/data/data-grid/overlays/NoRowsOverlayCustom.tsx
@@ -0,0 +1,72 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { DataGrid } from '@mui/x-data-grid';
+import { useDemoData } from '@mui/x-data-grid-generator';
+import { styled } from '@mui/material/styles';
+
+const StyledGridOverlay = styled('div')(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '100%',
+ '& .no-rows-primary': {
+ fill: theme.palette.mode === 'light' ? '#AEB8C2' : '#3D4751',
+ },
+ '& .no-rows-secondary': {
+ fill: theme.palette.mode === 'light' ? '#E8EAED' : '#1D2126',
+ },
+}));
+
+function CustomNoRowsOverlay() {
+ return (
+
+
+
+
+
+
+
+ No rows
+
+ );
+}
+
+export default function NoRowsOverlayCustom() {
+ const { data } = useDemoData({
+ dataSet: 'Commodity',
+ rowLength: 100,
+ maxColumns: 6,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/components/CustomEmptyOverlayGrid.tsx.preview b/docs/data/data-grid/overlays/NoRowsOverlayCustom.tsx.preview
similarity index 100%
rename from docs/data/data-grid/components/CustomEmptyOverlayGrid.tsx.preview
rename to docs/data/data-grid/overlays/NoRowsOverlayCustom.tsx.preview
diff --git a/docs/data/data-grid/overlays/overlays.md b/docs/data/data-grid/overlays/overlays.md
new file mode 100644
index 000000000000..426e8b615b3e
--- /dev/null
+++ b/docs/data/data-grid/overlays/overlays.md
@@ -0,0 +1,91 @@
+# Data Grid - Overlays
+
+The various data grid overlays.
+
+## Loading overlay
+
+To display a loading overlay and signify that the data grid is in a loading state, set the `loading` prop to `true`.
+
+The data grid supports 3 loading overlay variants out of the box:
+
+- `circular-progress` (default): a circular loading spinner.
+- `linear-progress`: an indeterminate linear progress bar.
+- `skeleton`: an animated placeholder of the data grid.
+
+The type of loading overlay to display can be set via `slotProps.loadingOverlay` for the following two props:
+
+- `variant`: when `loading` and there are rows in the table.
+- `noRowsVariant`: when `loading` and there are not any rows in the table.
+
+```tsx
+
+```
+
+### Circular progress
+
+A circular loading spinner, the default loading overlay.
+
+{{"demo": "LoadingOverlay.js", "bg": "inline"}}
+
+### Linear progress
+
+An indeterminate linear progress bar.
+
+{{"demo": "LoadingOverlayLinearProgress.js", "bg": "inline"}}
+
+### Skeleton
+
+An animated placeholder of the data grid.
+
+{{"demo": "LoadingOverlaySkeleton.js", "bg": "inline"}}
+
+### Custom component
+
+If you want to customize the no rows overlay, a component can be passed to the `loadingOverlay` slot.
+
+In the following demo, a labelled determinate [CircularProgress](/material-ui/react-progress/#circular-determinate) component is rendered in place of the default loading overlay, with some additional _Loading rowsβ¦_ text.
+
+{{"demo": "LoadingOverlayCustom.js", "bg": "inline"}}
+
+## No rows overlay
+
+The no rows overlay is displayed when the data grid has no rows.
+
+{{"demo": "NoRowsOverlay.js", "bg": "inline"}}
+
+### Custom component
+
+If you want to customize the no rows overlay, a component can be passed to the `noRowsOverlay` slot and rendered in place.
+
+In the following demo, an illustration is added on top of the default "No rows" message.
+
+{{"demo": "NoRowsOverlayCustom.js", "bg": "inline"}}
+
+## No results overlay
+
+The no results overlay is displayed when the data grid has no results after filtering.
+
+{{"demo": "NoResultsOverlay.js", "bg": "inline"}}
+
+### Custom component
+
+If you want to customize the no results overlay, a component can be passed to the `noResults` slot and rendered in place.
+
+In the following demo, an illustration is added on top of the default "No results found" message.
+
+{{"demo": "NoResultsOverlayCustom.js", "bg": "inline"}}
+
+## API
+
+- [DataGrid](/x/api/data-grid/data-grid/)
+- [DataGridPro](/x/api/data-grid/data-grid-pro/)
+- [DataGridPremium](/x/api/data-grid/data-grid-premium/)
diff --git a/docs/data/data-grid/overview/overview.md b/docs/data/data-grid/overview/overview.md
index d39a88beb8eb..cb859d0d35c9 100644
--- a/docs/data/data-grid/overview/overview.md
+++ b/docs/data/data-grid/overview/overview.md
@@ -12,7 +12,7 @@ waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/grid/
The Data Grid component is built with React and TypeScript to provide a smooth UX for manipulating an unlimited set of data.
It features an intuitive API for real-time updates as well as theming and custom templatesβall with blazing-fast performance.
-{{"component": "modules/components/ComponentLinkHeader.js"}}
+{{"component": "@mui/docs/ComponentLinkHeader"}}
## Overview
diff --git a/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.js b/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.js
new file mode 100644
index 000000000000..837ea2546b6b
--- /dev/null
+++ b/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.js
@@ -0,0 +1,55 @@
+import * as React from 'react';
+import {
+ DataGridPremium,
+ useGridApiRef,
+ useGridSelector,
+ useKeepGroupedColumnsHidden,
+ useGridApiContext,
+ gridFilteredDescendantRowCountSelector,
+} from '@mui/x-data-grid-premium';
+import { useMovieData } from '@mui/x-data-grid-generator';
+import { Box } from '@mui/material';
+
+function CustomFooterRowCount(props) {
+ const { visibleRowCount: topLevelRowCount } = props;
+ const apiRef = useGridApiContext();
+ const descendantRowCount = useGridSelector(
+ apiRef,
+ gridFilteredDescendantRowCountSelector,
+ );
+
+ return (
+
+ {descendantRowCount} row{descendantRowCount > 1 ? 's' : ''} in{' '}
+ {topLevelRowCount} group
+ {topLevelRowCount > 1 ? 's' : ''}
+
+ );
+}
+
+export default function RowGroupingChildRowCount() {
+ const data = useMovieData();
+ const apiRef = useGridApiRef();
+
+ const initialState = useKeepGroupedColumnsHidden({
+ apiRef,
+ initialState: {
+ rowGrouping: {
+ model: ['company'],
+ },
+ },
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.tsx b/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.tsx
new file mode 100644
index 000000000000..514aa8358b3c
--- /dev/null
+++ b/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.tsx
@@ -0,0 +1,56 @@
+import * as React from 'react';
+import {
+ DataGridPremium,
+ GridRowCountProps,
+ useGridApiRef,
+ useGridSelector,
+ useKeepGroupedColumnsHidden,
+ useGridApiContext,
+ gridFilteredDescendantRowCountSelector,
+} from '@mui/x-data-grid-premium';
+import { useMovieData } from '@mui/x-data-grid-generator';
+import { Box } from '@mui/material';
+
+function CustomFooterRowCount(props: GridRowCountProps) {
+ const { visibleRowCount: topLevelRowCount } = props;
+ const apiRef = useGridApiContext();
+ const descendantRowCount = useGridSelector(
+ apiRef,
+ gridFilteredDescendantRowCountSelector,
+ );
+
+ return (
+
+ {descendantRowCount} row{descendantRowCount > 1 ? 's' : ''} in{' '}
+ {topLevelRowCount} group
+ {topLevelRowCount > 1 ? 's' : ''}
+
+ );
+}
+
+export default function RowGroupingChildRowCount() {
+ const data = useMovieData();
+ const apiRef = useGridApiRef();
+
+ const initialState = useKeepGroupedColumnsHidden({
+ apiRef,
+ initialState: {
+ rowGrouping: {
+ model: ['company'],
+ },
+ },
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.tsx.preview b/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.tsx.preview
new file mode 100644
index 000000000000..f9d69b63cb0a
--- /dev/null
+++ b/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.tsx.preview
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/docs/data/data-grid/recipes-row-grouping/recipes-row-grouping.md b/docs/data/data-grid/recipes-row-grouping/recipes-row-grouping.md
index 014bbd94411a..90067a6eb271 100644
--- a/docs/data/data-grid/recipes-row-grouping/recipes-row-grouping.md
+++ b/docs/data/data-grid/recipes-row-grouping/recipes-row-grouping.md
@@ -25,3 +25,11 @@ By default, the row grouping column uses `sortComparator` of the grouping column
To sort the row groups by the number of child rows, you can override it using `groupingColDef.sortComparator`:
{{"demo": "RowGroupingSortByChildRows.js", "bg": "inline", "defaultCodeOpen": false}}
+
+## Dispaying child row count in footer
+
+By default, the row count in the footer is the number of top level rows that are visible after filtering.
+
+In the demo below, a `CustomFooterRowCount` component is added to the `footerRowCount` slot. This component uses the `gridFilteredDescendantRowCountSelector` to get the number of child rows and display it alongside the number of groups.
+
+{{"demo": "RowGroupingChildRowCount.js", "bg": "inline", "defaultCodeOpen": false}}
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js
new file mode 100644
index 000000000000..3c0dbeb76f86
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js
@@ -0,0 +1,57 @@
+import * as React from 'react';
+import { DataGridPro } from '@mui/x-data-grid-pro';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+function ServerSideDataGrid() {
+ const { columns, initialState, fetchRows } = useMockServer(
+ {},
+ { useCursorPagination: false },
+ );
+
+ const dataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ }),
+ [fetchRows],
+ );
+
+ const initialStateWithPagination = React.useMemo(
+ () => ({
+ ...initialState,
+ pagination: {
+ paginationModel: { pageSize: 10, page: 0 },
+ rowCount: 0,
+ },
+ }),
+ [initialState],
+ );
+
+ return (
+
+
+
+ );
+}
+
+export default ServerSideDataGrid;
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx
new file mode 100644
index 000000000000..514b77b23bf9
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx
@@ -0,0 +1,57 @@
+import * as React from 'react';
+import { DataGridPro, GridDataSource } from '@mui/x-data-grid-pro';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+function ServerSideDataGrid() {
+ const { columns, initialState, fetchRows } = useMockServer(
+ {},
+ { useCursorPagination: false },
+ );
+
+ const dataSource: GridDataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ }),
+ [fetchRows],
+ );
+
+ const initialStateWithPagination = React.useMemo(
+ () => ({
+ ...initialState,
+ pagination: {
+ paginationModel: { pageSize: 10, page: 0 },
+ rowCount: 0,
+ },
+ }),
+ [initialState],
+ );
+
+ return (
+
+
+
+ );
+}
+
+export default ServerSideDataGrid;
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js
new file mode 100644
index 000000000000..e059e7b98b00
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js
@@ -0,0 +1,60 @@
+import * as React from 'react';
+import { DataGridPro } from '@mui/x-data-grid-pro';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const pageSizeOptions = [5, 10, 50];
+
+const serverOptions = { useCursorPagination: false };
+const dataSetOptions = {};
+
+export default function ServerSideDataGridNoCache() {
+ const { fetchRows, columns, initialState } = useMockServer(
+ dataSetOptions,
+ serverOptions,
+ );
+
+ const dataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ }),
+ [fetchRows],
+ );
+
+ const initialStateWithPagination = React.useMemo(
+ () => ({
+ ...initialState,
+ pagination: {
+ paginationModel: { pageSize: 10, page: 0 },
+ },
+ }),
+ [initialState],
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx
new file mode 100644
index 000000000000..ca8a9fe14814
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx
@@ -0,0 +1,60 @@
+import * as React from 'react';
+import { DataGridPro, GridDataSource } from '@mui/x-data-grid-pro';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const pageSizeOptions = [5, 10, 50];
+
+const serverOptions = { useCursorPagination: false };
+const dataSetOptions = {};
+
+export default function ServerSideDataGridNoCache() {
+ const { fetchRows, columns, initialState } = useMockServer(
+ dataSetOptions,
+ serverOptions,
+ );
+
+ const dataSource: GridDataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ }),
+ [fetchRows],
+ );
+
+ const initialStateWithPagination = React.useMemo(
+ () => ({
+ ...initialState,
+ pagination: {
+ paginationModel: { pageSize: 10, page: 0 },
+ },
+ }),
+ [initialState],
+ );
+
+ return (
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview
new file mode 100644
index 000000000000..ed2e75557b91
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx.preview
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js
new file mode 100644
index 000000000000..615cf4072a62
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js
@@ -0,0 +1,60 @@
+import * as React from 'react';
+import { DataGridPro, GridDataSourceCacheDefault } from '@mui/x-data-grid-pro';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const lowTTLCache = new GridDataSourceCacheDefault({ ttl: 1000 * 10 }); // 10 seconds
+
+function ServerSideDataGridTTL() {
+ const { columns, initialState, fetchRows } = useMockServer(
+ {},
+ { useCursorPagination: false },
+ );
+
+ const dataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ }),
+ [fetchRows],
+ );
+
+ const initialStateWithPagination = React.useMemo(
+ () => ({
+ ...initialState,
+ pagination: {
+ paginationModel: { pageSize: 10, page: 0 },
+ rowCount: 0,
+ },
+ }),
+ [initialState],
+ );
+
+ return (
+
+
+
+ );
+}
+
+export default ServerSideDataGridTTL;
diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx
new file mode 100644
index 000000000000..3f8f3525e78c
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx
@@ -0,0 +1,64 @@
+import * as React from 'react';
+import {
+ DataGridPro,
+ GridDataSource,
+ GridDataSourceCacheDefault,
+} from '@mui/x-data-grid-pro';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const lowTTLCache = new GridDataSourceCacheDefault({ ttl: 1000 * 10 }); // 10 seconds
+
+function ServerSideDataGridTTL() {
+ const { columns, initialState, fetchRows } = useMockServer(
+ {},
+ { useCursorPagination: false },
+ );
+
+ const dataSource: GridDataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ }),
+ [fetchRows],
+ );
+
+ const initialStateWithPagination = React.useMemo(
+ () => ({
+ ...initialState,
+ pagination: {
+ paginationModel: { pageSize: 10, page: 0 },
+ rowCount: 0,
+ },
+ }),
+ [initialState],
+ );
+
+ return (
+
+
+
+ );
+}
+
+export default ServerSideDataGridTTL;
diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js
new file mode 100644
index 000000000000..1bf394ea930f
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js
@@ -0,0 +1,125 @@
+import * as React from 'react';
+import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro';
+import Button from '@mui/material/Button';
+import Checkbox from '@mui/material/Checkbox';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import { alpha, styled, darken, lighten } from '@mui/material/styles';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const pageSizeOptions = [5, 10, 50];
+const serverOptions = { useCursorPagination: false };
+const datasetOptions = {};
+
+function getBorderColor(theme) {
+ if (theme.palette.mode === 'light') {
+ return lighten(alpha(theme.palette.divider, 1), 0.88);
+ }
+ return darken(alpha(theme.palette.divider, 1), 0.68);
+}
+
+const StyledDiv = styled('div')(({ theme: t }) => ({
+ position: 'absolute',
+ zIndex: 10,
+ fontSize: '0.875em',
+ top: 0,
+ height: '100%',
+ width: '100%',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: '4px',
+ border: `1px solid ${getBorderColor(t)}`,
+ backgroundColor: t.palette.background.default,
+}));
+
+function ErrorOverlay({ error }) {
+ if (!error) {
+ return null;
+ }
+ return {error} ;
+}
+
+export default function ServerSideErrorHandling() {
+ const apiRef = useGridApiRef();
+ const [error, setError] = React.useState();
+ const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false);
+
+ const { fetchRows, ...props } = useMockServer(
+ datasetOptions,
+ serverOptions,
+ shouldRequestsFail,
+ );
+
+ const dataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ }),
+ [fetchRows],
+ );
+
+ const initialState = React.useMemo(
+ () => ({
+ ...props.initialState,
+ pagination: {
+ paginationModel: {
+ pageSize: 5,
+ },
+ rowCount: 0,
+ },
+ }),
+ [props.initialState],
+ );
+
+ return (
+
+
+ {
+ setError('');
+ apiRef.current?.unstable_dataSource.fetchRows();
+ }}
+ >
+ Refetch rows
+
+ setShouldRequestsFail(e.target.checked)}
+ />
+ }
+ label="Make the requests fail"
+ />
+
+
+ setError(e.message)}
+ unstable_dataSourceCache={null}
+ apiRef={apiRef}
+ pagination
+ pageSizeOptions={pageSizeOptions}
+ initialState={initialState}
+ slots={{ toolbar: GridToolbar }}
+ />
+ {error && }
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx
new file mode 100644
index 000000000000..a030a8bf6e50
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx
@@ -0,0 +1,131 @@
+import * as React from 'react';
+import {
+ DataGridPro,
+ useGridApiRef,
+ GridInitialState,
+ GridToolbar,
+ GridDataSource,
+} from '@mui/x-data-grid-pro';
+import Button from '@mui/material/Button';
+import Checkbox from '@mui/material/Checkbox';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const pageSizeOptions = [5, 10, 50];
+const serverOptions = { useCursorPagination: false };
+const datasetOptions = {};
+
+function getBorderColor(theme: Theme) {
+ if (theme.palette.mode === 'light') {
+ return lighten(alpha(theme.palette.divider, 1), 0.88);
+ }
+ return darken(alpha(theme.palette.divider, 1), 0.68);
+}
+
+const StyledDiv = styled('div')(({ theme: t }) => ({
+ position: 'absolute',
+ zIndex: 10,
+ fontSize: '0.875em',
+ top: 0,
+ height: '100%',
+ width: '100%',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: '4px',
+ border: `1px solid ${getBorderColor(t)}`,
+ backgroundColor: t.palette.background.default,
+}));
+
+function ErrorOverlay({ error }: { error: string }) {
+ if (!error) {
+ return null;
+ }
+ return {error} ;
+}
+
+export default function ServerSideErrorHandling() {
+ const apiRef = useGridApiRef();
+ const [error, setError] = React.useState();
+ const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false);
+
+ const { fetchRows, ...props } = useMockServer(
+ datasetOptions,
+ serverOptions,
+ shouldRequestsFail,
+ );
+
+ const dataSource: GridDataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ }),
+ [fetchRows],
+ );
+
+ const initialState: GridInitialState = React.useMemo(
+ () => ({
+ ...props.initialState,
+ pagination: {
+ paginationModel: {
+ pageSize: 5,
+ },
+ rowCount: 0,
+ },
+ }),
+ [props.initialState],
+ );
+
+ return (
+
+
+ {
+ setError('');
+ apiRef.current?.unstable_dataSource.fetchRows();
+ }}
+ >
+ Refetch rows
+
+ setShouldRequestsFail(e.target.checked)}
+ />
+ }
+ label="Make the requests fail"
+ />
+
+
+ setError(e.message)}
+ unstable_dataSourceCache={null}
+ apiRef={apiRef}
+ pagination
+ pageSizeOptions={pageSizeOptions}
+ initialState={initialState}
+ slots={{ toolbar: GridToolbar }}
+ />
+ {error && }
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js
new file mode 100644
index 000000000000..ed0d0bc8e093
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js
@@ -0,0 +1,76 @@
+import * as React from 'react';
+import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro';
+import Button from '@mui/material/Button';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const pageSizeOptions = [5, 10, 50];
+const dataSetOptions = {
+ dataSet: 'Employee',
+ rowLength: 1000,
+ treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 },
+};
+
+export default function ServerSideTreeData() {
+ const apiRef = useGridApiRef();
+
+ const { fetchRows, columns, initialState } = useMockServer(dataSetOptions);
+
+ const initialStateWithPagination = React.useMemo(
+ () => ({
+ ...initialState,
+ pagination: {
+ paginationModel: {
+ pageSize: 5,
+ },
+ rowCount: 0,
+ },
+ }),
+ [initialState],
+ );
+
+ const dataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ getGroupKey: (row) => row[dataSetOptions.treeData.groupingField],
+ getChildrenCount: (row) => row.descendantCount,
+ }),
+ [fetchRows],
+ );
+
+ return (
+
+
apiRef.current.unstable_dataSource.cache.clear()}>
+ Reset cache
+
+
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx
new file mode 100644
index 000000000000..86a2e47fd1ef
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx
@@ -0,0 +1,82 @@
+import * as React from 'react';
+import {
+ DataGridPro,
+ useGridApiRef,
+ GridInitialState,
+ GridToolbar,
+ GridDataSource,
+} from '@mui/x-data-grid-pro';
+import Button from '@mui/material/Button';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const pageSizeOptions = [5, 10, 50];
+const dataSetOptions = {
+ dataSet: 'Employee' as const,
+ rowLength: 1000,
+ treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 },
+};
+
+export default function ServerSideTreeData() {
+ const apiRef = useGridApiRef();
+
+ const { fetchRows, columns, initialState } = useMockServer(dataSetOptions);
+
+ const initialStateWithPagination: GridInitialState = React.useMemo(
+ () => ({
+ ...initialState,
+ pagination: {
+ paginationModel: {
+ pageSize: 5,
+ },
+ rowCount: 0,
+ },
+ }),
+ [initialState],
+ );
+
+ const dataSource: GridDataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ getGroupKey: (row) => row[dataSetOptions.treeData.groupingField],
+ getChildrenCount: (row) => row.descendantCount,
+ }),
+ [fetchRows],
+ );
+
+ return (
+
+
apiRef.current.unstable_dataSource.cache.clear()}>
+ Reset cache
+
+
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview
new file mode 100644
index 000000000000..4a508197000b
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx.preview
@@ -0,0 +1,16 @@
+ apiRef.current.unstable_dataSource.cache.clear()}>
+ Reset cache
+
+
+
+
\ No newline at end of file
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js
new file mode 100644
index 000000000000..920a1b2d5609
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js
@@ -0,0 +1,107 @@
+import * as React from 'react';
+import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro';
+import Button from '@mui/material/Button';
+import { useMockServer } from '@mui/x-data-grid-generator';
+import { QueryClient } from '@tanstack/query-core';
+
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 1000 * 60 * 60,
+ },
+ },
+});
+
+function getKey(params) {
+ return [
+ params.paginationModel,
+ params.sortModel,
+ params.filterModel,
+ params.groupKeys,
+ ];
+}
+
+const cache = {
+ set: (key, value) => {
+ const queryKey = getKey(key);
+ queryClient.setQueryData(queryKey, value);
+ },
+ get: (key) => {
+ const queryKey = getKey(key);
+ return queryClient.getQueryData(queryKey);
+ },
+ clear: () => {
+ queryClient.clear();
+ },
+};
+
+const pageSizeOptions = [5, 10, 50];
+const dataSetOptions = {
+ dataSet: 'Employee',
+ rowLength: 1000,
+ treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 },
+};
+
+export default function ServerSideTreeDataCustomCache() {
+ const apiRef = useGridApiRef();
+
+ const { fetchRows, ...props } = useMockServer(dataSetOptions);
+
+ const dataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ getGroupKey: (row) => row[dataSetOptions.treeData.groupingField],
+ getChildrenCount: (row) => row.descendantCount,
+ }),
+ [fetchRows],
+ );
+
+ const initialState = React.useMemo(
+ () => ({
+ ...props.initialState,
+ pagination: {
+ paginationModel: {
+ pageSize: 5,
+ },
+ rowCount: 0,
+ },
+ }),
+ [props.initialState],
+ );
+
+ return (
+
+
queryClient.clear()}>Reset cache
+
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx
new file mode 100644
index 000000000000..a3f2961d549f
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx
@@ -0,0 +1,115 @@
+import * as React from 'react';
+import {
+ DataGridPro,
+ useGridApiRef,
+ GridInitialState,
+ GridToolbar,
+ GridDataSourceCache,
+ GridDataSource,
+ GridGetRowsParams,
+} from '@mui/x-data-grid-pro';
+import Button from '@mui/material/Button';
+import { useMockServer } from '@mui/x-data-grid-generator';
+import { QueryClient } from '@tanstack/query-core';
+
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 1000 * 60 * 60,
+ },
+ },
+});
+
+function getKey(params: GridGetRowsParams) {
+ return [
+ params.paginationModel,
+ params.sortModel,
+ params.filterModel,
+ params.groupKeys,
+ ];
+}
+
+const cache: GridDataSourceCache = {
+ set: (key: GridGetRowsParams, value) => {
+ const queryKey = getKey(key);
+ queryClient.setQueryData(queryKey, value);
+ },
+ get: (key: GridGetRowsParams) => {
+ const queryKey = getKey(key);
+ return queryClient.getQueryData(queryKey);
+ },
+ clear: () => {
+ queryClient.clear();
+ },
+};
+
+const pageSizeOptions = [5, 10, 50];
+const dataSetOptions = {
+ dataSet: 'Employee' as 'Employee',
+ rowLength: 1000,
+ treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 },
+};
+
+export default function ServerSideTreeDataCustomCache() {
+ const apiRef = useGridApiRef();
+
+ const { fetchRows, ...props } = useMockServer(dataSetOptions);
+
+ const dataSource: GridDataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ getGroupKey: (row) => row[dataSetOptions.treeData.groupingField],
+ getChildrenCount: (row) => row.descendantCount,
+ }),
+ [fetchRows],
+ );
+
+ const initialState: GridInitialState = React.useMemo(
+ () => ({
+ ...props.initialState,
+ pagination: {
+ paginationModel: {
+ pageSize: 5,
+ },
+ rowCount: 0,
+ },
+ }),
+ [props.initialState],
+ );
+
+ return (
+
+
queryClient.clear()}>Reset cache
+
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview
new file mode 100644
index 000000000000..1c8c80d0e4b6
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx.preview
@@ -0,0 +1,15 @@
+ queryClient.clear()}>Reset cache
+
+
+
\ No newline at end of file
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js
new file mode 100644
index 000000000000..44da5bf0cab3
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js
@@ -0,0 +1,148 @@
+import * as React from 'react';
+import { DataGridPro, useGridApiRef } from '@mui/x-data-grid-pro';
+import Snackbar from '@mui/material/Snackbar';
+import Button from '@mui/material/Button';
+import Checkbox from '@mui/material/Checkbox';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import { alpha, styled, darken, lighten } from '@mui/material/styles';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const pageSizeOptions = [5, 10, 50];
+const serverOptions = { useCursorPagination: false };
+const dataSetOptions = {
+ dataSet: 'Employee',
+ rowLength: 1000,
+ treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 },
+};
+
+export default function ServerSideTreeDataErrorHandling() {
+ const apiRef = useGridApiRef();
+ const [rootError, setRootError] = React.useState();
+ const [childrenError, setChildrenError] = React.useState();
+ const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false);
+
+ const { fetchRows, ...props } = useMockServer(
+ dataSetOptions,
+ serverOptions,
+ shouldRequestsFail,
+ );
+
+ const dataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ getGroupKey: (row) => row[dataSetOptions.treeData.groupingField],
+ getChildrenCount: (row) => row.descendantCount,
+ }),
+ [fetchRows],
+ );
+
+ const initialState = React.useMemo(
+ () => ({
+ ...props.initialState,
+ pagination: {
+ paginationModel: {
+ pageSize: 5,
+ },
+ rowCount: 0,
+ },
+ }),
+ [props.initialState],
+ );
+
+ return (
+
+
+ {
+ setRootError('');
+ apiRef.current.unstable_dataSource.fetchRows();
+ }}
+ >
+ Refetch rows
+
+ setShouldRequestsFail(e.target.checked)}
+ />
+ }
+ label="Make the requests fail"
+ />
+
+
+ {
+ if (!params.groupKeys || params.groupKeys.length === 0) {
+ setRootError(e.message);
+ } else {
+ setChildrenError(
+ `${e.message} (Requested level: ${params.groupKeys.join(' > ')})`,
+ );
+ }
+ }}
+ unstable_dataSourceCache={null}
+ apiRef={apiRef}
+ pagination
+ pageSizeOptions={pageSizeOptions}
+ initialState={initialState}
+ />
+ {rootError && }
+ setChildrenError('')}
+ message={childrenError}
+ />
+
+
+ );
+}
+
+function getBorderColor(theme) {
+ if (theme.palette.mode === 'light') {
+ return lighten(alpha(theme.palette.divider, 1), 0.88);
+ }
+ return darken(alpha(theme.palette.divider, 1), 0.68);
+}
+
+const StyledDiv = styled('div')(({ theme: t }) => ({
+ position: 'absolute',
+ zIndex: 10,
+ fontSize: '0.875em',
+ top: 0,
+ height: '100%',
+ width: '100%',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: '4px',
+ border: `1px solid ${getBorderColor(t)}`,
+ backgroundColor: t.palette.background.default,
+}));
+
+function ErrorOverlay({ error }) {
+ if (!error) {
+ return null;
+ }
+ return {error} ;
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx
new file mode 100644
index 000000000000..fdd4deeb1771
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx
@@ -0,0 +1,153 @@
+import * as React from 'react';
+import {
+ DataGridPro,
+ useGridApiRef,
+ GridInitialState,
+ GridDataSource,
+} from '@mui/x-data-grid-pro';
+import Snackbar from '@mui/material/Snackbar';
+import Button from '@mui/material/Button';
+import Checkbox from '@mui/material/Checkbox';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import { alpha, styled, darken, lighten, Theme } from '@mui/material/styles';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const pageSizeOptions = [5, 10, 50];
+const serverOptions = { useCursorPagination: false };
+const dataSetOptions = {
+ dataSet: 'Employee' as 'Employee',
+ rowLength: 1000,
+ treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 },
+};
+
+export default function ServerSideTreeDataErrorHandling() {
+ const apiRef = useGridApiRef();
+ const [rootError, setRootError] = React.useState();
+ const [childrenError, setChildrenError] = React.useState();
+ const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false);
+
+ const { fetchRows, ...props } = useMockServer(
+ dataSetOptions,
+ serverOptions,
+ shouldRequestsFail,
+ );
+
+ const dataSource: GridDataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ getGroupKey: (row) => row[dataSetOptions.treeData.groupingField],
+ getChildrenCount: (row) => row.descendantCount,
+ }),
+ [fetchRows],
+ );
+
+ const initialState: GridInitialState = React.useMemo(
+ () => ({
+ ...props.initialState,
+ pagination: {
+ paginationModel: {
+ pageSize: 5,
+ },
+ rowCount: 0,
+ },
+ }),
+ [props.initialState],
+ );
+
+ return (
+
+
+ {
+ setRootError('');
+ apiRef.current.unstable_dataSource.fetchRows();
+ }}
+ >
+ Refetch rows
+
+ setShouldRequestsFail(e.target.checked)}
+ />
+ }
+ label="Make the requests fail"
+ />
+
+
+ {
+ if (!params.groupKeys || params.groupKeys.length === 0) {
+ setRootError(e.message);
+ } else {
+ setChildrenError(
+ `${e.message} (Requested level: ${params.groupKeys.join(' > ')})`,
+ );
+ }
+ }}
+ unstable_dataSourceCache={null}
+ apiRef={apiRef}
+ pagination
+ pageSizeOptions={pageSizeOptions}
+ initialState={initialState}
+ />
+ {rootError && }
+ setChildrenError('')}
+ message={childrenError}
+ />
+
+
+ );
+}
+
+function getBorderColor(theme: Theme) {
+ if (theme.palette.mode === 'light') {
+ return lighten(alpha(theme.palette.divider, 1), 0.88);
+ }
+ return darken(alpha(theme.palette.divider, 1), 0.68);
+}
+
+const StyledDiv = styled('div')(({ theme: t }) => ({
+ position: 'absolute',
+ zIndex: 10,
+ fontSize: '0.875em',
+ top: 0,
+ height: '100%',
+ width: '100%',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: '4px',
+ border: `1px solid ${getBorderColor(t)}`,
+ backgroundColor: t.palette.background.default,
+}));
+
+function ErrorOverlay({ error }: { error: string }) {
+ if (!error) {
+ return null;
+ }
+ return {error} ;
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js
new file mode 100644
index 000000000000..9b61ceacdcf8
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js
@@ -0,0 +1,77 @@
+import * as React from 'react';
+import { DataGridPro, useGridApiRef, GridToolbar } from '@mui/x-data-grid-pro';
+import Button from '@mui/material/Button';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+const pageSizeOptions = [5, 10, 50];
+const dataSetOptions = {
+ dataSet: 'Employee',
+ rowLength: 1000,
+ treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 },
+};
+
+export default function ServerSideTreeDataGroupExpansion() {
+ const apiRef = useGridApiRef();
+
+ const { fetchRows, columns, initialState } = useMockServer(dataSetOptions);
+
+ const dataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ getGroupKey: (row) => row[dataSetOptions.treeData.groupingField],
+ getChildrenCount: (row) => row.descendantCount,
+ }),
+ [fetchRows],
+ );
+
+ const initialStateWithPagination = React.useMemo(
+ () => ({
+ ...initialState,
+ pagination: {
+ paginationModel: {
+ pageSize: 5,
+ },
+ rowCount: 0,
+ },
+ }),
+ [initialState],
+ );
+
+ return (
+
+
apiRef.current.unstable_dataSource.cache.clear()}>
+ Reset cache
+
+
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx
new file mode 100644
index 000000000000..9eb7fdd75ba4
--- /dev/null
+++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx
@@ -0,0 +1,85 @@
+import * as React from 'react';
+import {
+ DataGridPro,
+ useGridApiRef,
+ GridInitialState,
+ GridToolbar,
+ GridDataSource,
+} from '@mui/x-data-grid-pro';
+import Button from '@mui/material/Button';
+import { useMockServer } from '@mui/x-data-grid-generator';
+
+type DataSet = 'Commodity' | 'Employee';
+
+const pageSizeOptions = [5, 10, 50];
+const dataSetOptions = {
+ dataSet: 'Employee' as DataSet,
+ rowLength: 1000,
+ treeData: { maxDepth: 3, groupingField: 'name', averageChildren: 5 },
+};
+
+export default function ServerSideTreeDataGroupExpansion() {
+ const apiRef = useGridApiRef();
+
+ const { fetchRows, columns, initialState } = useMockServer(dataSetOptions);
+
+ const dataSource: GridDataSource = React.useMemo(
+ () => ({
+ getRows: async (params) => {
+ const urlParams = new URLSearchParams({
+ paginationModel: encodeURIComponent(
+ JSON.stringify(params.paginationModel),
+ ),
+ filterModel: encodeURIComponent(JSON.stringify(params.filterModel)),
+ sortModel: encodeURIComponent(JSON.stringify(params.sortModel)),
+ groupKeys: encodeURIComponent(JSON.stringify(params.groupKeys)),
+ });
+ const getRowsResponse = await fetchRows(
+ `https://mui.com/x/api/data-grid?${urlParams.toString()}`,
+ );
+ return {
+ rows: getRowsResponse.rows,
+ rowCount: getRowsResponse.rowCount,
+ };
+ },
+ getGroupKey: (row) => row[dataSetOptions.treeData.groupingField],
+ getChildrenCount: (row) => row.descendantCount,
+ }),
+ [fetchRows],
+ );
+
+ const initialStateWithPagination: GridInitialState = React.useMemo(
+ () => ({
+ ...initialState,
+ pagination: {
+ paginationModel: {
+ pageSize: 5,
+ },
+ rowCount: 0,
+ },
+ }),
+ [initialState],
+ );
+
+ return (
+
+
apiRef.current.unstable_dataSource.cache.clear()}>
+ Reset cache
+
+
+
+
+
+ );
+}
diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md
index a626f60d7c3c..38a003c98581 100644
--- a/docs/data/data-grid/server-side-data/index.md
+++ b/docs/data/data-grid/server-side-data/index.md
@@ -2,23 +2,19 @@
title: React Data Grid - Server-side data
---
-# Data Grid - Server-side data π§
+# Data Grid - Server-side data [ ](/x/introduction/licensing/#pro-plan 'Pro plan')
The data grid server-side data.
-## Overview
+## Introduction
-Managing server-side data efficiently in a React application can become complex as the dataset grows.
+Server-side data management in React can become complex with growing datasets.
+Challenges include manual data fetching, pagination, sorting, filtering, and performance optimization.
+A dedicated module can help abstract these complexities, improving user experience.
-Without a dedicated module that abstracts its complexities, developers often face challenges related to manual data fetching, pagination, sorting, and filtering, and it often gets trickier to tackle performance issues, which can lead to a poor user experience.
-
-Have a look at an example:
-
-### Example scenario
-
-Imagine having a data grid that displays a list of users. The data grid has pagination enabled and the user can sort the data by clicking on the column headers and also apply filters.
-
-The data grid is configured to fetch data from the server whenever the user changes the page or updates filtering or sorting.
+Consider a Data Grid displaying a list of users.
+It supports pagination, sorting by column headers, and filtering.
+The Data Grid fetches data from the server when the user changes the page or updates filtering or sorting.
```tsx
const [rows, setRows] = React.useState([]);
@@ -72,51 +68,51 @@ Trying to solve these problems one after the other can make the code complex and
## Data source
-A very common pattern to solve these problems is to use a centralized data source. A data source is an abstraction layer that sits between the data grid and the server. It provides a simple interface to the data grid to fetch data and update it. It handles a lot of the complexities related to server-side data fetching. Let's delve a bit deeper into how it will look like.
+The idea for a centralized data source is to simplify server-side data fetching.
+It's an abstraction layer between the Data Grid and the server, providing a simple interface for interacting with the server.
+Think of it like a middleman handling the communication between the Data Grid (client) and the actual data source (server).
:::warning
-This feature is still under development and the information shared on this page is subject to change. Feel free to subscribe or comment on the official GitHub [issue](https://github.com/mui/mui-x/issues/8179).
+This feature is under development and is marked as **unstable**.
+The information shared on this page could change in future.
+Feel free to subscribe or comment on the official GitHub [umbrella issue](https://github.com/mui/mui-x/issues/8179).
:::
-### Overview
-
-The Data Grid already supports manual server-side data fetching for features like sorting, filtering, etc. In order to make it more powerful and simple to use, the grid will support a data source interface that you can implement with your existing data fetching logic.
-
-The datasource will work with all the major data grid features which require server-side data fetching such as sorting, filtering, pagination, grouping, etc.
+It has an initial set of required methods that you need to implement. The data grid will use these methods internally to fetch a sub-set of data when needed.
-### Usage
-
-The data grid server-side data source has an initial set of required methods that you need to implement. The data grid will call these methods internally when the data is required for a specific page.
+Let's take a look at the minimal `GridDataSource` interface configuration.
```tsx
-interface DataSource {
+interface GridDataSource {
/**
- Fetcher Functions:
- - `getRows` is required
- - `updateRow` is optional
-
- `getRows` will be used by the grid to fetch data for the current page or children for the current parent group
- It may return a `rowCount` to update the total count of rows in the grid
- */
- getRows(params: GetRowsParams): Promise;
- updateRow?(updatedRow: GridRowModel): Promise;
+ * This method will be called when the grid needs to fetch some rows
+ * @param {GridGetRowsParams} params The parameters required to fetch the rows
+ * @returns {Promise} A promise that resolves to the data of type [GridGetRowsResponse]
+ */
+ getRows(params: GridGetRowsParams): Promise;
}
```
-Here's how the code will look like for the above example when implemented with data source:
+:::info
+
+The above interface is a minimal configuration required for a data source to work.
+More specific properties like `getChildrenCount` and `getGroupKey` will be discussed in the corresponding sections.
+
+:::
+
+Here's how the above mentioned example would look like when implemented with the data source:
```tsx
-const customDataSource: DataSource = {
- getRows: async (params: GetRowsParams): GetRowsResponse => {
- // fetch data from server
+const customDataSource: GridDataSource = {
+ getRows: async (params: GridGetRowsParams): GetRowsResponse => {
const response = await fetch('https://my-api.com/data', {
method: 'GET',
body: JSON.stringify(params),
});
const data = await response.json();
- // return the data and the total number of rows
+
return {
rows: data.rows,
rowCount: data.totalCount,
@@ -126,161 +122,145 @@ const customDataSource: DataSource = {
```
-Not only the code has been reduced significantly, it has removed the hassle of managing controlled states and data fetching logic too.
+The code has been significantly reduced, the need for managing the controlled states is removed, and data fetching logic is centralized.
-On top of that, the data source will also handle a lot of other aspects like caching and deduping of requests.
+## Server-side filtering, sorting, and pagination
-#### Loading data
+The data source changes how the existing server-side features like `filtering`, `sorting`, and `pagination` work.
-The method `dataSource.getRows` will be called with the `GetRowsParams` object whenever some data from the server is needed. This object contains all the information that you need to fetch the data from the server.
+**Without data source**
-Since previously, the data grid did not support internal data fetching, the `rows` prop was the way to pass the data to the grid. However, with server-side data, the `rows` prop is no longer needed. Instead, the data grid will call the `getRows` method whenever it needs to fetch data.
-
-Here's the `GetRowsParams` object for reference:
+When there's no data source, the features `filtering`, `sorting`, `pagination` work on `client` by default.
+In order for them to work with server-side data, you need to set them to `server` explicitly and provide the [`onFilterModelChange`](https://mui.com/x/react-data-grid/filtering/server-side/), [`onSortModelChange`](https://mui.com/x/react-data-grid/sorting/#server-side-sorting), [`onPaginationModelChange`](https://mui.com/x/react-data-grid/pagination/#server-side-pagination) event handlers to fetch the data from the server based on the updated variables.
```tsx
-interface GetRowsParams {
- sortModel: GridSortModel;
- filterModel: GridFilterModel;
- /**
- * Alternate to `start` and `end`, maps to `GridPaginationModel` interface
- */
- paginationModel: GridPaginationModel;
- /**
- * First row index to fetch (number) or cursor information (number | string)
- */
- start: number | string; // first row index to fetch or cursor information
- /**
- * Last row index to fetch
- */
- end: number; // last row index to fetch
- /**
- * Array of keys returned by `getGroupKey` of all the parent rows until the row for which the data is requested
- * `getGroupKey` prop must be implemented to use this
- * Useful for `treeData` and `rowGrouping` only
- */
- groupKeys: string[];
- /**
- * List of grouped columns (only applicable with `rowGrouping`)
- */
- groupFields: GridColDef['field'][]; // list of grouped columns (`rowGrouping`)
-}
+ {
+ // fetch data from server
+ }}
+ onSortModelChange={(newSortModel) => {
+ // fetch data from server
+ }}
+ onFilterModelChange={(newFilterModel) => {
+ // fetch data from server
+ }}
+/>
```
-And here's the `GetRowsResponse` object for reference:
+**With data source**
+
+With the data source, the features `filtering`, `sorting`, `pagination` are automatically set to `server`.
+
+When the corresponding models update, the data grid calls the `getRows` method with the updated values of type `GridGetRowsParams` to get updated data.
```tsx
-interface GetRowsResponse {
- /**
- * Subset of the rows as per the passed `GetRowsParams`
- */
- rows: GridRowModel[];
- /**
- * To reflect updates in total `rowCount` (optional)
- * Useful when the `rowCount` is inaccurate (for example when filtering) or not available upfront
- */
- rowCount?: number;
- /**
- * Additional `pageInfo` to help the grid determine if there are more rows to fetch (corner-cases)
- * `hasNextPage`: When row count is unknown/inaccurate, if `truncated` is set or rowCount is not known, data will keep loading until `hasNextPage` is `false`
- * `truncated`: To reflect `rowCount` is inaccurate (will trigger `x-y of many` in pagination after the count of rows fetched is greater than provided `rowCount`)
- * It could be useful with:
- * 1. Cursor based pagination:
- * When rowCount is not known, grid will check for `hasNextPage` to determine
- * if there are more rows to fetch.
- * 2. Inaccurate `rowCount`:
- * `truncated: true` will let the grid know that `rowCount` is estimated/truncated.
- * Thus `hasNextPage` will come into play to check more rows are available to fetch after the number becomes >= provided `rowCount`
- */
- pageInfo?: {
- hasNextPage?: boolean;
- truncated?: number;
- };
-}
+
```
-#### Updating data
+The following demo showcases this behavior.
-If provided, the method `dataSource.updateRow` will be called with the `GridRowModel` object whenever the user edits a row. This method is optional and you can skip it if you don't need to update the data on the server. It will work in a similar way as the `processRowUpdate` prop.
+{{"demo": "ServerSideDataGrid.js", "bg": "inline"}}
-#### Data Grid props
+:::info
+The data source demos use a utility function `useMockServer` to simulate the server-side data fetching.
+In a real-world scenario, you should replace this with your own server-side data fetching logic.
-These data grid props will work with the server-side data source:
+Open info section of the browser console to see the requests being made and the data being fetched in response.
+:::
-- `dataSource: DataSource`: the data source object that you need to implement
-- `rows`: will be ignored, could be skipped when `dataSource` is provided
-- `rowCount`: will be used to identify the total number of rows in the grid, if not provided, the grid will check for the _GetRowsResponse.rowCount_ value, unless the feature being used is infinite loading where no `rowCount` is available at all.
+## Data caching
-Props related to grouped data (`treeData` and `rowGrouping`):
+The data source caches fetched data by default.
+This means that if the user navigates to a page or expands a node that has already been fetched, the grid will not call the `getRows` function again to avoid unnecessary calls to the server.
-- `getGroupKey(row: GridRowModel): string`
+The `GridDataSourceCacheDefault` is used by default which is a simple in-memory cache that stores the data in a plain object. It could be seen in action in the demo below.
- will be used by the grid to group rows by their parent group
- This effectively replaces `getTreeDataPath`.
- Consider this structure:
+{{"demo": "ServerSideDataGrid.js", "bg": "inline"}}
- ```js
- - (1) Sarah // groupKey 'Sarah'
- - (2) Thomas // groupKey 'Thomas'
- ```
+### Customize the cache lifetime
- When (2) is expanded, the `getRows` function will be called with group keys `['Sarah', 'Thomas']`.
+The `GridDataSourceCacheDefault` has a default Time To Live (`ttl`) of 5 minutes. To customize it, pass the `ttl` option in milliseconds to the `GridDataSourceCacheDefault` constructor, and then pass it as the `unstable_dataSourceCache` prop.
-- `hasChildren?(row: GridRowModel): boolean`
+```tsx
+import { GridDataSourceCacheDefault } from '@mui/x-data-grid-pro';
- Will be used by the grid to determine if a row has children on server
+const lowTTLCache = new GridDataSourceCacheDefault({ ttl: 1000 * 10 }); // 10 seconds
-- `getChildrenCount?: (row: GridRowModel) => number`
+ ;
+```
+
+{{"demo": "ServerSideDataGridTTL.js", "bg": "inline"}}
+
+### Custom cache
- Will be used by the grid to determine the number of children of a row on server
+To provide a custom cache, use `unstable_dataSourceCache` prop, which could be either written from scratch or based out of another cache library.
+This prop accepts a generic interface of type `GridDataSourceCache`.
-#### Existing server-side features
+```tsx
+export interface GridDataSourceCache {
+ set: (key: GridGetRowsParams, value: GridGetRowsResponse) => void;
+ get: (key: GridGetRowsParams) => GridGetRowsResponse | undefined;
+ clear: () => void;
+}
+```
-The server-side data source will change a bit the way existing server-side features like `filtering`, `sorting`, and `pagination` work.
+### Disable cache
-**Without data source**:
-When there's no data source, the features `filtering`, `sorting`, `pagination` will work on `client` by default. In order for them to work with server-side data, you need to set them to `server` explicitly and listen to the [`onFilterModelChange`](https://mui.com/x/react-data-grid/filtering/server-side/), [`onSortModelChange`](https://mui.com/x/react-data-grid/sorting/#server-side-sorting), [`onPaginationModelChange`](https://mui.com/x/react-data-grid/pagination/#server-side-pagination) events to fetch the data from the server based on the updated variables.
+To disable the data source cache, pass `null` to the `unstable_dataSourceCache` prop.
```tsx
- {
- // fetch data from server
- }}
- onSortModelChange={(newSortModel) => {
- // fetch data from server
- }}
- onFilterModelChange={(newFilterModel) => {
- // fetch data from server
- }}
+ unstable_dataSource={customDataSource}
+ unstable_dataSourceCache={null}
/>
```
-**With data source**:
-However, with a valid data source passed the features `filtering`, `sorting`, `pagination` will automatically be set to `server`.
+{{"demo": "ServerSideDataGridNoCache.js", "bg": "inline"}}
-You just need to implement the `getRows` method and the data grid will call the `getRows` method with the proper params whenever it needs data.
+## Error handling
+
+You could handle the errors with the data source by providing an error handler function using the `unstable_onDataSourceError`.
+It will be called whenever there's an error in fetching the data.
+
+The first argument of this function is the error object, and the second argument is the fetch parameters of type `GridGetRowsParams`.
```tsx
{
+ console.error(error);
+ }}
/>
```
-#### Caching
+{{"demo": "ServerSideErrorHandling.js", "bg": "inline"}}
+
+## Updating data π§
+
+This feature is yet to be implemented, when completed, the method `unstable_dataSource.updateRow` will be called with the `GridRowModel` whenever the user edits a row.
+It will work in a similar way as the `processRowUpdate` prop.
-The data grid will cache the data it receives from the server. This means that if the user navigates to a page that has already been fetched, the grid will not call the `getRows` function again. This is to avoid unnecessary calls to the server.
+Feel free to upvote the related GitHub [issue](https://github.com/mui/mui-x/issues/13261) to see this feature land faster.
## API
diff --git a/docs/data/data-grid/server-side-data/tree-data.md b/docs/data/data-grid/server-side-data/tree-data.md
index ba9d93e36719..8e77fe0542bf 100644
--- a/docs/data/data-grid/server-side-data/tree-data.md
+++ b/docs/data/data-grid/server-side-data/tree-data.md
@@ -2,14 +2,72 @@
title: React Server-side tree data
---
-# Data Grid - Server-side tree data [ ](/x/introduction/licensing/#pro-plan 'Pro plan')π§
+# Data Grid - Server-side tree data [ ](/x/introduction/licensing/#pro-plan 'Pro plan')
Tree data lazy-loading with server-side data source.
-:::warning
-This feature isn't implemented yet. It's coming.
+To dynamically load tree data from the server, including lazy-loading of children, you must create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [overview section](/x/react-data-grid/server-side-data/).
-π Upvote [issue #3377](https://github.com/mui/mui-x/issues/3377) if you want to see it land faster.
+The data source also requires some additional props to handle tree data, namely `getGroupKey` and `getChildrenCount`.
+If the children count is not available for some reason, but there are some children, `getChildrenCount` should return `-1`.
-Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this component, or if you are facing a pain point with the [currently proposed workaround](https://mui.com/x/react-data-grid/tree-data/#children-lazy-loading).
+```tsx
+const customDataSource: GridDataSource = {
+ getRows: async (params) => {
+ // Fetch the data from the server
+ },
+ getGroupKey: (row) => {
+ // Return the group key for the row, e.g. `name`
+ return row.name;
+ },
+ getChildrenCount: (row) => {
+ // Return the number of children for the row
+ return row.childrenCount;
+ },
+};
+```
+
+The following tree data example supports filtering, sorting, and pagination on the server.
+It also caches the data by default.
+
+{{"demo": "ServerSideTreeData.js", "bg": "inline"}}
+
+:::info
+The data source demos use a utility function `useMockServer` to simulate the server-side data fetching.
+In a real-world scenario, you would replace this with your own server-side data fetching logic.
+
+Open the info section of the browser console to see the requests being made and the data being fetched in response.
:::
+
+## Error handling
+
+For each row group expansion, the data source is called to fetch the children. If an error occurs during the fetch, the grid will display an error message in the row group cell. `unstable_onDataSourceError` is also triggered with the error and the fetch params.
+
+The demo below shows a toast apart from the default error message in the grouping cell. Cache has been disabled in this demo for simplicity.
+
+{{"demo": "ServerSideTreeDataErrorHandling.js", "bg": "inline"}}
+
+## Group expansion
+
+The idea behind the group expansion is the same as explained in the [Row grouping](/x/react-data-grid/row-grouping/#group-expansion) section.
+The difference is that the data is not initially available and is fetched automatically after the Data Grid is mounted based on the props `defaultGroupingExpansionDepth` and `isGroupExpandedByDefault` in a waterfall manner.
+
+The following demo uses `defaultGroupingExpansionDepth='-1'` to expand all levels of the tree by default.
+
+{{"demo": "ServerSideTreeDataGroupExpansion.js", "bg": "inline"}}
+
+## Custom cache
+
+The data source uses a cache by default to store the fetched data.
+Use the `unstable_dataSourceCache` prop to provide a custom cache if necessary.
+See [Data caching](/x/react-data-grid/server-side-data/#data-caching) for more info.
+
+The following demo uses `QueryClient` from `@tanstack/react-core` as a data source cache.
+
+{{"demo": "ServerSideTreeDataCustomCache.js", "bg": "inline"}}
+
+## API
+
+- [DataGrid](/x/api/data-grid/data-grid/)
+- [DataGridPro](/x/api/data-grid/data-grid-pro/)
+- [DataGridPremium](/x/api/data-grid/data-grid-premium/)
diff --git a/docs/data/data-grid/tree-data/TreeDataLazyLoading.js b/docs/data/data-grid/tree-data/TreeDataLazyLoading.js
deleted file mode 100644
index ea3f9c8fd2a4..000000000000
--- a/docs/data/data-grid/tree-data/TreeDataLazyLoading.js
+++ /dev/null
@@ -1,297 +0,0 @@
-import * as React from 'react';
-import {
- DataGridPro,
- getDataGridUtilityClass,
- useGridApiContext,
- useGridApiRef,
- useGridRootProps,
-} from '@mui/x-data-grid-pro';
-import { unstable_composeClasses as composeClasses, styled } from '@mui/material';
-import Box from '@mui/material/Box';
-import CircularProgress from '@mui/material/CircularProgress';
-import IconButton from '@mui/material/IconButton';
-
-export const isNavigationKey = (key) =>
- key === 'Home' ||
- key === 'End' ||
- key.indexOf('Arrow') === 0 ||
- key.indexOf('Page') === 0 ||
- key === ' ';
-
-const ALL_ROWS = [
- {
- hierarchy: ['Sarah'],
- jobTitle: 'Head of Human Resources',
- recruitmentDate: new Date(2020, 8, 12),
- id: 0,
- },
- {
- hierarchy: ['Thomas'],
- jobTitle: 'Head of Sales',
- recruitmentDate: new Date(2017, 3, 4),
- id: 1,
- },
- {
- hierarchy: ['Thomas', 'Robert'],
- jobTitle: 'Sales Person',
- recruitmentDate: new Date(2020, 11, 20),
- id: 2,
- },
- {
- hierarchy: ['Thomas', 'Karen'],
- jobTitle: 'Sales Person',
- recruitmentDate: new Date(2020, 10, 14),
- id: 3,
- },
- {
- hierarchy: ['Thomas', 'Nancy'],
- jobTitle: 'Sales Person',
- recruitmentDate: new Date(2017, 10, 29),
- id: 4,
- },
- {
- hierarchy: ['Thomas', 'Daniel'],
- jobTitle: 'Sales Person',
- recruitmentDate: new Date(2020, 7, 21),
- id: 5,
- },
- {
- hierarchy: ['Thomas', 'Christopher'],
- jobTitle: 'Sales Person',
- recruitmentDate: new Date(2020, 7, 20),
- id: 6,
- },
- {
- hierarchy: ['Thomas', 'Donald'],
- jobTitle: 'Sales Person',
- recruitmentDate: new Date(2019, 6, 28),
- id: 7,
- },
- {
- hierarchy: ['Mary'],
- jobTitle: 'Head of Engineering',
- recruitmentDate: new Date(2016, 3, 14),
- id: 8,
- },
- {
- hierarchy: ['Mary', 'Jennifer'],
- jobTitle: 'Tech lead front',
- recruitmentDate: new Date(2016, 5, 17),
- id: 9,
- },
- {
- hierarchy: ['Mary', 'Jennifer', 'Anna'],
- jobTitle: 'Front-end developer',
- recruitmentDate: new Date(2019, 11, 7),
- id: 10,
- },
- {
- hierarchy: ['Mary', 'Michael'],
- jobTitle: 'Tech lead devops',
- recruitmentDate: new Date(2021, 7, 1),
- id: 11,
- },
- {
- hierarchy: ['Mary', 'Linda'],
- jobTitle: 'Tech lead back',
- recruitmentDate: new Date(2017, 0, 12),
- id: 12,
- },
- {
- hierarchy: ['Mary', 'Linda', 'Elizabeth'],
- jobTitle: 'Back-end developer',
- recruitmentDate: new Date(2019, 2, 22),
- id: 13,
- },
- {
- hierarchy: ['Mary', 'Linda', 'William'],
- jobTitle: 'Back-end developer',
- recruitmentDate: new Date(2018, 4, 19),
- id: 14,
- },
-];
-
-const columns = [
- { field: 'jobTitle', headerName: 'Job Title', width: 200 },
- {
- field: 'recruitmentDate',
- headerName: 'Recruitment Date',
- type: 'date',
- width: 150,
- },
-];
-
-const getChildren = (parentPath) => {
- const parentPathStr = parentPath.join('-');
- return ALL_ROWS.filter(
- (row) => row.hierarchy.slice(0, -1).join('-') === parentPathStr,
- );
-};
-
-/**
- * This is a naive implementation with terrible performances on a real dataset.
- * This fake server is only here for demonstration purposes.
- */
-const fakeDataFetcher = (parentPath = []) =>
- new Promise((resolve) => {
- setTimeout(
- () => {
- const rows = getChildren(parentPath).map((row) => ({
- ...row,
- descendantCount: getChildren(row.hierarchy).length,
- }));
- resolve(rows);
- },
- 500 + Math.random() * 300,
- );
- });
-
-const LoadingContainer = styled('div')({
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- height: '100%',
-});
-
-const getTreeDataPath = (row) => row.hierarchy;
-
-const useUtilityClasses = (ownerState) => {
- const { classes } = ownerState;
-
- const slots = {
- root: ['treeDataGroupingCell'],
- toggle: ['treeDataGroupingCellToggle'],
- };
-
- return composeClasses(slots, getDataGridUtilityClass, classes);
-};
-
-/**
- * Reproduce the behavior of the `GridTreeDataGroupingCell` component in `@mui/x-data-grid-pro`
- * But base the amount of children on a `row.descendantCount` property rather than on the internal lookups.
- */
-function GroupingCellWithLazyLoading(props) {
- const { id, rowNode, row, hideDescendantCount, formattedValue } = props;
-
- const rootProps = useGridRootProps();
- const apiRef = useGridApiContext();
- const classes = useUtilityClasses({ classes: rootProps.classes });
-
- const isLoading = rowNode.childrenExpanded ? !row.childrenFetched : false;
-
- const Icon = rowNode.childrenExpanded
- ? rootProps.slots.treeDataCollapseIcon
- : rootProps.slots.treeDataExpandIcon;
-
- const handleClick = () => {
- apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded);
- };
-
- return (
-
-
- {row.descendantCount > 0 &&
- (isLoading ? (
-
-
-
- ) : (
-
-
-
- ))}
-
-
- {formattedValue === undefined ? rowNode.groupingKey : formattedValue}
- {!hideDescendantCount && row.descendantCount > 0
- ? ` (${row.descendantCount})`
- : ''}
-
-
- );
-}
-
-const CUSTOM_GROUPING_COL_DEF = {
- renderCell: (params) => ,
-};
-
-// Optional
-const getRowId = (row) => {
- if (typeof row?.id === 'string' && row?.id.startsWith('placeholder-children-')) {
- return row.id;
- }
- return row.id;
-};
-
-function updateRows(apiRef, rows) {
- if (!apiRef.current) {
- return;
- }
- const rowsToAdd = [...rows];
- rows.forEach((row) => {
- if (row.descendantCount && row.descendantCount > 0) {
- // Add a placeholder row to make the row expandable
- rowsToAdd.push({
- id: `placeholder-children-${getRowId(row)}`,
- hierarchy: [...row.hierarchy, ''],
- });
- }
- });
- apiRef.current.updateRows(rowsToAdd);
-}
-
-const initialRows = [];
-
-export default function TreeDataLazyLoading() {
- const apiRef = useGridApiRef();
-
- React.useEffect(() => {
- fakeDataFetcher().then((rowsData) => {
- updateRows(apiRef, rowsData);
- });
-
- const handleRowExpansionChange = async (node) => {
- const row = apiRef.current.getRow(node.id);
-
- if (!node.childrenExpanded || !row || row.childrenFetched) {
- return;
- }
-
- const childrenRows = await fakeDataFetcher(row.hierarchy);
- updateRows(apiRef, [
- ...childrenRows,
- { ...row, childrenFetched: true },
- { id: `placeholder-children-${node.id}`, _action: 'delete' },
- ]);
- };
-
- return apiRef.current.subscribeEvent(
- 'rowExpansionChange',
- handleRowExpansionChange,
- );
- }, [apiRef]);
-
- return (
-
-
-
- );
-}
diff --git a/docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx b/docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx
deleted file mode 100644
index 4eb26369ed09..000000000000
--- a/docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx
+++ /dev/null
@@ -1,329 +0,0 @@
-import * as React from 'react';
-import {
- DataGridPro,
- GridApi,
- getDataGridUtilityClass,
- GridColDef,
- DataGridProProps,
- GridEventListener,
- GridGroupingColDefOverride,
- GridRenderCellParams,
- GridRowModel,
- GridRowsProp,
- GridGroupNode,
- useGridApiContext,
- useGridApiRef,
- useGridRootProps,
- GridRowModelUpdate,
- GridRowIdGetter,
-} from '@mui/x-data-grid-pro';
-import { unstable_composeClasses as composeClasses, styled } from '@mui/material';
-import Box from '@mui/material/Box';
-import CircularProgress from '@mui/material/CircularProgress';
-import IconButton, { IconButtonProps } from '@mui/material/IconButton';
-
-export const isNavigationKey = (key: string) =>
- key === 'Home' ||
- key === 'End' ||
- key.indexOf('Arrow') === 0 ||
- key.indexOf('Page') === 0 ||
- key === ' ';
-
-interface Row {
- hierarchy: string[];
- jobTitle: string;
- recruitmentDate: Date;
- id: number;
- descendantCount?: number;
- childrenFetched?: boolean;
-}
-
-const ALL_ROWS: GridRowModel[] = [
- {
- hierarchy: ['Sarah'],
- jobTitle: 'Head of Human Resources',
- recruitmentDate: new Date(2020, 8, 12),
- id: 0,
- },
- {
- hierarchy: ['Thomas'],
- jobTitle: 'Head of Sales',
- recruitmentDate: new Date(2017, 3, 4),
- id: 1,
- },
- {
- hierarchy: ['Thomas', 'Robert'],
- jobTitle: 'Sales Person',
- recruitmentDate: new Date(2020, 11, 20),
- id: 2,
- },
- {
- hierarchy: ['Thomas', 'Karen'],
- jobTitle: 'Sales Person',
- recruitmentDate: new Date(2020, 10, 14),
- id: 3,
- },
- {
- hierarchy: ['Thomas', 'Nancy'],
- jobTitle: 'Sales Person',
- recruitmentDate: new Date(2017, 10, 29),
- id: 4,
- },
- {
- hierarchy: ['Thomas', 'Daniel'],
- jobTitle: 'Sales Person',
- recruitmentDate: new Date(2020, 7, 21),
- id: 5,
- },
- {
- hierarchy: ['Thomas', 'Christopher'],
- jobTitle: 'Sales Person',
- recruitmentDate: new Date(2020, 7, 20),
- id: 6,
- },
- {
- hierarchy: ['Thomas', 'Donald'],
- jobTitle: 'Sales Person',
- recruitmentDate: new Date(2019, 6, 28),
- id: 7,
- },
- {
- hierarchy: ['Mary'],
- jobTitle: 'Head of Engineering',
- recruitmentDate: new Date(2016, 3, 14),
- id: 8,
- },
- {
- hierarchy: ['Mary', 'Jennifer'],
- jobTitle: 'Tech lead front',
- recruitmentDate: new Date(2016, 5, 17),
- id: 9,
- },
- {
- hierarchy: ['Mary', 'Jennifer', 'Anna'],
- jobTitle: 'Front-end developer',
- recruitmentDate: new Date(2019, 11, 7),
- id: 10,
- },
- {
- hierarchy: ['Mary', 'Michael'],
- jobTitle: 'Tech lead devops',
- recruitmentDate: new Date(2021, 7, 1),
- id: 11,
- },
- {
- hierarchy: ['Mary', 'Linda'],
- jobTitle: 'Tech lead back',
- recruitmentDate: new Date(2017, 0, 12),
- id: 12,
- },
- {
- hierarchy: ['Mary', 'Linda', 'Elizabeth'],
- jobTitle: 'Back-end developer',
- recruitmentDate: new Date(2019, 2, 22),
- id: 13,
- },
- {
- hierarchy: ['Mary', 'Linda', 'William'],
- jobTitle: 'Back-end developer',
- recruitmentDate: new Date(2018, 4, 19),
- id: 14,
- },
-];
-
-const columns: GridColDef[] = [
- { field: 'jobTitle', headerName: 'Job Title', width: 200 },
- {
- field: 'recruitmentDate',
- headerName: 'Recruitment Date',
- type: 'date',
- width: 150,
- },
-];
-
-const getChildren = (parentPath: string[]) => {
- const parentPathStr = parentPath.join('-');
- return ALL_ROWS.filter(
- (row) => row.hierarchy.slice(0, -1).join('-') === parentPathStr,
- );
-};
-
-/**
- * This is a naive implementation with terrible performances on a real dataset.
- * This fake server is only here for demonstration purposes.
- */
-const fakeDataFetcher = (parentPath: string[] = []) =>
- new Promise[]>((resolve) => {
- setTimeout(
- () => {
- const rows = getChildren(parentPath).map((row) => ({
- ...row,
- descendantCount: getChildren(row.hierarchy).length,
- }));
- resolve(rows);
- },
- 500 + Math.random() * 300,
- );
- });
-
-const LoadingContainer = styled('div')({
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- height: '100%',
-});
-
-const getTreeDataPath: DataGridProProps['getTreeDataPath'] = (row) => row.hierarchy;
-
-const useUtilityClasses = (ownerState: { classes: DataGridProProps['classes'] }) => {
- const { classes } = ownerState;
-
- const slots = {
- root: ['treeDataGroupingCell'],
- toggle: ['treeDataGroupingCellToggle'],
- };
-
- return composeClasses(slots, getDataGridUtilityClass, classes);
-};
-
-interface GroupingCellWithLazyLoadingProps
- extends GridRenderCellParams {
- hideDescendantCount?: boolean;
-}
-
-/**
- * Reproduce the behavior of the `GridTreeDataGroupingCell` component in `@mui/x-data-grid-pro`
- * But base the amount of children on a `row.descendantCount` property rather than on the internal lookups.
- */
-function GroupingCellWithLazyLoading(props: GroupingCellWithLazyLoadingProps) {
- const { id, rowNode, row, hideDescendantCount, formattedValue } = props;
-
- const rootProps = useGridRootProps();
- const apiRef = useGridApiContext();
- const classes = useUtilityClasses({ classes: rootProps.classes });
-
- const isLoading = rowNode.childrenExpanded ? !row.childrenFetched : false;
-
- const Icon = rowNode.childrenExpanded
- ? rootProps.slots.treeDataCollapseIcon
- : rootProps.slots.treeDataExpandIcon;
-
- const handleClick: IconButtonProps['onClick'] = () => {
- apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded);
- };
-
- return (
-
-
- {row.descendantCount > 0 &&
- (isLoading ? (
-
-
-
- ) : (
-
-
-
- ))}
-
-
- {formattedValue === undefined ? rowNode.groupingKey : formattedValue}
- {!hideDescendantCount && row.descendantCount > 0
- ? ` (${row.descendantCount})`
- : ''}
-
-
- );
-}
-
-const CUSTOM_GROUPING_COL_DEF: GridGroupingColDefOverride = {
- renderCell: (params) => (
-
- ),
-};
-
-// Optional
-const getRowId: GridRowIdGetter = (row) => {
- if (typeof row?.id === 'string' && row?.id.startsWith('placeholder-children-')) {
- return row.id;
- }
- return row.id;
-};
-
-function updateRows(
- apiRef: React.MutableRefObject,
- rows: GridRowModelUpdate[],
-) {
- if (!apiRef.current) {
- return;
- }
- const rowsToAdd = [...rows];
- rows.forEach((row) => {
- if (row.descendantCount && row.descendantCount > 0) {
- // Add a placeholder row to make the row expandable
- rowsToAdd.push({
- id: `placeholder-children-${getRowId(row)}`,
- hierarchy: [...row.hierarchy, ''],
- });
- }
- });
- apiRef.current.updateRows(rowsToAdd);
-}
-
-const initialRows: GridRowsProp = [];
-
-export default function TreeDataLazyLoading() {
- const apiRef = useGridApiRef();
-
- React.useEffect(() => {
- fakeDataFetcher().then((rowsData) => {
- updateRows(apiRef, rowsData);
- });
-
- const handleRowExpansionChange: GridEventListener<'rowExpansionChange'> = async (
- node,
- ) => {
- const row = apiRef.current.getRow(node.id) as Row | null;
-
- if (!node.childrenExpanded || !row || row.childrenFetched) {
- return;
- }
-
- const childrenRows = await fakeDataFetcher(row.hierarchy);
- updateRows(apiRef, [
- ...childrenRows,
- { ...row, childrenFetched: true },
- { id: `placeholder-children-${node.id}`, _action: 'delete' },
- ]);
- };
-
- return apiRef.current.subscribeEvent(
- 'rowExpansionChange',
- handleRowExpansionChange,
- );
- }, [apiRef]);
-
- return (
-
-
-
- );
-}
diff --git a/docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx.preview b/docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx.preview
deleted file mode 100644
index 527bdff70663..000000000000
--- a/docs/data/data-grid/tree-data/TreeDataLazyLoading.tsx.preview
+++ /dev/null
@@ -1,10 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/data-grid/tree-data/tree-data.md b/docs/data/data-grid/tree-data/tree-data.md
index fe6fa401e005..3af238f688a3 100644
--- a/docs/data/data-grid/tree-data/tree-data.md
+++ b/docs/data/data-grid/tree-data/tree-data.md
@@ -126,23 +126,9 @@ const invalidRows = [{ path: ['A'] }, { path: ['B'] }, { path: ['A', 'A'] }];
:::
-## Children lazy-loading π§
+## Children lazy-loading
-:::warning
-This feature isn't implemented yet. It's coming.
-
-π Upvote [issue #3377](https://github.com/mui/mui-x/issues/3377) if you want to see it land faster.
-
-Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this component, or if you are facing a pain point with your current solution.
-:::
-
-Alternatively, you can achieve a similar behavior by implementing this feature outside the component as shown below.
-This implementation does not support every feature of the data grid but can be a good starting point for large datasets.
-
-The idea is to add a property `descendantCount` on the row and to use it instead of the internal grid state.
-To do so, you need to override both the `renderCell` of the grouping column and to manually open the rows by listening to `rowExpansionChange` event.
-
-{{"demo": "TreeDataLazyLoading.js", "bg": "inline", "defaultCodeOpen": false}}
+Check the [Server-side tree data](/x/react-data-grid/server-side-data/tree-data/) section for more information about lazy-loading tree data children.
## Full example
diff --git a/docs/data/date-pickers/adapters-locale/adapters-locale.md b/docs/data/date-pickers/adapters-locale/adapters-locale.md
index 6ab1f2944f39..26b4113d0f7b 100644
--- a/docs/data/date-pickers/adapters-locale/adapters-locale.md
+++ b/docs/data/date-pickers/adapters-locale/adapters-locale.md
@@ -79,6 +79,14 @@ import { AdapterLuxon } from '@mui/x-date-pickers/AdapterLuxon';
{{"demo": "LocalizationLuxon.js"}}
+:::warning
+`AdapterLuxon` does not support `Settings.throwOnInvalid = true` [setting](https://moment.github.io/luxon/api-docs/index.html#settingsthrowoninvalid).
+
+π Upvote [issue #11853](https://github.com/mui/mui-x/issues/11853) if you need support for it.
+
+Don't hesitate to leave feedback on how you would like the data entry to behave.
+:::
+
### With `moment`
For `moment`, import the locale and then pass its name to `LocalizationProvider`:
diff --git a/docs/data/date-pickers/custom-components/ActionBarComponent.js b/docs/data/date-pickers/custom-components/ActionBarComponent.js
index 8071aea4d7f6..4615954c25c1 100644
--- a/docs/data/date-pickers/custom-components/ActionBarComponent.js
+++ b/docs/data/date-pickers/custom-components/ActionBarComponent.js
@@ -9,11 +9,11 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { StaticDatePicker } from '@mui/x-date-pickers/StaticDatePicker';
-import { useLocaleText } from '@mui/x-date-pickers/internals';
+import { usePickersTranslations } from '@mui/x-date-pickers/hooks';
function CustomActionBar(props) {
const { onAccept, onClear, onCancel, onSetToday, actions, className } = props;
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const id = useId();
@@ -34,7 +34,7 @@ function CustomActionBar(props) {
}}
key={actionType}
>
- {localeText.clearButtonLabel}
+ {translations.clearButtonLabel}
);
@@ -47,7 +47,7 @@ function CustomActionBar(props) {
}}
key={actionType}
>
- {localeText.cancelButtonLabel}
+ {translations.cancelButtonLabel}
);
@@ -60,7 +60,7 @@ function CustomActionBar(props) {
}}
key={actionType}
>
- {localeText.okButtonLabel}
+ {translations.okButtonLabel}
);
@@ -74,7 +74,7 @@ function CustomActionBar(props) {
}}
key={actionType}
>
- {localeText.todayButtonLabel}
+ {translations.todayButtonLabel}
);
diff --git a/docs/data/date-pickers/custom-components/ActionBarComponent.tsx b/docs/data/date-pickers/custom-components/ActionBarComponent.tsx
index 7985a95c6dfd..7516085bb00e 100644
--- a/docs/data/date-pickers/custom-components/ActionBarComponent.tsx
+++ b/docs/data/date-pickers/custom-components/ActionBarComponent.tsx
@@ -9,11 +9,11 @@ import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { StaticDatePicker } from '@mui/x-date-pickers/StaticDatePicker';
import { PickersActionBarProps } from '@mui/x-date-pickers/PickersActionBar';
-import { useLocaleText } from '@mui/x-date-pickers/internals';
+import { usePickersTranslations } from '@mui/x-date-pickers/hooks';
function CustomActionBar(props: PickersActionBarProps) {
const { onAccept, onClear, onCancel, onSetToday, actions, className } = props;
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const id = useId();
@@ -34,7 +34,7 @@ function CustomActionBar(props: PickersActionBarProps) {
}}
key={actionType}
>
- {localeText.clearButtonLabel}
+ {translations.clearButtonLabel}
);
case 'cancel':
@@ -46,7 +46,7 @@ function CustomActionBar(props: PickersActionBarProps) {
}}
key={actionType}
>
- {localeText.cancelButtonLabel}
+ {translations.cancelButtonLabel}
);
case 'accept':
@@ -58,7 +58,7 @@ function CustomActionBar(props: PickersActionBarProps) {
}}
key={actionType}
>
- {localeText.okButtonLabel}
+ {translations.okButtonLabel}
);
case 'today':
@@ -71,7 +71,7 @@ function CustomActionBar(props: PickersActionBarProps) {
}}
key={actionType}
>
- {localeText.todayButtonLabel}
+ {translations.todayButtonLabel}
);
default:
diff --git a/docs/data/date-pickers/date-picker/CustomizationExamplesNoSnap.js b/docs/data/date-pickers/date-picker/CustomizationExamplesNoSnap.js
index 3b1a1f36bb4c..c61118d8584a 100644
--- a/docs/data/date-pickers/date-picker/CustomizationExamplesNoSnap.js
+++ b/docs/data/date-pickers/date-picker/CustomizationExamplesNoSnap.js
@@ -4,7 +4,7 @@ import ToggleButton from '@mui/material/ToggleButton';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import Stack from '@mui/material/Stack';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
-import BrandingProvider from 'docs/src/BrandingProvider';
+import { BrandingProvider } from '@mui/docs/branding';
import CustomizationPlayground from 'docsx/src/modules/components/CustomizationPlayground';
import CircularProgress from '@mui/material/CircularProgress';
import { pickerExamples } from './examplesConfig.styling';
diff --git a/docs/data/date-pickers/localization/UseLocaleText.tsx.preview b/docs/data/date-pickers/localization/UseLocaleText.tsx.preview
new file mode 100644
index 000000000000..395c5e40aaea
--- /dev/null
+++ b/docs/data/date-pickers/localization/UseLocaleText.tsx.preview
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/docs/data/date-pickers/localization/localization.md b/docs/data/date-pickers/localization/localization.md
index d461a3946516..0e0cbdcc2a61 100644
--- a/docs/data/date-pickers/localization/localization.md
+++ b/docs/data/date-pickers/localization/localization.md
@@ -131,3 +131,17 @@ You can [find the source](https://github.com/mui/mui-x/tree/HEAD/packages/x-date
To create your own translation or to customize the English text, copy this file to your project, make any changes needed and import the locale from there.
Note that these translations of the date and time picker components depend on the [Localization strategy](/material-ui/guides/localization/) of the whole library.
+
+## Access the translations in slots and subcomponents
+
+You can use the `usePickersTranslations` hook to access the translations in your custom components.
+
+```tsx
+import { usePickersTranslations } from '@mui/x-date-pickers/hooks';
+
+const translations = usePickersTranslations();
+```
+
+:::info
+See [Custom slots and subcomponentsβAction bar](/x/react-date-pickers/custom-components/#component) for more details.
+:::
diff --git a/docs/data/date-pickers/overview/overview.md b/docs/data/date-pickers/overview/overview.md
index ae699e363d1d..8c73477e1a0a 100644
--- a/docs/data/date-pickers/overview/overview.md
+++ b/docs/data/date-pickers/overview/overview.md
@@ -11,7 +11,7 @@ waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepick
These react date picker and time picker components let users select date or time values.
-{{"component": "modules/components/ComponentLinkHeader.js"}}
+{{"component": "@mui/docs/ComponentLinkHeader"}}
## Overview
diff --git a/docs/data/pages.ts b/docs/data/pages.ts
index 65822bf57988..12a8c186dfc8 100644
--- a/docs/data/pages.ts
+++ b/docs/data/pages.ts
@@ -87,6 +87,7 @@ const pages: MuiPage[] = [
},
{ pathname: '/x/react-data-grid/export' },
{ pathname: '/x/react-data-grid/clipboard', title: 'Copy and paste', newFeature: true },
+ { pathname: '/x/react-data-grid/overlays', title: 'Overlays' },
{ pathname: '/x/react-data-grid/components', title: 'Custom subcomponents' },
{
pathname: '/x/react-data-grid/style-group',
@@ -115,9 +116,10 @@ const pages: MuiPage[] = [
{
pathname: '/x/react-data-grid/server-side-data-group',
title: 'Server-side data',
- planned: true,
+ plan: 'pro',
children: [
- { pathname: '/x/react-data-grid/server-side-data', title: 'Overview', planned: true },
+ { pathname: '/x/react-data-grid/server-side-data', title: 'Overview' },
+ { pathname: '/x/react-data-grid/server-side-data/tree-data', plan: 'pro' },
{
pathname: '/x/react-data-grid/server-side-data/lazy-loading',
plan: 'pro',
@@ -128,7 +130,6 @@ const pages: MuiPage[] = [
plan: 'pro',
planned: true,
},
- { pathname: '/x/react-data-grid/server-side-data/tree-data', plan: 'pro', planned: true },
{
pathname: '/x/react-data-grid/server-side-data/row-grouping',
plan: 'pro',
@@ -423,6 +424,12 @@ const pages: MuiPage[] = [
title: 'Sparkline',
},
{ pathname: '/x/react-charts/gauge' },
+ {
+ pathname: '/x/react-charts/heatmap',
+ title: 'Heatmap',
+ plan: 'pro',
+ unstable: true,
+ },
{
pathname: '/x/react-charts/common-features',
subheader: 'Common features',
@@ -435,6 +442,7 @@ const pages: MuiPage[] = [
{ pathname: '/x/react-charts/stacking' },
{ pathname: '/x/react-charts/styling' },
{ pathname: '/x/react-charts/tooltip', title: 'Tooltip & Highlights' },
+ { pathname: '/x/react-charts/zoom-and-pan', title: 'Zoom & Pan', plan: 'pro' },
],
},
{
@@ -461,12 +469,6 @@ const pages: MuiPage[] = [
children: [
{ pathname: '/x/react-charts/radar', planned: true },
{ pathname: '/x/react-charts/treemap', title: 'Treemap', planned: true },
- {
- pathname: '/x/react-charts/heatmap',
- title: 'Heatmap',
- plan: 'pro',
- planned: true,
- },
{ pathname: '/x/react-charts/funnel', plan: 'pro', planned: true },
{ pathname: '/x/react-charts/sankey', plan: 'pro', planned: true },
{ pathname: '/x/react-charts/gantt', plan: 'pro', planned: true },
diff --git a/docs/data/tree-view/overview/overview.md b/docs/data/tree-view/overview/overview.md
index 9429bf8a1d30..64c374965652 100644
--- a/docs/data/tree-view/overview/overview.md
+++ b/docs/data/tree-view/overview/overview.md
@@ -10,7 +10,7 @@ packageName: '@mui/x-tree-view'
The Tree View component lets users navigate hierarchical lists of data with nested levels that can be expanded and collapsed.
-{{"component": "modules/components/ComponentLinkHeader.js"}}
+{{"component": "@mui/docs/ComponentLinkHeader"}}
## Available components
diff --git a/docs/data/tree-view/rich-tree-view/customization/IconExpansionTreeView.tsx.preview b/docs/data/tree-view/rich-tree-view/customization/IconExpansionTreeView.tsx.preview
deleted file mode 100644
index 660542f7bf2c..000000000000
--- a/docs/data/tree-view/rich-tree-view/customization/IconExpansionTreeView.tsx.preview
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/data/tree-view/rich-tree-view/customization/customization.md b/docs/data/tree-view/rich-tree-view/customization/customization.md
index 8f1552b6f0df..4bedda31d764 100644
--- a/docs/data/tree-view/rich-tree-view/customization/customization.md
+++ b/docs/data/tree-view/rich-tree-view/customization/customization.md
@@ -64,12 +64,6 @@ The demo below shows how to add an avatar and custom typography elements.
## Common examples
-### Limit expansion to icon container
-
-The demo below shows how to trigger the expansion interaction just by clicking on the icon container instead of the whole Tree Item surface.
-
-{{"demo": "IconExpansionTreeView.js", "defaultCodeOpen": false}}
-
### File explorer
:::warning
diff --git a/docs/data/tree-view/rich-tree-view/customization/IconExpansionTreeView.js b/docs/data/tree-view/rich-tree-view/expansion/IconExpansionTreeView.js
similarity index 55%
rename from docs/data/tree-view/rich-tree-view/customization/IconExpansionTreeView.js
rename to docs/data/tree-view/rich-tree-view/expansion/IconExpansionTreeView.js
index fa2935769a63..db2d29f0fcfa 100644
--- a/docs/data/tree-view/rich-tree-view/customization/IconExpansionTreeView.js
+++ b/docs/data/tree-view/rich-tree-view/expansion/IconExpansionTreeView.js
@@ -1,9 +1,6 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
-import { useTreeItem2Utils } from '@mui/x-tree-view/hooks';
-
-import { TreeItem2 } from '@mui/x-tree-view/TreeItem2';
const MUI_X_PRODUCTS = [
{
@@ -35,37 +32,10 @@ const MUI_X_PRODUCTS = [
},
];
-const CustomTreeItem = React.forwardRef(function MyTreeItem(props, ref) {
- const { interactions } = useTreeItem2Utils({
- itemId: props.itemId,
- children: props.children,
- });
-
- const handleContentClick = (event) => {
- event.defaultMuiPrevented = true;
- interactions.handleSelection(event);
- };
-
- const handleIconContainerClick = (event) => {
- interactions.handleExpansion(event);
- };
-
- return (
-
- );
-});
-
export default function IconExpansionTreeView() {
return (
-
+
);
}
diff --git a/docs/data/tree-view/rich-tree-view/customization/IconExpansionTreeView.tsx b/docs/data/tree-view/rich-tree-view/expansion/IconExpansionTreeView.tsx
similarity index 51%
rename from docs/data/tree-view/rich-tree-view/customization/IconExpansionTreeView.tsx
rename to docs/data/tree-view/rich-tree-view/expansion/IconExpansionTreeView.tsx
index 0d7345a1047d..94b9816b9a89 100644
--- a/docs/data/tree-view/rich-tree-view/customization/IconExpansionTreeView.tsx
+++ b/docs/data/tree-view/rich-tree-view/expansion/IconExpansionTreeView.tsx
@@ -1,9 +1,6 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
-import { useTreeItem2Utils } from '@mui/x-tree-view/hooks';
-import { UseTreeItem2ContentSlotOwnProps } from '@mui/x-tree-view/useTreeItem2';
-import { TreeItem2, TreeItem2Props } from '@mui/x-tree-view/TreeItem2';
import { TreeViewBaseItem } from '@mui/x-tree-view/models';
const MUI_X_PRODUCTS: TreeViewBaseItem[] = [
@@ -36,40 +33,10 @@ const MUI_X_PRODUCTS: TreeViewBaseItem[] = [
},
];
-const CustomTreeItem = React.forwardRef(function MyTreeItem(
- props: TreeItem2Props,
- ref: React.Ref,
-) {
- const { interactions } = useTreeItem2Utils({
- itemId: props.itemId,
- children: props.children,
- });
-
- const handleContentClick: UseTreeItem2ContentSlotOwnProps['onClick'] = (event) => {
- event.defaultMuiPrevented = true;
- interactions.handleSelection(event);
- };
-
- const handleIconContainerClick = (event: React.MouseEvent) => {
- interactions.handleExpansion(event);
- };
-
- return (
-
- );
-});
-
export default function IconExpansionTreeView() {
return (
-
+
);
}
diff --git a/docs/data/tree-view/rich-tree-view/expansion/IconExpansionTreeView.tsx.preview b/docs/data/tree-view/rich-tree-view/expansion/IconExpansionTreeView.tsx.preview
new file mode 100644
index 000000000000..49f8c4e18162
--- /dev/null
+++ b/docs/data/tree-view/rich-tree-view/expansion/IconExpansionTreeView.tsx.preview
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/data/tree-view/rich-tree-view/expansion/expansion.md b/docs/data/tree-view/rich-tree-view/expansion/expansion.md
index e36fe65d20c1..84bcb34b503b 100644
--- a/docs/data/tree-view/rich-tree-view/expansion/expansion.md
+++ b/docs/data/tree-view/rich-tree-view/expansion/expansion.md
@@ -33,6 +33,12 @@ Use the `onItemExpansionToggle` prop if you want to react to an item expansion c
{{"demo": "TrackItemExpansionToggle.js"}}
+## Limit expansion to icon container
+
+You can use the `expansionTrigger` prop to decide if the expansion interaction should be triggered by clicking on the icon container instead of the whole Tree Item content.
+
+{{"demo": "IconExpansionTreeView.js"}}
+
## Imperative API
:::success
@@ -56,9 +62,10 @@ Use the `setItemExpansion` API method to change the expansion of an item.
apiRef.current.setItemExpansion(
// The DOM event that triggered the change
event,
- // The ID of the item to expand or collapse
+ // The id of the item to expand or collapse
itemId,
- // `true` if the item should be expanded, `false` if it should be collapsed
+ // If `true` the item will be expanded
+ // If `false` the item will be collapsed
isExpanded,
);
```
diff --git a/docs/data/tree-view/rich-tree-view/focus/focus.md b/docs/data/tree-view/rich-tree-view/focus/focus.md
index 7451953dac48..a293e75b0d20 100644
--- a/docs/data/tree-view/rich-tree-view/focus/focus.md
+++ b/docs/data/tree-view/rich-tree-view/focus/focus.md
@@ -34,7 +34,7 @@ Use the `focusItem` API method to focus a specific item.
apiRef.current.focusItem(
// The DOM event that triggered the change
event,
- // The ID of the item to focus
+ // The id of the item to focus
itemId,
);
```
diff --git a/docs/data/tree-view/rich-tree-view/items/ApiMethodGetItemDOMElement.js b/docs/data/tree-view/rich-tree-view/items/ApiMethodGetItemDOMElement.js
new file mode 100644
index 000000000000..560e4412ee84
--- /dev/null
+++ b/docs/data/tree-view/rich-tree-view/items/ApiMethodGetItemDOMElement.js
@@ -0,0 +1,64 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
+
+import { useTreeViewApiRef } from '@mui/x-tree-view/hooks/useTreeViewApiRef';
+
+const MUI_X_PRODUCTS = [
+ {
+ id: 'grid',
+ label: 'Data Grid',
+ children: [
+ { id: 'grid-community', label: '@mui/x-data-grid' },
+ { id: 'grid-pro', label: '@mui/x-data-grid-pro' },
+ { id: 'grid-premium', label: '@mui/x-data-grid-premium' },
+ ],
+ },
+ {
+ id: 'pickers',
+ label: 'Date and Time Pickers',
+ children: [
+ { id: 'pickers-community', label: '@mui/x-date-pickers' },
+ { id: 'pickers-pro', label: '@mui/x-date-pickers-pro' },
+ ],
+ },
+ {
+ id: 'charts',
+ label: 'Charts',
+ children: [{ id: 'charts-community', label: '@mui/x-charts' }],
+ },
+ {
+ id: 'tree-view',
+ label: 'Tree View',
+ children: [{ id: 'tree-view-community', label: '@mui/x-tree-view' }],
+ },
+];
+
+export default function ApiMethodGetItemDOMElement() {
+ const apiRef = useTreeViewApiRef();
+ const handleScrollToChartsCommunity = (event) => {
+ apiRef.current.focusItem(event, 'charts-community');
+ apiRef.current
+ .getItemDOMElement('charts-community')
+ ?.scrollIntoView({ block: 'center' });
+ };
+
+ return (
+
+
+
+ Focus and scroll to charts community item
+
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/rich-tree-view/items/ApiMethodGetItemDOMElement.tsx b/docs/data/tree-view/rich-tree-view/items/ApiMethodGetItemDOMElement.tsx
new file mode 100644
index 000000000000..caec1f25a7f2
--- /dev/null
+++ b/docs/data/tree-view/rich-tree-view/items/ApiMethodGetItemDOMElement.tsx
@@ -0,0 +1,64 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
+import { TreeViewBaseItem } from '@mui/x-tree-view/models';
+import { useTreeViewApiRef } from '@mui/x-tree-view/hooks/useTreeViewApiRef';
+
+const MUI_X_PRODUCTS: TreeViewBaseItem[] = [
+ {
+ id: 'grid',
+ label: 'Data Grid',
+ children: [
+ { id: 'grid-community', label: '@mui/x-data-grid' },
+ { id: 'grid-pro', label: '@mui/x-data-grid-pro' },
+ { id: 'grid-premium', label: '@mui/x-data-grid-premium' },
+ ],
+ },
+ {
+ id: 'pickers',
+ label: 'Date and Time Pickers',
+ children: [
+ { id: 'pickers-community', label: '@mui/x-date-pickers' },
+ { id: 'pickers-pro', label: '@mui/x-date-pickers-pro' },
+ ],
+ },
+ {
+ id: 'charts',
+ label: 'Charts',
+ children: [{ id: 'charts-community', label: '@mui/x-charts' }],
+ },
+ {
+ id: 'tree-view',
+ label: 'Tree View',
+ children: [{ id: 'tree-view-community', label: '@mui/x-tree-view' }],
+ },
+];
+
+export default function ApiMethodGetItemDOMElement() {
+ const apiRef = useTreeViewApiRef();
+ const handleScrollToChartsCommunity = (event: React.SyntheticEvent) => {
+ apiRef.current!.focusItem(event, 'charts-community');
+ apiRef
+ .current!.getItemDOMElement('charts-community')
+ ?.scrollIntoView({ block: 'center' });
+ };
+
+ return (
+
+
+
+ Focus and scroll to charts community item
+
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/rich-tree-view/items/ApiMethodGetItemDOMElement.tsx.preview b/docs/data/tree-view/rich-tree-view/items/ApiMethodGetItemDOMElement.tsx.preview
new file mode 100644
index 000000000000..96b7480941c8
--- /dev/null
+++ b/docs/data/tree-view/rich-tree-view/items/ApiMethodGetItemDOMElement.tsx.preview
@@ -0,0 +1,12 @@
+
+
+ Focus and scroll to charts community item
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/data/tree-view/rich-tree-view/items/items.md b/docs/data/tree-view/rich-tree-view/items/items.md
index 9370571c23e3..28966a869e08 100644
--- a/docs/data/tree-view/rich-tree-view/items/items.md
+++ b/docs/data/tree-view/rich-tree-view/items/items.md
@@ -151,9 +151,22 @@ Use the `getItem` API method to get an item by its ID.
```ts
const item = apiRef.current.getItem(
- // The ID of the item to retrieve
+ // The id of the item to retrieve
itemId,
);
```
{{"demo": "ApiMethodGetItem.js", "defaultCodeOpen": false}}
+
+### Get an item's DOM element by ID
+
+Use the `getItemDOMElement` API method to get an item's DOM element by its ID.
+
+```ts
+const itemElement = apiRef.current.getItemDOMElement(
+ // The id of the item to get the DOM element of
+ itemId,
+);
+```
+
+{{"demo": "ApiMethodGetItemDOMElement.js", "defaultCodeOpen": false}}
diff --git a/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItem.js b/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItem.js
new file mode 100644
index 000000000000..30d2f2d6ed3a
--- /dev/null
+++ b/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItem.js
@@ -0,0 +1,59 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+
+import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
+import { useTreeViewApiRef } from '@mui/x-tree-view/hooks/useTreeViewApiRef';
+
+const MUI_X_PRODUCTS = [
+ {
+ id: 'grid',
+ label: 'Data Grid',
+ children: [
+ { id: 'grid-community', label: '@mui/x-data-grid' },
+ { id: 'grid-pro', label: '@mui/x-data-grid-pro' },
+ { id: 'grid-premium', label: '@mui/x-data-grid-premium' },
+ ],
+ },
+ {
+ id: 'pickers',
+ label: 'Date and Time Pickers',
+ children: [
+ { id: 'pickers-community', label: '@mui/x-date-pickers' },
+ { id: 'pickers-pro', label: '@mui/x-date-pickers-pro' },
+ ],
+ },
+ {
+ id: 'charts',
+ label: 'Charts',
+ children: [{ id: 'charts-community', label: '@mui/x-charts' }],
+ },
+ {
+ id: 'tree-view',
+ label: 'Tree View',
+ children: [{ id: 'tree-view-community', label: '@mui/x-tree-view' }],
+ },
+];
+
+export default function ApiMethodSelectItem() {
+ const apiRef = useTreeViewApiRef();
+ const handleSelectGridPro = (event) => {
+ apiRef.current?.selectItem({ event, itemId: 'grid-pro' });
+ };
+
+ return (
+
+
+ Select grid pro item
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItem.tsx b/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItem.tsx
new file mode 100644
index 000000000000..c8c0ee7bfa33
--- /dev/null
+++ b/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItem.tsx
@@ -0,0 +1,59 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import { TreeViewBaseItem } from '@mui/x-tree-view/models';
+import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
+import { useTreeViewApiRef } from '@mui/x-tree-view/hooks/useTreeViewApiRef';
+
+const MUI_X_PRODUCTS: TreeViewBaseItem[] = [
+ {
+ id: 'grid',
+ label: 'Data Grid',
+ children: [
+ { id: 'grid-community', label: '@mui/x-data-grid' },
+ { id: 'grid-pro', label: '@mui/x-data-grid-pro' },
+ { id: 'grid-premium', label: '@mui/x-data-grid-premium' },
+ ],
+ },
+ {
+ id: 'pickers',
+ label: 'Date and Time Pickers',
+ children: [
+ { id: 'pickers-community', label: '@mui/x-date-pickers' },
+ { id: 'pickers-pro', label: '@mui/x-date-pickers-pro' },
+ ],
+ },
+ {
+ id: 'charts',
+ label: 'Charts',
+ children: [{ id: 'charts-community', label: '@mui/x-charts' }],
+ },
+ {
+ id: 'tree-view',
+ label: 'Tree View',
+ children: [{ id: 'tree-view-community', label: '@mui/x-tree-view' }],
+ },
+];
+
+export default function ApiMethodSelectItem() {
+ const apiRef = useTreeViewApiRef();
+ const handleSelectGridPro = (event: React.SyntheticEvent) => {
+ apiRef.current?.selectItem({ event, itemId: 'grid-pro' });
+ };
+
+ return (
+
+
+ Select grid pro item
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItem.tsx.preview b/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItem.tsx.preview
new file mode 100644
index 000000000000..00d30b33d158
--- /dev/null
+++ b/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItem.tsx.preview
@@ -0,0 +1,10 @@
+
+ Select grid pro item
+
+
+
+
\ No newline at end of file
diff --git a/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.js b/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.js
new file mode 100644
index 000000000000..8d6da938ae63
--- /dev/null
+++ b/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.js
@@ -0,0 +1,65 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+
+import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
+import { useTreeViewApiRef } from '@mui/x-tree-view/hooks/useTreeViewApiRef';
+
+const MUI_X_PRODUCTS = [
+ {
+ id: 'grid',
+ label: 'Data Grid',
+ children: [
+ { id: 'grid-community', label: '@mui/x-data-grid' },
+ { id: 'grid-pro', label: '@mui/x-data-grid-pro' },
+ { id: 'grid-premium', label: '@mui/x-data-grid-premium' },
+ ],
+ },
+ {
+ id: 'pickers',
+ label: 'Date and Time Pickers',
+ children: [
+ { id: 'pickers-community', label: '@mui/x-date-pickers' },
+ { id: 'pickers-pro', label: '@mui/x-date-pickers-pro' },
+ ],
+ },
+ {
+ id: 'charts',
+ label: 'Charts',
+ children: [{ id: 'charts-community', label: '@mui/x-charts' }],
+ },
+ {
+ id: 'tree-view',
+ label: 'Tree View',
+ children: [{ id: 'tree-view-community', label: '@mui/x-tree-view' }],
+ },
+];
+
+export default function ApiMethodSelectItemKeepExistingSelection() {
+ const apiRef = useTreeViewApiRef();
+ const handleSelectGridPro = (event) => {
+ apiRef.current?.selectItem({
+ event,
+ itemId: 'grid-pro',
+ keepExistingSelection: true,
+ });
+ };
+
+ return (
+
+
+ Select grid pro item
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.tsx b/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.tsx
new file mode 100644
index 000000000000..d9c562924547
--- /dev/null
+++ b/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.tsx
@@ -0,0 +1,65 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import { TreeViewBaseItem } from '@mui/x-tree-view/models';
+import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
+import { useTreeViewApiRef } from '@mui/x-tree-view/hooks/useTreeViewApiRef';
+
+const MUI_X_PRODUCTS: TreeViewBaseItem[] = [
+ {
+ id: 'grid',
+ label: 'Data Grid',
+ children: [
+ { id: 'grid-community', label: '@mui/x-data-grid' },
+ { id: 'grid-pro', label: '@mui/x-data-grid-pro' },
+ { id: 'grid-premium', label: '@mui/x-data-grid-premium' },
+ ],
+ },
+ {
+ id: 'pickers',
+ label: 'Date and Time Pickers',
+ children: [
+ { id: 'pickers-community', label: '@mui/x-date-pickers' },
+ { id: 'pickers-pro', label: '@mui/x-date-pickers-pro' },
+ ],
+ },
+ {
+ id: 'charts',
+ label: 'Charts',
+ children: [{ id: 'charts-community', label: '@mui/x-charts' }],
+ },
+ {
+ id: 'tree-view',
+ label: 'Tree View',
+ children: [{ id: 'tree-view-community', label: '@mui/x-tree-view' }],
+ },
+];
+
+export default function ApiMethodSelectItemKeepExistingSelection() {
+ const apiRef = useTreeViewApiRef();
+ const handleSelectGridPro = (event: React.SyntheticEvent) => {
+ apiRef.current?.selectItem({
+ event,
+ itemId: 'grid-pro',
+ keepExistingSelection: true,
+ });
+ };
+
+ return (
+
+
+ Select grid pro item
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.tsx.preview b/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.tsx.preview
new file mode 100644
index 000000000000..3d14770db217
--- /dev/null
+++ b/docs/data/tree-view/rich-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.tsx.preview
@@ -0,0 +1,12 @@
+
+ Select grid pro item
+
+
+
+
\ No newline at end of file
diff --git a/docs/data/tree-view/rich-tree-view/selection/selection.md b/docs/data/tree-view/rich-tree-view/selection/selection.md
index 2fb893baee65..2c45bd280411 100644
--- a/docs/data/tree-view/rich-tree-view/selection/selection.md
+++ b/docs/data/tree-view/rich-tree-view/selection/selection.md
@@ -94,3 +94,45 @@ you can create your own custom solution using the `selectedItems`,
`onSelectedItemsChange` and `onItemSelectionToggle` props:
{{"demo": "ParentChildrenSelectionRelationship.js"}}
+
+## Imperative API
+
+:::success
+To use the `apiRef` object, you need to initialize it using the `useTreeViewApiRef` hook as follows:
+
+```tsx
+const apiRef = useTreeViewApiRef();
+
+return {children} ;
+```
+
+When your component first renders, `apiRef` will be `undefined`.
+After this initial render, `apiRef` holds methods to interact imperatively with the Tree View.
+:::
+
+### Select or deselect an item
+
+Use the `selectItem` API method to select or deselect an item:
+
+```ts
+apiRef.current.selectItem({
+ // The DOM event that triggered the change
+ event,
+ // The id of the item to select or deselect
+ itemId,
+ // If `true`, the other already selected items will remain selected
+ // Otherwise, they will be deselected
+ // This parameter is only relevant when `multiSelect` is `true`
+ keepExistingSelection,
+ // If `true` the item will be selected
+ // If `false` the item will be deselected
+ // If not defined, the item's new selection status will be the opposite of its current one
+ shouldBeSelected,
+});
+```
+
+{{"demo": "ApiMethodSelectItem.js", "defaultCodeOpen": false}}
+
+You can use the `keepExistingSelection` property to avoid losing the already selected items when using `multiSelect`:
+
+{{"demo": "ApiMethodSelectItemKeepExistingSelection.js", "defaultCodeOpen": false}}
diff --git a/docs/data/tree-view/simple-tree-view/customization/IconExpansionTreeView.js b/docs/data/tree-view/simple-tree-view/customization/IconExpansionTreeView.js
deleted file mode 100644
index 36e9e200da11..000000000000
--- a/docs/data/tree-view/simple-tree-view/customization/IconExpansionTreeView.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import * as React from 'react';
-import Box from '@mui/material/Box';
-import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
-import { useTreeItem2Utils } from '@mui/x-tree-view/hooks';
-
-import { TreeItem2 } from '@mui/x-tree-view/TreeItem2';
-
-const CustomTreeItem = React.forwardRef(function MyTreeItem(props, ref) {
- const { interactions } = useTreeItem2Utils({
- itemId: props.itemId,
- children: props.children,
- });
-
- const handleContentClick = (event) => {
- event.defaultMuiPrevented = true;
- interactions.handleSelection(event);
- };
-
- const handleIconContainerClick = (event) => {
- interactions.handleExpansion(event);
- };
-
- return (
-
- );
-});
-
-export default function IconExpansionTreeView() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/docs/data/tree-view/simple-tree-view/customization/IconExpansionTreeView.tsx b/docs/data/tree-view/simple-tree-view/customization/IconExpansionTreeView.tsx
deleted file mode 100644
index f0a5de292e60..000000000000
--- a/docs/data/tree-view/simple-tree-view/customization/IconExpansionTreeView.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import * as React from 'react';
-import Box from '@mui/material/Box';
-import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
-import { useTreeItem2Utils } from '@mui/x-tree-view/hooks';
-import { UseTreeItem2ContentSlotOwnProps } from '@mui/x-tree-view/useTreeItem2';
-import { TreeItem2, TreeItem2Props } from '@mui/x-tree-view/TreeItem2';
-
-const CustomTreeItem = React.forwardRef(function MyTreeItem(
- props: TreeItem2Props,
- ref: React.Ref,
-) {
- const { interactions } = useTreeItem2Utils({
- itemId: props.itemId,
- children: props.children,
- });
-
- const handleContentClick: UseTreeItem2ContentSlotOwnProps['onClick'] = (event) => {
- event.defaultMuiPrevented = true;
- interactions.handleSelection(event);
- };
-
- const handleIconContainerClick = (event: React.MouseEvent) => {
- interactions.handleExpansion(event);
- };
-
- return (
-
- );
-});
-
-export default function IconExpansionTreeView() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/docs/data/tree-view/simple-tree-view/customization/customization.md b/docs/data/tree-view/simple-tree-view/customization/customization.md
index e5e70b9e4aa8..7cf009836436 100644
--- a/docs/data/tree-view/simple-tree-view/customization/customization.md
+++ b/docs/data/tree-view/simple-tree-view/customization/customization.md
@@ -70,19 +70,6 @@ Target the `treeItemClasses.groupTransition` class to add connection borders bet
{{"demo": "BorderedTreeView.js", "defaultCodeOpen": false}}
-### Limit expansion to icon container
-
-:::warning
-This example is built using the new `TreeItem2` component
-which adds several slots to modify the content of the Tree Item or change its behavior.
-
-You can learn more about this new component in the [Overview page](/x/react-tree-view/#tree-item-components).
-:::
-
-The demo below shows how to trigger the expansion interaction just by clicking on the icon container instead of the whole Tree Item surface.
-
-{{"demo": "IconExpansionTreeView.js", "defaultCodeOpen": false}}
-
### Gmail clone
:::warning
diff --git a/docs/data/tree-view/simple-tree-view/expansion/IconExpansionTreeView.js b/docs/data/tree-view/simple-tree-view/expansion/IconExpansionTreeView.js
new file mode 100644
index 000000000000..3a046031d357
--- /dev/null
+++ b/docs/data/tree-view/simple-tree-view/expansion/IconExpansionTreeView.js
@@ -0,0 +1,28 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
+import { TreeItem } from '@mui/x-tree-view/TreeItem';
+
+export default function IconExpansionTreeView() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/simple-tree-view/expansion/IconExpansionTreeView.tsx b/docs/data/tree-view/simple-tree-view/expansion/IconExpansionTreeView.tsx
new file mode 100644
index 000000000000..3a046031d357
--- /dev/null
+++ b/docs/data/tree-view/simple-tree-view/expansion/IconExpansionTreeView.tsx
@@ -0,0 +1,28 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
+import { TreeItem } from '@mui/x-tree-view/TreeItem';
+
+export default function IconExpansionTreeView() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/simple-tree-view/expansion/expansion.md b/docs/data/tree-view/simple-tree-view/expansion/expansion.md
index a6b0a3f4e46a..68a069a40243 100644
--- a/docs/data/tree-view/simple-tree-view/expansion/expansion.md
+++ b/docs/data/tree-view/simple-tree-view/expansion/expansion.md
@@ -32,6 +32,12 @@ Use the `onItemExpansionToggle` prop to trigger an action upon an item being exp
{{"demo": "TrackItemExpansionToggle.js"}}
+## Limit expansion to icon container
+
+You can use the `expansionTrigger` prop to decide if the expansion interaction should be triggered by clicking on the icon container instead of the whole Tree Item content.
+
+{{"demo": "IconExpansionTreeView.js"}}
+
## Imperative API
:::success
@@ -55,9 +61,10 @@ Use the `setItemExpansion` API method to change the expansion of an item.
apiRef.current.setItemExpansion(
// The DOM event that triggered the change
event,
- // The ID of the item to expand or collapse
+ // The id of the item to expand or collapse
itemId,
- // `true` if the item should be expanded, `false` if it should be collapsed
+ // If `true` the item will be expanded
+ // If `false` the item will be collapsed
isExpanded,
);
```
diff --git a/docs/data/tree-view/simple-tree-view/focus/focus.md b/docs/data/tree-view/simple-tree-view/focus/focus.md
index d1596614823c..ef9cd7d5558e 100644
--- a/docs/data/tree-view/simple-tree-view/focus/focus.md
+++ b/docs/data/tree-view/simple-tree-view/focus/focus.md
@@ -34,7 +34,7 @@ Use the `focusItem` API method to focus a specific item.
apiRef.current.focusItem(
// The DOM event that triggered the change
event,
- // The ID of the item to focus
+ // The id of the item to focus
itemId,
);
```
diff --git a/docs/data/tree-view/simple-tree-view/items/ApiMethodGetItemDOMElement.js b/docs/data/tree-view/simple-tree-view/items/ApiMethodGetItemDOMElement.js
new file mode 100644
index 000000000000..74e7dd11ca46
--- /dev/null
+++ b/docs/data/tree-view/simple-tree-view/items/ApiMethodGetItemDOMElement.js
@@ -0,0 +1,49 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
+import { TreeItem } from '@mui/x-tree-view/TreeItem';
+import { useTreeViewApiRef } from '@mui/x-tree-view/hooks/useTreeViewApiRef';
+
+export default function ApiMethodGetItemDOMElement() {
+ const apiRef = useTreeViewApiRef();
+ const handleScrollToChartsCommunity = (event) => {
+ apiRef.current.focusItem(event, 'charts-community');
+ apiRef.current
+ .getItemDOMElement('charts-community')
+ ?.scrollIntoView({ block: 'center' });
+ };
+
+ return (
+
+
+
+ Focus and scroll to charts community item
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/simple-tree-view/items/ApiMethodGetItemDOMElement.tsx b/docs/data/tree-view/simple-tree-view/items/ApiMethodGetItemDOMElement.tsx
new file mode 100644
index 000000000000..5d0e9d77ee8a
--- /dev/null
+++ b/docs/data/tree-view/simple-tree-view/items/ApiMethodGetItemDOMElement.tsx
@@ -0,0 +1,49 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
+import { TreeItem } from '@mui/x-tree-view/TreeItem';
+import { useTreeViewApiRef } from '@mui/x-tree-view/hooks/useTreeViewApiRef';
+
+export default function ApiMethodGetItemDOMElement() {
+ const apiRef = useTreeViewApiRef();
+ const handleScrollToChartsCommunity = (event: React.SyntheticEvent) => {
+ apiRef.current!.focusItem(event, 'charts-community');
+ apiRef
+ .current!.getItemDOMElement('charts-community')
+ ?.scrollIntoView({ block: 'center' });
+ };
+
+ return (
+
+
+
+ Focus and scroll to charts community item
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/simple-tree-view/items/items.md b/docs/data/tree-view/simple-tree-view/items/items.md
index a35ce2f3119c..efffddcf5574 100644
--- a/docs/data/tree-view/simple-tree-view/items/items.md
+++ b/docs/data/tree-view/simple-tree-view/items/items.md
@@ -73,3 +73,18 @@ When it's set to true:
- Programmatic focus will focus disabled items.
{{"demo": "DisabledItemsFocusable.js", "defaultCodeOpen": false}}
+
+## Imperative API
+
+### Get an item's DOM element by ID
+
+Use the `getItemDOMElement` API method to get an item's DOM element by its ID.
+
+```ts
+const itemElement = apiRef.current.getItemDOMElement(
+ // The id of the item to get the DOM element of
+ itemId,
+);
+```
+
+{{"demo": "ApiMethodGetItemDOMElement.js", "defaultCodeOpen": false}}
diff --git a/docs/data/tree-view/simple-tree-view/selection/ApiMethodSelectItem.js b/docs/data/tree-view/simple-tree-view/selection/ApiMethodSelectItem.js
new file mode 100644
index 000000000000..41a9034a616c
--- /dev/null
+++ b/docs/data/tree-view/simple-tree-view/selection/ApiMethodSelectItem.js
@@ -0,0 +1,41 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
+import { TreeItem } from '@mui/x-tree-view/TreeItem';
+import { useTreeViewApiRef } from '@mui/x-tree-view/hooks/useTreeViewApiRef';
+
+export default function ApiMethodSelectItem() {
+ const apiRef = useTreeViewApiRef();
+ const handleSelectGridPro = (event) => {
+ apiRef.current?.selectItem({ event, itemId: 'grid-pro' });
+ };
+
+ return (
+
+
+ Select grid pro item
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/simple-tree-view/selection/ApiMethodSelectItem.tsx b/docs/data/tree-view/simple-tree-view/selection/ApiMethodSelectItem.tsx
new file mode 100644
index 000000000000..24bfef05a393
--- /dev/null
+++ b/docs/data/tree-view/simple-tree-view/selection/ApiMethodSelectItem.tsx
@@ -0,0 +1,41 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
+import { TreeItem } from '@mui/x-tree-view/TreeItem';
+import { useTreeViewApiRef } from '@mui/x-tree-view/hooks/useTreeViewApiRef';
+
+export default function ApiMethodSelectItem() {
+ const apiRef = useTreeViewApiRef();
+ const handleSelectGridPro = (event: React.SyntheticEvent) => {
+ apiRef.current?.selectItem({ event, itemId: 'grid-pro' });
+ };
+
+ return (
+
+
+ Select grid pro item
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/simple-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.js b/docs/data/tree-view/simple-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.js
new file mode 100644
index 000000000000..8a643361ba29
--- /dev/null
+++ b/docs/data/tree-view/simple-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.js
@@ -0,0 +1,50 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
+import { TreeItem } from '@mui/x-tree-view/TreeItem';
+import { useTreeViewApiRef } from '@mui/x-tree-view/hooks/useTreeViewApiRef';
+
+export default function ApiMethodSelectItemKeepExistingSelection() {
+ const apiRef = useTreeViewApiRef();
+ const handleSelectGridPro = (event) => {
+ apiRef.current?.selectItem({
+ event,
+ itemId: 'grid-pro',
+ keepExistingSelection: true,
+ });
+ };
+
+ return (
+
+
+ Select grid pro item
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/simple-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.tsx b/docs/data/tree-view/simple-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.tsx
new file mode 100644
index 000000000000..df3a6ae13aea
--- /dev/null
+++ b/docs/data/tree-view/simple-tree-view/selection/ApiMethodSelectItemKeepExistingSelection.tsx
@@ -0,0 +1,50 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Stack from '@mui/material/Stack';
+import Button from '@mui/material/Button';
+import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
+import { TreeItem } from '@mui/x-tree-view/TreeItem';
+import { useTreeViewApiRef } from '@mui/x-tree-view/hooks/useTreeViewApiRef';
+
+export default function ApiMethodSelectItemKeepExistingSelection() {
+ const apiRef = useTreeViewApiRef();
+ const handleSelectGridPro = (event: React.SyntheticEvent) => {
+ apiRef.current?.selectItem({
+ event,
+ itemId: 'grid-pro',
+ keepExistingSelection: true,
+ });
+ };
+
+ return (
+
+
+ Select grid pro item
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/docs/data/tree-view/simple-tree-view/selection/selection.md b/docs/data/tree-view/simple-tree-view/selection/selection.md
index 434a4284a0a2..aceb02cb7215 100644
--- a/docs/data/tree-view/simple-tree-view/selection/selection.md
+++ b/docs/data/tree-view/simple-tree-view/selection/selection.md
@@ -73,3 +73,45 @@ Learn more about the _Controlled and uncontrolled_ pattern in the [React documen
Use the `onItemSelectionToggle` prop if you want to react to an item selection change:
{{"demo": "TrackItemSelectionToggle.js"}}
+
+## Imperative API
+
+:::success
+To use the `apiRef` object, you need to initialize it using the `useTreeViewApiRef` hook as follows:
+
+```tsx
+const apiRef = useTreeViewApiRef();
+
+return {children} ;
+```
+
+When your component first renders, `apiRef` will be `undefined`.
+After this initial render, `apiRef` holds methods to interact imperatively with the Tree View.
+:::
+
+### Select or deselect an item
+
+Use the `selectItem` API method to select or deselect an item:
+
+```ts
+apiRef.current.selectItem({
+ // The DOM event that triggered the change
+ event,
+ // The id of the item to select or deselect
+ itemId,
+ // If `true`, the other already selected items will remain selected
+ // Otherwise, they will be deselected
+ // This parameter is only relevant when `multiSelect` is `true`
+ keepExistingSelection,
+ // If `true` the item will be selected
+ // If `false` the item will be deselected
+ // If not defined, the item's new selection status will be the opposite of its current one
+ shouldBeSelected,
+});
+```
+
+{{"demo": "ApiMethodSelectItem.js"}}
+
+You can use the `keepExistingSelection` property to avoid losing the already selected items when using `multiSelect`:
+
+{{"demo": "ApiMethodSelectItemKeepExistingSelection.js"}}
diff --git a/docs/next.config.mjs b/docs/next.config.mjs
index f72e1399a49f..de1ede2b32f4 100644
--- a/docs/next.config.mjs
+++ b/docs/next.config.mjs
@@ -68,7 +68,7 @@ export default withDocsInfra({
transpilePackages: [
// TODO, those shouldn't be needed in the first place
'@mui/monorepo', // Migrate everything to @mui/docs until the @mui/monorepo dependency becomes obsolete
- '@mui/docs',
+ '@mui/docs', // needed to fix slashes in the generated links (https://github.com/mui/mui-x/pull/13713#issuecomment-2205591461, )
],
// Avoid conflicts with the other Next.js apps hosted under https://mui.com/
assetPrefix: process.env.DEPLOY_ENV === 'development' ? undefined : '/x',
@@ -104,18 +104,6 @@ export default withDocsInfra({
return {
...config,
plugins,
- // TODO, this shouldn't be needed in the first place
- // Migrate everything from @mui/monorepo to @mui/docs and embed @mui/internal-markdown in @mui/docs
- resolveLoader: {
- ...config.resolveLoader,
- alias: {
- ...config.resolveLoader.alias,
- '@mui/internal-markdown/loader': path.resolve(
- MONOREPO_PATH,
- './packages/markdown/loader',
- ),
- },
- },
resolve: {
...config.resolve,
alias: {
diff --git a/docs/package.json b/docs/package.json
index 3d7c5ce85c38..6d3c83b76fca 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -1,6 +1,6 @@
{
"name": "docs",
- "version": "6.7.0",
+ "version": "7.8.0",
"private": true,
"author": "MUI Team",
"license": "MIT",
@@ -28,14 +28,15 @@
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.5",
"@mui/base": "^5.0.0-beta.40",
- "@mui/icons-material": "^5.15.20",
- "@mui/joy": "5.0.0-beta.32",
- "@mui/lab": "^5.0.0-alpha.170",
- "@mui/material": "^5.15.20",
+ "@mui/docs": "^6.0.0-alpha.14",
+ "@mui/icons-material": "^5.16.0",
+ "@mui/joy": "^5.0.0-beta.47",
+ "@mui/lab": "^5.0.0-alpha.171",
+ "@mui/material": "^5.16.0",
"@mui/material-nextjs": "^5.15.11",
- "@mui/styles": "^5.15.20",
- "@mui/system": "^5.15.20",
- "@mui/utils": "^5.15.20",
+ "@mui/styles": "^5.16.0",
+ "@mui/system": "^5.16.0",
+ "@mui/utils": "^5.16.0",
"@mui/x-charts": "workspace:*",
"@mui/x-data-grid": "workspace:*",
"@mui/x-data-grid-generator": "workspace:*",
@@ -45,6 +46,7 @@
"@mui/x-date-pickers-pro": "workspace:*",
"@mui/x-tree-view": "workspace:*",
"@react-spring/web": "^9.7.3",
+ "@tanstack/query-core": "^5.50.1",
"ast-types": "^0.14.2",
"autoprefixer": "^10.4.19",
"babel-plugin-module-resolver": "^5.0.2",
@@ -58,37 +60,38 @@
"clsx": "^2.1.1",
"core-js": "^2.6.12",
"cross-env": "^7.0.3",
+ "d3-scale-chromatic": "^3.1.0",
"date-fns": "^2.30.0",
"date-fns-jalali": "^2.30.0-0",
"dayjs": "^1.11.11",
"doctrine": "^3.0.0",
"exceljs": "^4.4.0",
"fg-loadcss": "^3.1.0",
- "jscodeshift": "0.13.1",
+ "jscodeshift": "0.16.1",
"lodash": "^4.17.21",
"luxon": "^3.4.4",
"lz-string": "^1.5.0",
"markdown-to-jsx": "^7.4.7",
"moment": "^2.30.1",
"moment-hijri": "^2.1.2",
- "moment-jalaali": "^0.10.0",
+ "moment-jalaali": "^0.10.1",
"moment-timezone": "^0.5.45",
"next": "^14.2.4",
"nprogress": "^0.2.0",
- "postcss": "^8.4.38",
+ "postcss": "^8.4.39",
"prismjs": "^1.29.0",
"prop-types": "^15.8.1",
- "react": "^18.2.0",
+ "react": "^18.3.1",
"react-docgen": "^5.4.3",
- "react-dom": "^18.2.0",
- "react-hook-form": "^7.52.0",
- "react-is": "^18.2.0",
- "react-router": "^6.23.1",
- "react-router-dom": "^6.23.1",
+ "react-dom": "^18.3.1",
+ "react-hook-form": "^7.52.1",
+ "react-is": "^18.3.1",
+ "react-router": "^6.24.1",
+ "react-router-dom": "^6.24.1",
"react-runner": "^1.0.5",
- "react-simple-code-editor": "^0.13.1",
+ "react-simple-code-editor": "^0.14.1",
"recast": "^0.23.9",
- "rimraf": "^5.0.7",
+ "rimraf": "^5.0.8",
"rxjs": "^7.8.1",
"styled-components": "^6.1.11",
"stylis": "^4.3.2",
@@ -99,15 +102,16 @@
"@babel/plugin-transform-react-constant-elements": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@mui/internal-docs-utils": "^1.0.7",
- "@mui/internal-scripts": "^1.0.10",
+ "@mui/internal-scripts": "^1.0.12",
"@types/chance": "^1.1.6",
"@types/d3-scale": "^4.0.8",
+ "@types/d3-scale-chromatic": "^3.0.3",
"@types/doctrine": "^0.0.9",
- "@types/lodash": "^4.17.5",
+ "@types/lodash": "^4.17.6",
"@types/luxon": "^3.4.2",
"@types/moment-hijri": "^2.1.4",
"@types/moment-jalaali": "^0.7.9",
- "@types/react-dom": "18.2.25",
+ "@types/react-dom": "^18.3.0",
"@types/react-router-dom": "^5.3.3",
"@types/stylis": "^4.2.6",
"@types/webpack-bundle-analyzer": "^4.7.0",
diff --git a/docs/pages/_app.js b/docs/pages/_app.js
index 69982b1a9e72..08a025daad50 100644
--- a/docs/pages/_app.js
+++ b/docs/pages/_app.js
@@ -7,17 +7,18 @@ import { loadCSS } from 'fg-loadcss/src/loadCSS';
import NextHead from 'next/head';
import PropTypes from 'prop-types';
import { useRouter } from 'next/router';
+import { LicenseInfo } from '@mui/x-license';
import { ponyfillGlobal } from '@mui/utils';
import PageContext from 'docs/src/modules/components/PageContext';
import GoogleAnalytics from 'docs/src/modules/components/GoogleAnalytics';
+import { CodeCopyProvider } from '@mui/docs/CodeCopy';
import { ThemeProvider } from 'docs/src/modules/components/ThemeContext';
import { CodeVariantProvider } from 'docs/src/modules/utils/codeVariant';
-import { CodeCopyProvider } from 'docs/src/modules/utils/CodeCopy';
+import { CodeStylingProvider } from 'docs/src/modules/utils/codeStylingSolution';
import DocsStyledEngineProvider from 'docs/src/modules/utils/StyledEngineProvider';
-import { pathnameToLanguage } from 'docs/src/modules/utils/helpers';
import createEmotionCache from 'docs/src/createEmotionCache';
import findActivePage from 'docs/src/modules/utils/findActivePage';
-import { LicenseInfo } from '@mui/x-license';
+import { pathnameToLanguage } from 'docs/src/modules/utils/helpers';
import getProductInfoFromUrl from 'docs/src/modules/utils/getProductInfoFromUrl';
import { DocsProvider } from '@mui/docs/DocsProvider';
import { mapTranslations } from '@mui/docs/i18n';
@@ -64,6 +65,7 @@ ponyfillGlobal.muiDocConfig = {
'@mui/x-charts-pro': getMuiPackageVersion('x-charts-pro', muiCommitRef),
'@mui/x-tree-view': getMuiPackageVersion('x-tree-view', muiCommitRef),
'@mui/x-tree-view-pro': getMuiPackageVersion('x-tree-view-pro', muiCommitRef),
+ '@mui/x-internals': getMuiPackageVersion('x-internals', muiCommitRef),
exceljs: 'latest',
};
return output;
@@ -173,7 +175,6 @@ Tip: you can access the documentation \`theme\` object directly in the console.
'font-family:monospace;color:#1976d2;font-size:12px;',
);
}
-
function AppWrapper(props) {
const { children, emotionCache, pageProps } = props;
@@ -202,13 +203,6 @@ function AppWrapper(props) {
}
}, []);
- let fonts = [];
- if (pathnameToLanguage(router.asPath).canonicalAs.match(/onepirate/)) {
- fonts = [
- 'https://fonts.googleapis.com/css?family=Roboto+Condensed:700|Work+Sans:300,400&display=swap',
- ];
- }
-
const pageContextValue = React.useMemo(() => {
const { activePage, activePageParents } = findActivePage(pages, router.pathname);
const languagePrefix = pageProps.userLanguage === 'en' ? '' : `/${pageProps.userLanguage}`;
@@ -299,6 +293,13 @@ function AppWrapper(props) {
};
}, [productId, productCategoryId, pageProps.userLanguage, router.pathname]);
+ let fonts = [];
+ if (pathnameToLanguage(router.asPath).canonicalAs.match(/onepirate/)) {
+ fonts = [
+ 'https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@700&family=Work+Sans:wght@300;400&display=swap',
+ ];
+ }
+
// Replicate change reverted in https://github.com/mui/material-ui/pull/35969/files#r1089572951
// Fixes playground styles in dark mode.
const ThemeWrapper = router.pathname.startsWith('/playground') ? React.Fragment : ThemeProvider;
@@ -306,6 +307,7 @@ function AppWrapper(props) {
return (
+
{fonts.map((font) => (
))}
@@ -318,16 +320,18 @@ function AppWrapper(props) {
translations={pageProps.translations}
>
-
-
-
-
- {children}
-
-
-
-
-
+
+
+
+
+
+ {children}
+
+
+
+
+
+
@@ -343,9 +347,11 @@ AppWrapper.propTypes = {
export default function MyApp(props) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
+ const getLayout = Component.getLayout ?? ((page) => page);
+
return (
-
+ {getLayout( )}
);
}
@@ -377,6 +383,7 @@ MyApp.getInitialProps = async ({ ctx, Component }) => {
// Track fraction of actual events to prevent exceeding event quota.
// Filter sessions instead of individual events so that we can track multiple metrics per device.
+// See https://github.com/GoogleChromeLabs/web-vitals-report to use this data
const disableWebVitalsReporting = Math.random() > 0.0001;
export function reportWebVitals({ id, name, label, delta, value }) {
if (disableWebVitalsReporting) {
diff --git a/docs/pages/x/api/charts/axis-config.json b/docs/pages/x/api/charts/axis-config.json
index 8f826848b286..a3b887bf9d81 100644
--- a/docs/pages/x/api/charts/axis-config.json
+++ b/docs/pages/x/api/charts/axis-config.json
@@ -3,8 +3,10 @@
"imports": ["import { AxisConfig } from '@mui/x-charts'"],
"properties": {
"id": { "type": { "description": "AxisId" }, "required": true },
- "scaleType": { "type": { "description": "'linear'" }, "required": true },
- "colorMap": { "type": { "description": "ContinuousColorConfig | PiecewiseColorConfig" } },
+ "scaleType": { "type": { "description": "'band'" }, "required": true },
+ "colorMap": {
+ "type": { "description": "OrdinalColorConfig | ContinuousColorConfig | PiecewiseColorConfig" }
+ },
"data": { "type": { "description": "V[]" } },
"dataKey": { "type": { "description": "string" } },
"hideTooltip": { "type": { "description": "boolean" } },
diff --git a/docs/pages/x/api/charts/chart-container.json b/docs/pages/x/api/charts/chart-container.json
index b92a05628f0d..823f299ffea2 100644
--- a/docs/pages/x/api/charts/chart-container.json
+++ b/docs/pages/x/api/charts/chart-container.json
@@ -48,7 +48,7 @@
"zAxis": {
"type": {
"name": "arrayOf",
- "description": "Array<{ colorMap?: { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date | number | string> } | { color: Array<string> | func, max?: Date | number, min?: Date | number, type: 'continuous' } | { colors: Array<string>, thresholds: Array<Date | number>, type: 'piecewise' }, data?: array, dataKey?: string, id?: string }>"
+ "description": "Array<{ colorMap?: { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date | number | string> } | { color: Array<string> | func, max?: Date | number, min?: Date | number, type: 'continuous' } | { colors: Array<string>, thresholds: Array<Date | number>, type: 'piecewise' }, data?: array, dataKey?: string, id?: string, max?: number, min?: number }>"
}
}
},
diff --git a/docs/pages/x/api/charts/charts-axis-tooltip-content.js b/docs/pages/x/api/charts/charts-axis-tooltip-content.js
deleted file mode 100644
index 9dc6feb6568c..000000000000
--- a/docs/pages/x/api/charts/charts-axis-tooltip-content.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import * as React from 'react';
-import ApiPage from 'docs/src/modules/components/ApiPage';
-import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
-import jsonPageContent from './charts-axis-tooltip-content.json';
-
-export default function Page(props) {
- const { descriptions, pageContent } = props;
- return ;
-}
-
-Page.getInitialProps = () => {
- const req = require.context(
- 'docsx/translations/api-docs/charts/charts-axis-tooltip-content',
- false,
- /\.\/charts-axis-tooltip-content.*.json$/,
- );
- const descriptions = mapApiPageTranslations(req);
-
- return {
- descriptions,
- pageContent: jsonPageContent,
- };
-};
diff --git a/docs/pages/x/api/charts/charts-axis-tooltip-content.json b/docs/pages/x/api/charts/charts-axis-tooltip-content.json
deleted file mode 100644
index 377c83930f32..000000000000
--- a/docs/pages/x/api/charts/charts-axis-tooltip-content.json
+++ /dev/null
@@ -1,63 +0,0 @@
-{
- "props": {},
- "name": "ChartsAxisTooltipContent",
- "imports": [
- "import { ChartsAxisTooltipContent } from '@mui/x-charts/ChartsTooltip';",
- "import { ChartsAxisTooltipContent } from '@mui/x-charts';"
- ],
- "classes": [
- {
- "key": "cell",
- "className": "MuiChartsAxisTooltipContent-cell",
- "description": "Styles applied to the cell element.",
- "isGlobal": false
- },
- {
- "key": "labelCell",
- "className": "MuiChartsAxisTooltipContent-labelCell",
- "description": "Styles applied to the labelCell element.",
- "isGlobal": false
- },
- {
- "key": "mark",
- "className": "MuiChartsAxisTooltipContent-mark",
- "description": "Styles applied to the mark element.",
- "isGlobal": false
- },
- {
- "key": "markCell",
- "className": "MuiChartsAxisTooltipContent-markCell",
- "description": "Styles applied to the markCell element.",
- "isGlobal": false
- },
- {
- "key": "root",
- "className": "MuiChartsAxisTooltipContent-root",
- "description": "Styles applied to the root element.",
- "isGlobal": false
- },
- {
- "key": "row",
- "className": "MuiChartsAxisTooltipContent-row",
- "description": "Styles applied to the row element.",
- "isGlobal": false
- },
- {
- "key": "table",
- "className": "MuiChartsAxisTooltipContent-table",
- "description": "Styles applied to the table element.",
- "isGlobal": false
- },
- {
- "key": "valueCell",
- "className": "MuiChartsAxisTooltipContent-valueCell",
- "description": "Styles applied to the valueCell element.",
- "isGlobal": false
- }
- ],
- "muiName": "MuiChartsAxisTooltipContent",
- "filename": "/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx",
- "inheritance": null,
- "demos": "",
- "cssComponent": false
-}
diff --git a/docs/pages/x/api/charts/charts-item-tooltip-content.js b/docs/pages/x/api/charts/charts-item-tooltip-content.js
deleted file mode 100644
index 3eb3dbfb139a..000000000000
--- a/docs/pages/x/api/charts/charts-item-tooltip-content.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import * as React from 'react';
-import ApiPage from 'docs/src/modules/components/ApiPage';
-import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations';
-import jsonPageContent from './charts-item-tooltip-content.json';
-
-export default function Page(props) {
- const { descriptions, pageContent } = props;
- return ;
-}
-
-Page.getInitialProps = () => {
- const req = require.context(
- 'docsx/translations/api-docs/charts/charts-item-tooltip-content',
- false,
- /\.\/charts-item-tooltip-content.*.json$/,
- );
- const descriptions = mapApiPageTranslations(req);
-
- return {
- descriptions,
- pageContent: jsonPageContent,
- };
-};
diff --git a/docs/pages/x/api/charts/charts-item-tooltip-content.json b/docs/pages/x/api/charts/charts-item-tooltip-content.json
deleted file mode 100644
index e00782f42984..000000000000
--- a/docs/pages/x/api/charts/charts-item-tooltip-content.json
+++ /dev/null
@@ -1,63 +0,0 @@
-{
- "props": {},
- "name": "ChartsItemTooltipContent",
- "imports": [
- "import { ChartsItemTooltipContent } from '@mui/x-charts/ChartsTooltip';",
- "import { ChartsItemTooltipContent } from '@mui/x-charts';"
- ],
- "classes": [
- {
- "key": "cell",
- "className": "MuiChartsItemTooltipContent-cell",
- "description": "Styles applied to the cell element.",
- "isGlobal": false
- },
- {
- "key": "labelCell",
- "className": "MuiChartsItemTooltipContent-labelCell",
- "description": "Styles applied to the labelCell element.",
- "isGlobal": false
- },
- {
- "key": "mark",
- "className": "MuiChartsItemTooltipContent-mark",
- "description": "Styles applied to the mark element.",
- "isGlobal": false
- },
- {
- "key": "markCell",
- "className": "MuiChartsItemTooltipContent-markCell",
- "description": "Styles applied to the markCell element.",
- "isGlobal": false
- },
- {
- "key": "root",
- "className": "MuiChartsItemTooltipContent-root",
- "description": "Styles applied to the root element.",
- "isGlobal": false
- },
- {
- "key": "row",
- "className": "MuiChartsItemTooltipContent-row",
- "description": "Styles applied to the row element.",
- "isGlobal": false
- },
- {
- "key": "table",
- "className": "MuiChartsItemTooltipContent-table",
- "description": "Styles applied to the table element.",
- "isGlobal": false
- },
- {
- "key": "valueCell",
- "className": "MuiChartsItemTooltipContent-valueCell",
- "description": "Styles applied to the valueCell element.",
- "isGlobal": false
- }
- ],
- "muiName": "MuiChartsItemTooltipContent",
- "filename": "/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx",
- "inheritance": null,
- "demos": "",
- "cssComponent": false
-}
diff --git a/docs/pages/x/api/charts/responsive-chart-container.json b/docs/pages/x/api/charts/responsive-chart-container.json
index f758a3c363d9..b04b83407c93 100644
--- a/docs/pages/x/api/charts/responsive-chart-container.json
+++ b/docs/pages/x/api/charts/responsive-chart-container.json
@@ -48,7 +48,7 @@
"zAxis": {
"type": {
"name": "arrayOf",
- "description": "Array<{ colorMap?: { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date | number | string> } | { color: Array<string> | func, max?: Date | number, min?: Date | number, type: 'continuous' } | { colors: Array<string>, thresholds: Array<Date | number>, type: 'piecewise' }, data?: array, dataKey?: string, id?: string }>"
+ "description": "Array<{ colorMap?: { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date | number | string> } | { color: Array<string> | func, max?: Date | number, min?: Date | number, type: 'continuous' } | { colors: Array<string>, thresholds: Array<Date | number>, type: 'piecewise' }, data?: array, dataKey?: string, id?: string, max?: number, min?: number }>"
}
}
},
diff --git a/docs/pages/x/api/charts/scatter-chart.json b/docs/pages/x/api/charts/scatter-chart.json
index 42263922241a..96bb86d6ba1e 100644
--- a/docs/pages/x/api/charts/scatter-chart.json
+++ b/docs/pages/x/api/charts/scatter-chart.json
@@ -101,7 +101,7 @@
"zAxis": {
"type": {
"name": "arrayOf",
- "description": "Array<{ colorMap?: { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date | number | string> } | { color: Array<string> | func, max?: Date | number, min?: Date | number, type: 'continuous' } | { colors: Array<string>, thresholds: Array<Date | number>, type: 'piecewise' }, data?: array, dataKey?: string, id?: string }>"
+ "description": "Array<{ colorMap?: { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date | number | string> } | { color: Array<string> | func, max?: Date | number, min?: Date | number, type: 'continuous' } | { colors: Array<string>, thresholds: Array<Date | number>, type: 'piecewise' }, data?: array, dataKey?: string, id?: string, max?: number, min?: number }>"
}
}
},
diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json
index 0e19418dae1c..1c79c35a04e6 100644
--- a/docs/pages/x/api/data-grid/data-grid-premium.json
+++ b/docs/pages/x/api/data-grid/data-grid-premium.json
@@ -664,6 +664,12 @@
"default": "GridColumnHeaderFilterIconButton",
"class": null
},
+ {
+ "name": "columnHeaderSortIcon",
+ "description": "Sort icon component rendered in each column header.",
+ "default": "GridColumnHeaderSortIcon",
+ "class": null
+ },
{
"name": "columnMenu",
"description": "Column menu component rendered by clicking on the 3 dots \"kebab\" icon in column headers.",
@@ -1758,6 +1764,12 @@
"description": "Styles applied to the row's draggable placeholder element inside the special row reorder cell.",
"isGlobal": false
},
+ {
+ "key": "rowSkeleton",
+ "className": "MuiDataGridPremium-rowSkeleton",
+ "description": "Styles applied to the skeleton row element.",
+ "isGlobal": false
+ },
{
"key": "scrollArea",
"className": "MuiDataGridPremium-scrollArea",
diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json
index 6bf884ab4d1c..c3eb24a2ffb8 100644
--- a/docs/pages/x/api/data-grid/data-grid-pro.json
+++ b/docs/pages/x/api/data-grid/data-grid-pro.json
@@ -599,6 +599,12 @@
"default": "GridColumnHeaderFilterIconButton",
"class": null
},
+ {
+ "name": "columnHeaderSortIcon",
+ "description": "Sort icon component rendered in each column header.",
+ "default": "GridColumnHeaderSortIcon",
+ "class": null
+ },
{
"name": "columnMenu",
"description": "Column menu component rendered by clicking on the 3 dots \"kebab\" icon in column headers.",
@@ -1675,6 +1681,12 @@
"description": "Styles applied to the row's draggable placeholder element inside the special row reorder cell.",
"isGlobal": false
},
+ {
+ "key": "rowSkeleton",
+ "className": "MuiDataGridPro-rowSkeleton",
+ "description": "Styles applied to the skeleton row element.",
+ "isGlobal": false
+ },
{
"key": "scrollArea",
"className": "MuiDataGridPro-scrollArea",
diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json
index 2a408aaf08e2..7543c9d9f0ab 100644
--- a/docs/pages/x/api/data-grid/data-grid.json
+++ b/docs/pages/x/api/data-grid/data-grid.json
@@ -497,6 +497,12 @@
"default": "GridColumnHeaderFilterIconButton",
"class": null
},
+ {
+ "name": "columnHeaderSortIcon",
+ "description": "Sort icon component rendered in each column header.",
+ "default": "GridColumnHeaderSortIcon",
+ "class": null
+ },
{
"name": "columnMenu",
"description": "Column menu component rendered by clicking on the 3 dots \"kebab\" icon in column headers.",
@@ -1561,6 +1567,12 @@
"description": "Styles applied to the row's draggable placeholder element inside the special row reorder cell.",
"isGlobal": false
},
+ {
+ "key": "rowSkeleton",
+ "className": "MuiDataGrid-rowSkeleton",
+ "description": "Styles applied to the skeleton row element.",
+ "isGlobal": false
+ },
{
"key": "scrollArea",
"className": "MuiDataGrid-scrollArea",
diff --git a/docs/pages/x/api/data-grid/grid-actions-col-def.json b/docs/pages/x/api/data-grid/grid-actions-col-def.json
index ca909eb5cbaa..26d5acce67f3 100644
--- a/docs/pages/x/api/data-grid/grid-actions-col-def.json
+++ b/docs/pages/x/api/data-grid/grid-actions-col-def.json
@@ -10,7 +10,7 @@
"field": { "type": { "description": "string" }, "required": true },
"getActions": {
"type": {
- "description": "(params: GridRowParams<R>) => React.ReactElement<GridActionsCellItemProps>[]"
+ "description": "(params: GridRowParams<R>) => readonly React.ReactElement<GridActionsCellItemProps>[]"
},
"required": true
},
@@ -37,7 +37,9 @@
"display": { "type": { "description": "'text' | 'flex'" } },
"editable": { "type": { "description": "boolean" }, "default": "false" },
"filterable": { "type": { "description": "boolean" }, "default": "true" },
- "filterOperators": { "type": { "description": "GridFilterOperator<R, V, F>[]" } },
+ "filterOperators": {
+ "type": { "description": "readonly GridFilterOperator<R, V, F>[]" }
+ },
"flex": { "type": { "description": "number" } },
"getApplyQuickFilterFn": { "type": { "description": "GetApplyQuickFilterFn<R, V>" } },
"getSortComparator": {
@@ -89,7 +91,7 @@
"resizable": { "type": { "description": "boolean" }, "default": "true" },
"sortable": { "type": { "description": "boolean" }, "default": "true" },
"sortComparator": { "type": { "description": "GridComparatorFn<V>" } },
- "sortingOrder": { "type": { "description": "GridSortDirection[]" } },
+ "sortingOrder": { "type": { "description": "readonly GridSortDirection[]" } },
"valueFormatter": { "type": { "description": "GridValueFormatter<R, V, F>" } },
"valueGetter": { "type": { "description": "GridValueGetter<R, V, F>" } },
"valueParser": { "type": { "description": "GridValueParser<R, V, F>" } },
diff --git a/docs/pages/x/api/data-grid/grid-api.json b/docs/pages/x/api/data-grid/grid-api.json
index 88b2080043cd..5f5202edf84f 100644
--- a/docs/pages/x/api/data-grid/grid-api.json
+++ b/docs/pages/x/api/data-grid/grid-api.json
@@ -350,6 +350,7 @@
},
"required": true
},
+ "setLoading": { "type": { "description": "(loading: boolean) => void" }, "required": true },
"setPage": { "type": { "description": "(page: number) => void" }, "required": true },
"setPageSize": { "type": { "description": "(pageSize: number) => void" }, "required": true },
"setPaginationMeta": {
@@ -394,7 +395,7 @@
},
"setRows": { "type": { "description": "(rows: GridRowModel[]) => void" }, "required": true },
"setRowSelectionModel": {
- "type": { "description": "(rowIds: GridRowId[]) => void" },
+ "type": { "description": "(rowIds: readonly GridRowId[]) => void" },
"required": true
},
"setSortModel": {
@@ -466,6 +467,11 @@
"required": true,
"isProPlan": true
},
+ "unstable_dataSource": {
+ "type": { "description": "GridDataSourceApiBase" },
+ "required": true,
+ "isProPlan": true
+ },
"unstable_replaceRows": {
"type": { "description": "(firstRowToReplace: number, newRows: GridRowModel[]) => void" },
"required": true
diff --git a/docs/pages/x/api/data-grid/grid-col-def.json b/docs/pages/x/api/data-grid/grid-col-def.json
index 6173cc8c314e..bfe97f8a9704 100644
--- a/docs/pages/x/api/data-grid/grid-col-def.json
+++ b/docs/pages/x/api/data-grid/grid-col-def.json
@@ -30,7 +30,9 @@
"display": { "type": { "description": "'text' | 'flex'" } },
"editable": { "type": { "description": "boolean" }, "default": "false" },
"filterable": { "type": { "description": "boolean" }, "default": "true" },
- "filterOperators": { "type": { "description": "GridFilterOperator<R, V, F>[]" } },
+ "filterOperators": {
+ "type": { "description": "readonly GridFilterOperator<R, V, F>[]" }
+ },
"flex": { "type": { "description": "number" } },
"getApplyQuickFilterFn": { "type": { "description": "GetApplyQuickFilterFn<R, V>" } },
"getSortComparator": {
@@ -82,7 +84,7 @@
"resizable": { "type": { "description": "boolean" }, "default": "true" },
"sortable": { "type": { "description": "boolean" }, "default": "true" },
"sortComparator": { "type": { "description": "GridComparatorFn<V>" } },
- "sortingOrder": { "type": { "description": "GridSortDirection[]" } },
+ "sortingOrder": { "type": { "description": "readonly GridSortDirection[]" } },
"type": { "type": { "description": "GridColType" }, "default": "'singleSelect'" },
"valueFormatter": { "type": { "description": "GridValueFormatter<R, V, F>" } },
"valueGetter": { "type": { "description": "GridValueGetter<R, V, F>" } },
diff --git a/docs/pages/x/api/data-grid/grid-pagination-model-api.json b/docs/pages/x/api/data-grid/grid-pagination-model-api.json
new file mode 100644
index 000000000000..cb60edc245db
--- /dev/null
+++ b/docs/pages/x/api/data-grid/grid-pagination-model-api.json
@@ -0,0 +1,21 @@
+{
+ "name": "GridPaginationModelApi",
+ "description": "The pagination model API interface that is available in the grid `apiRef`.",
+ "properties": [
+ {
+ "name": "setPage",
+ "description": "Sets the displayed page to the value given by page
.",
+ "type": "(page: number) => void"
+ },
+ {
+ "name": "setPageSize",
+ "description": "Sets the number of displayed rows to the value given by pageSize
.",
+ "type": "(pageSize: number) => void"
+ },
+ {
+ "name": "setPaginationModel",
+ "description": "Sets the paginationModel
to a new value.",
+ "type": "(model: GridPaginationModel) => void"
+ }
+ ]
+}
diff --git a/docs/pages/x/api/data-grid/grid-row-selection-api.json b/docs/pages/x/api/data-grid/grid-row-selection-api.json
index 16522e648d1f..4daa27a95dee 100644
--- a/docs/pages/x/api/data-grid/grid-row-selection-api.json
+++ b/docs/pages/x/api/data-grid/grid-row-selection-api.json
@@ -25,7 +25,7 @@
{
"name": "setRowSelectionModel",
"description": "Updates the selected rows to be those passed to the rowIds
argument. Any row already selected will be unselected.",
- "type": "(rowIds: GridRowId[]) => void"
+ "type": "(rowIds: readonly GridRowId[]) => void"
}
]
}
diff --git a/docs/pages/x/api/data-grid/grid-single-select-col-def.json b/docs/pages/x/api/data-grid/grid-single-select-col-def.json
index 2e62dcbf36e9..669318bc4848 100644
--- a/docs/pages/x/api/data-grid/grid-single-select-col-def.json
+++ b/docs/pages/x/api/data-grid/grid-single-select-col-def.json
@@ -35,7 +35,9 @@
"display": { "type": { "description": "'text' | 'flex'" } },
"editable": { "type": { "description": "boolean" }, "default": "false" },
"filterable": { "type": { "description": "boolean" }, "default": "true" },
- "filterOperators": { "type": { "description": "GridFilterOperator<R, V, F>[]" } },
+ "filterOperators": {
+ "type": { "description": "readonly GridFilterOperator<R, V, F>[]" }
+ },
"flex": { "type": { "description": "number" } },
"getApplyQuickFilterFn": { "type": { "description": "GetApplyQuickFilterFn<R, V>" } },
"getOptionLabel": { "type": { "description": "(value: ValueOptions) => string" } },
@@ -89,7 +91,7 @@
"resizable": { "type": { "description": "boolean" }, "default": "true" },
"sortable": { "type": { "description": "boolean" }, "default": "true" },
"sortComparator": { "type": { "description": "GridComparatorFn<V>" } },
- "sortingOrder": { "type": { "description": "GridSortDirection[]" } },
+ "sortingOrder": { "type": { "description": "readonly GridSortDirection[]" } },
"valueFormatter": { "type": { "description": "GridValueFormatter<R, V, F>" } },
"valueGetter": { "type": { "description": "GridValueGetter<R, V, F>" } },
"valueOptions": {
diff --git a/docs/pages/x/api/data-grid/selectors.json b/docs/pages/x/api/data-grid/selectors.json
index 10cb5c8c4439..ad0e4aefd23d 100644
--- a/docs/pages/x/api/data-grid/selectors.json
+++ b/docs/pages/x/api/data-grid/selectors.json
@@ -194,6 +194,20 @@
"description": "Get the filterable columns as a lookup (an object containing the field for keys and the definition for values).",
"supportsApiRef": true
},
+ {
+ "name": "gridFilteredDescendantRowCountSelector",
+ "returnType": "number",
+ "category": "Filtering",
+ "description": "Get the amount of descendant rows accessible after the filtering process.",
+ "supportsApiRef": true
+ },
+ {
+ "name": "gridFilteredRowCountSelector",
+ "returnType": "number",
+ "category": "Filtering",
+ "description": "Get the amount of rows accessible after the filtering process.\nIncludes top level and descendant rows.",
+ "supportsApiRef": true
+ },
{
"name": "gridFilteredSortedRowEntriesSelector",
"returnType": "GridRowEntry[]",
diff --git a/docs/pages/x/api/tree-view/rich-tree-view.json b/docs/pages/x/api/tree-view/rich-tree-view.json
index 7da4e28d56f0..52784c1c9c04 100644
--- a/docs/pages/x/api/tree-view/rich-tree-view.json
+++ b/docs/pages/x/api/tree-view/rich-tree-view.json
@@ -3,7 +3,7 @@
"apiRef": {
"type": {
"name": "shape",
- "description": "{ current?: { focusItem: func, getItem: func, setItemExpansion: func } }"
+ "description": "{ current?: { focusItem: func, getItem: func, getItemDOMElement: func, selectItem: func, setItemExpansion: func } }"
}
},
"checkboxSelection": { "type": { "name": "bool" }, "default": "false" },
@@ -16,6 +16,10 @@
"disabledItemsFocusable": { "type": { "name": "bool" }, "default": "false" },
"disableSelection": { "type": { "name": "bool" }, "default": "false" },
"expandedItems": { "type": { "name": "arrayOf", "description": "Array<string>" } },
+ "expansionTrigger": {
+ "type": { "name": "enum", "description": "'content' | 'iconContainer'" },
+ "default": "'content'"
+ },
"experimentalFeatures": {
"type": { "name": "shape", "description": "{ indentationAtItemLevel?: bool }" }
},
diff --git a/docs/pages/x/api/tree-view/simple-tree-view.json b/docs/pages/x/api/tree-view/simple-tree-view.json
index 1c52f9e03646..aaa6b7cf66b2 100644
--- a/docs/pages/x/api/tree-view/simple-tree-view.json
+++ b/docs/pages/x/api/tree-view/simple-tree-view.json
@@ -3,7 +3,7 @@
"apiRef": {
"type": {
"name": "shape",
- "description": "{ current?: { focusItem: func, getItem: func, setItemExpansion: func } }"
+ "description": "{ current?: { focusItem: func, getItem: func, getItemDOMElement: func, selectItem: func, setItemExpansion: func } }"
}
},
"checkboxSelection": { "type": { "name": "bool" }, "default": "false" },
@@ -17,6 +17,10 @@
"disabledItemsFocusable": { "type": { "name": "bool" }, "default": "false" },
"disableSelection": { "type": { "name": "bool" }, "default": "false" },
"expandedItems": { "type": { "name": "arrayOf", "description": "Array<string>" } },
+ "expansionTrigger": {
+ "type": { "name": "enum", "description": "'content' | 'iconContainer'" },
+ "default": "'content'"
+ },
"experimentalFeatures": {
"type": { "name": "shape", "description": "{ indentationAtItemLevel?: bool }" }
},
diff --git a/docs/pages/x/api/tree-view/tree-view.json b/docs/pages/x/api/tree-view/tree-view.json
index a528b9927844..9162a87809ab 100644
--- a/docs/pages/x/api/tree-view/tree-view.json
+++ b/docs/pages/x/api/tree-view/tree-view.json
@@ -3,7 +3,7 @@
"apiRef": {
"type": {
"name": "shape",
- "description": "{ current?: { focusItem: func, getItem: func, setItemExpansion: func } }"
+ "description": "{ current?: { focusItem: func, getItem: func, getItemDOMElement: func, selectItem: func, setItemExpansion: func } }"
}
},
"checkboxSelection": { "type": { "name": "bool" }, "default": "false" },
@@ -17,6 +17,10 @@
"disabledItemsFocusable": { "type": { "name": "bool" }, "default": "false" },
"disableSelection": { "type": { "name": "bool" }, "default": "false" },
"expandedItems": { "type": { "name": "arrayOf", "description": "Array<string>" } },
+ "expansionTrigger": {
+ "type": { "name": "enum", "description": "'content' | 'iconContainer'" },
+ "default": "'content'"
+ },
"experimentalFeatures": {
"type": { "name": "shape", "description": "{ indentationAtItemLevel?: bool }" }
},
diff --git a/docs/pages/x/react-charts/zoom-and-pan.js b/docs/pages/x/react-charts/zoom-and-pan.js
new file mode 100644
index 000000000000..4a2393ba8b38
--- /dev/null
+++ b/docs/pages/x/react-charts/zoom-and-pan.js
@@ -0,0 +1,7 @@
+import * as React from 'react';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import * as pageProps from 'docsx/data/charts/zoom-and-pan/zoom-and-pan.md?muiMarkdown';
+
+export default function Page() {
+ return ;
+}
diff --git a/docs/pages/x/react-data-grid/overlays.js b/docs/pages/x/react-data-grid/overlays.js
new file mode 100644
index 000000000000..eecf19cc11c3
--- /dev/null
+++ b/docs/pages/x/react-data-grid/overlays.js
@@ -0,0 +1,7 @@
+import * as React from 'react';
+import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
+import * as pageProps from 'docsx/data/data-grid/overlays/overlays.md?muiMarkdown';
+
+export default function Page() {
+ return ;
+}
diff --git a/docs/scripts/createXTypeScriptProjects.ts b/docs/scripts/createXTypeScriptProjects.ts
index 3b859b06427a..8d339b2b97c2 100644
--- a/docs/scripts/createXTypeScriptProjects.ts
+++ b/docs/scripts/createXTypeScriptProjects.ts
@@ -37,6 +37,7 @@ export type XProjectNames =
| 'x-date-pickers'
| 'x-date-pickers-pro'
| 'x-charts'
+ | 'x-charts-pro'
| 'x-tree-view'
| 'x-tree-view-pro';
diff --git a/docs/scripts/generateProptypes.ts b/docs/scripts/generateProptypes.ts
index 5d7f3c8ec553..c68602a94873 100644
--- a/docs/scripts/generateProptypes.ts
+++ b/docs/scripts/generateProptypes.ts
@@ -10,6 +10,8 @@ import {
import { fixBabelGeneratorIssues, fixLineEndings } from '@mui/internal-docs-utils';
import { createXTypeScriptProjects, XTypeScriptProject } from './createXTypeScriptProjects';
+const COMPONENTS_WITHOUT_PROPTYPES = ['ChartsAxisTooltipContent', 'ChartsItemTooltipContent'];
+
async function generateProptypes(project: XTypeScriptProject, sourceFile: string) {
const isTDate = (name: string) => {
if (['x-date-pickers', 'x-date-pickers-pro'].includes(project.name)) {
@@ -176,14 +178,20 @@ async function run() {
}
const componentsWithPropTypes = project.getComponentsWithPropTypes(project);
- return componentsWithPropTypes.map>(async (filename) => {
- try {
- await generateProptypes(project, filename);
- } catch (error: any) {
- error.message = `${filename}: ${error.message}`;
- throw error;
- }
- });
+ return componentsWithPropTypes
+ .filter((filename) =>
+ COMPONENTS_WITHOUT_PROPTYPES.every(
+ (ignoredComponent) => !filename.includes(ignoredComponent),
+ ),
+ )
+ .map>(async (filename) => {
+ try {
+ await generateProptypes(project, filename);
+ } catch (error: any) {
+ error.message = `${filename}: ${error.message}`;
+ throw error;
+ }
+ });
});
const results = await Promise.allSettled(promises);
diff --git a/docs/src/modules/components/ChartFeaturesGrid.js b/docs/src/modules/components/ChartFeaturesGrid.js
index 51bc73d2d991..ecff879e95c2 100644
--- a/docs/src/modules/components/ChartFeaturesGrid.js
+++ b/docs/src/modules/components/ChartFeaturesGrid.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import Grid from '@mui/material/Unstable_Grid2';
-import InfoCard from 'docs/src/components/action/InfoCard';
+import { InfoCard } from '@mui/docs/InfoCard';
import LineAxisRoundedIcon from '@mui/icons-material/LineAxisRounded';
import DashboardCustomizeRoundedIcon from '@mui/icons-material/DashboardCustomizeRounded';
import LegendToggleRoundedIcon from '@mui/icons-material/LegendToggleRounded';
diff --git a/docs/src/modules/components/ChartsUsageDemo.js b/docs/src/modules/components/ChartsUsageDemo.js
index 79087370cb16..dc0e4e24c302 100644
--- a/docs/src/modules/components/ChartsUsageDemo.js
+++ b/docs/src/modules/components/ChartsUsageDemo.js
@@ -1,7 +1,7 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import Box from '@mui/joy/Box';
-import BrandingProvider from 'docs/src/BrandingProvider';
+import { BrandingProvider } from '@mui/docs/branding';
import { HighlightedCode } from '@mui/docs/HighlightedCode';
import DemoPropsForm from './DemoPropsForm';
diff --git a/docs/src/modules/components/ComponentLinkHeader.js b/docs/src/modules/components/ComponentLinkHeader.js
deleted file mode 100644
index c63c24b9473c..000000000000
--- a/docs/src/modules/components/ComponentLinkHeader.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from 'docs/src/modules/components/ComponentLinkHeader';
diff --git a/docs/src/modules/components/CustomizationPlayground.tsx b/docs/src/modules/components/CustomizationPlayground.tsx
index 6f131c36a9e6..0e34112f8753 100644
--- a/docs/src/modules/components/CustomizationPlayground.tsx
+++ b/docs/src/modules/components/CustomizationPlayground.tsx
@@ -1,8 +1,6 @@
import * as React from 'react';
-// @ts-ignore
import { HighlightedCode } from '@mui/docs/HighlightedCode';
-// @ts-ignore
-import BrandingProvider from 'docs/src/BrandingProvider';
+import { BrandingProvider } from '@mui/docs/branding';
import { styled, Theme, alpha, useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import Tabs from '@mui/material/Tabs';
diff --git a/docs/src/modules/components/InstallationGrid.js b/docs/src/modules/components/InstallationGrid.js
index 1ec5bfc316cb..a5dcda305945 100644
--- a/docs/src/modules/components/InstallationGrid.js
+++ b/docs/src/modules/components/InstallationGrid.js
@@ -1,6 +1,6 @@
import * as React from 'react';
import Grid from '@mui/material/Unstable_Grid2';
-import InfoCard from 'docs/src/components/action/InfoCard';
+import { InfoCard } from '@mui/docs/InfoCard';
import AccountTreeRounded from '@mui/icons-material/AccountTreeRounded';
import PivotTableChartRoundedIcon from '@mui/icons-material/PivotTableChartRounded';
import CalendarMonthRoundedIcon from '@mui/icons-material/CalendarMonthRounded';
diff --git a/docs/src/modules/components/InstallationInstructions.tsx b/docs/src/modules/components/InstallationInstructions.tsx
index 51788eeed8cc..18faa37de0e6 100644
--- a/docs/src/modules/components/InstallationInstructions.tsx
+++ b/docs/src/modules/components/InstallationInstructions.tsx
@@ -1,6 +1,5 @@
import * as React from 'react';
-// @ts-expect-error
-import HighlightedCodeWithTabs from 'docs/src/modules/components/HighlightedCodeWithTabs';
+import HighlightedCodeWithTabs from '@mui/docs/HighlightedCodeWithTabs';
import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box';
import ToggleOptions from './ToggleOptions';
diff --git a/docs/src/modules/components/InterfaceApiPage.js b/docs/src/modules/components/InterfaceApiPage.js
index 05b518cd3b87..e0a040209153 100644
--- a/docs/src/modules/components/InterfaceApiPage.js
+++ b/docs/src/modules/components/InterfaceApiPage.js
@@ -6,7 +6,7 @@ import Typography from '@mui/material/Typography';
import Alert from '@mui/material/Alert';
import VerifiedRoundedIcon from '@mui/icons-material/VerifiedRounded';
import { alpha } from '@mui/material/styles';
-import { useTranslate, useUserLanguage } from 'docs/src/modules/utils/i18n';
+import { useTranslate, useUserLanguage } from '@mui/docs/i18n';
import { HighlightedCode } from '@mui/docs/HighlightedCode';
import { MarkdownElement } from '@mui/docs/MarkdownElement';
import { SectionTitle } from '@mui/docs/SectionTitle';
diff --git a/docs/src/modules/components/PickersPlayground.tsx b/docs/src/modules/components/PickersPlayground.tsx
index 079230d5f7aa..2b48de823b10 100644
--- a/docs/src/modules/components/PickersPlayground.tsx
+++ b/docs/src/modules/components/PickersPlayground.tsx
@@ -38,6 +38,9 @@ import { DesktopDateRangePicker } from '@mui/x-date-pickers-pro/DesktopDateRange
import { MobileDateRangePicker } from '@mui/x-date-pickers-pro/MobileDateRangePicker';
import { StaticDateRangePicker } from '@mui/x-date-pickers-pro/StaticDateRangePicker';
import { isDatePickerView, isTimeView } from '@mui/x-date-pickers/internals';
+import { pickersLayoutClasses } from '@mui/x-date-pickers/PickersLayout';
+import { DesktopDateTimeRangePicker } from '@mui/x-date-pickers-pro/DesktopDateTimeRangePicker';
+import { MobileDateTimeRangePicker } from '@mui/x-date-pickers-pro/MobileDateTimeRangePicker';
const ComponentSection = styled('div')(({ theme }) => ({
display: 'flex',
@@ -154,13 +157,14 @@ const DEFAULT_VIEWS_MAP: ViewsMap = {
seconds: false,
};
-type ComponentFamily = 'date' | 'time' | 'date-time' | 'date-range';
+type ComponentFamily = 'date' | 'time' | 'date-time' | 'date-range' | 'date-time-range';
const componentFamilies: { family: ComponentFamily; label: string }[] = [
{ family: 'date', label: 'Date' },
{ family: 'time', label: 'Time' },
{ family: 'date-time', label: 'Date Time' },
{ family: 'date-range', label: 'Date Range' },
+ { family: 'date-time-range', label: 'Date Time Range' },
];
interface ComponentFamilySet {
@@ -392,6 +396,7 @@ export default function PickersPlayground() {
ampm: ampm !== undefined ? ampm : undefined,
ampmInClock: ampmInClock !== undefined ? ampmInClock : undefined,
displayStaticWrapperAs: isStaticDesktopMode ? 'desktop' : 'mobile',
+ sx: { [`&.${pickersLayoutClasses.root}`]: { overflowX: 'auto' } },
}),
[
ampm,
@@ -508,6 +513,22 @@ export default function PickersPlayground() {
[commonProps],
);
+ const DATE_TIME_RANGE_PICKERS: ComponentFamilySet[] = React.useMemo(
+ () => [
+ {
+ name: 'DesktopDateTimeRangePicker',
+ component: DesktopDateTimeRangePicker,
+ props: commonProps,
+ },
+ {
+ name: 'MobileDateTimeRangePicker',
+ component: MobileDateTimeRangePicker,
+ props: commonProps,
+ },
+ ],
+ [commonProps],
+ );
+
return (
)}
+ {selectedPickers === 'date-time-range' && (
+
+ {DATE_TIME_RANGE_PICKERS.map(({ name, component: Component, props }) => (
+
+ ))}
+
+ )}
@@ -622,23 +667,31 @@ export default function PickersPlayground() {
{selectedPickers !== 'date-range' && (
)}
-
- {selectedPickers === 'date-time' && (
-
)}
+
+ {selectedPickers === 'date-time' ||
+ (selectedPickers === 'date-time-range' && (
+
+ ))}
- {selectedPickers !== 'date-range' && (
+ {selectedPickers !== 'date-range' && selectedPickers !== 'date-time-range' && (
model."
},
+ "setLoading": { "description": "Sets the internal loading state." },
"setPage": {
"description": "Sets the displayed page to the value given by page
."
},
@@ -243,6 +244,7 @@
},
"toggleDetailPanel": { "description": "Expands or collapses the detail panel of a row." },
"unpinColumn": { "description": "Unpins a column." },
+ "unstable_dataSource": { "description": "The data source API." },
"unstable_replaceRows": { "description": "Replace a set of rows with new rows." },
"unstable_setColumnVirtualization": { "description": "Enable/disable column virtualization." },
"unstable_setPinnedRows": { "description": "Changes the pinned rows." },
diff --git a/docs/translations/api-docs/tree-view/rich-tree-view/rich-tree-view.json b/docs/translations/api-docs/tree-view/rich-tree-view/rich-tree-view.json
index b866f4dcc07a..ea67296629a8 100644
--- a/docs/translations/api-docs/tree-view/rich-tree-view/rich-tree-view.json
+++ b/docs/translations/api-docs/tree-view/rich-tree-view/rich-tree-view.json
@@ -21,6 +21,9 @@
"expandedItems": {
"description": "Expanded item ids. Used when the item's expansion is controlled."
},
+ "expansionTrigger": {
+ "description": "The slot that triggers the item's expansion when clicked."
+ },
"experimentalFeatures": {
"description": "Unstable features, breaking changes might be introduced. For each feature, if the flag is not explicitly set to true
, the feature will be fully disabled and any property / method call will not have any effect."
},
@@ -51,14 +54,14 @@
"onExpandedItemsChange": {
"description": "Callback fired when tree items are expanded/collapsed.",
"typeDescriptions": {
- "event": "The event source of the callback.",
+ "event": "The DOM event that triggered the change.",
"itemIds": "The ids of the expanded items."
}
},
"onItemExpansionToggle": {
"description": "Callback fired when a tree item is expanded or collapsed.",
"typeDescriptions": {
- "event": "The event source of the callback.",
+ "event": "The DOM event that triggered the change.",
"itemId": "The itemId of the modified item.",
"isExpanded": "true
if the item has just been expanded, false
if it has just been collapsed."
}
@@ -66,7 +69,7 @@
"onItemFocus": {
"description": "Callback fired when tree items are focused.",
"typeDescriptions": {
- "event": "The event source of the callback Warning : This is a generic event not a focus event.",
+ "event": "The DOM event that triggered the change. Warning : This is a generic event not a focus event.",
"itemId": "The id of the focused item.",
"value": "of the focused item."
}
@@ -74,7 +77,7 @@
"onItemSelectionToggle": {
"description": "Callback fired when a tree item is selected or deselected.",
"typeDescriptions": {
- "event": "The event source of the callback.",
+ "event": "The DOM event that triggered the change.",
"itemId": "The itemId of the modified item.",
"isSelected": "true
if the item has just been selected, false
if it has just been deselected."
}
@@ -82,7 +85,7 @@
"onSelectedItemsChange": {
"description": "Callback fired when tree items are selected/deselected.",
"typeDescriptions": {
- "event": "The event source of the callback",
+ "event": "The DOM event that triggered the change.",
"itemIds": "The ids of the selected items. When multiSelect
is true
, this is an array of strings; when false (default) a string."
}
},
diff --git a/docs/translations/api-docs/tree-view/simple-tree-view/simple-tree-view.json b/docs/translations/api-docs/tree-view/simple-tree-view/simple-tree-view.json
index a12a98351778..bd1994ea533b 100644
--- a/docs/translations/api-docs/tree-view/simple-tree-view/simple-tree-view.json
+++ b/docs/translations/api-docs/tree-view/simple-tree-view/simple-tree-view.json
@@ -22,6 +22,9 @@
"expandedItems": {
"description": "Expanded item ids. Used when the item's expansion is controlled."
},
+ "expansionTrigger": {
+ "description": "The slot that triggers the item's expansion when clicked."
+ },
"experimentalFeatures": {
"description": "Unstable features, breaking changes might be introduced. For each feature, if the flag is not explicitly set to true
, the feature will be fully disabled and any property / method call will not have any effect."
},
@@ -37,14 +40,14 @@
"onExpandedItemsChange": {
"description": "Callback fired when tree items are expanded/collapsed.",
"typeDescriptions": {
- "event": "The event source of the callback.",
+ "event": "The DOM event that triggered the change.",
"itemIds": "The ids of the expanded items."
}
},
"onItemExpansionToggle": {
"description": "Callback fired when a tree item is expanded or collapsed.",
"typeDescriptions": {
- "event": "The event source of the callback.",
+ "event": "The DOM event that triggered the change.",
"itemId": "The itemId of the modified item.",
"isExpanded": "true
if the item has just been expanded, false
if it has just been collapsed."
}
@@ -52,7 +55,7 @@
"onItemFocus": {
"description": "Callback fired when tree items are focused.",
"typeDescriptions": {
- "event": "The event source of the callback Warning : This is a generic event not a focus event.",
+ "event": "The DOM event that triggered the change. Warning : This is a generic event not a focus event.",
"itemId": "The id of the focused item.",
"value": "of the focused item."
}
@@ -60,7 +63,7 @@
"onItemSelectionToggle": {
"description": "Callback fired when a tree item is selected or deselected.",
"typeDescriptions": {
- "event": "The event source of the callback.",
+ "event": "The DOM event that triggered the change.",
"itemId": "The itemId of the modified item.",
"isSelected": "true
if the item has just been selected, false
if it has just been deselected."
}
@@ -68,7 +71,7 @@
"onSelectedItemsChange": {
"description": "Callback fired when tree items are selected/deselected.",
"typeDescriptions": {
- "event": "The event source of the callback",
+ "event": "The DOM event that triggered the change.",
"itemIds": "The ids of the selected items. When multiSelect
is true
, this is an array of strings; when false (default) a string."
}
},
diff --git a/docs/translations/api-docs/tree-view/tree-view/tree-view.json b/docs/translations/api-docs/tree-view/tree-view/tree-view.json
index c29df2121733..dde9a0faa577 100644
--- a/docs/translations/api-docs/tree-view/tree-view/tree-view.json
+++ b/docs/translations/api-docs/tree-view/tree-view/tree-view.json
@@ -22,6 +22,9 @@
"expandedItems": {
"description": "Expanded item ids. Used when the item's expansion is controlled."
},
+ "expansionTrigger": {
+ "description": "The slot that triggers the item's expansion when clicked."
+ },
"experimentalFeatures": {
"description": "Unstable features, breaking changes might be introduced. For each feature, if the flag is not explicitly set to true
, the feature will be fully disabled and any property / method call will not have any effect."
},
@@ -37,14 +40,14 @@
"onExpandedItemsChange": {
"description": "Callback fired when tree items are expanded/collapsed.",
"typeDescriptions": {
- "event": "The event source of the callback.",
+ "event": "The DOM event that triggered the change.",
"itemIds": "The ids of the expanded items."
}
},
"onItemExpansionToggle": {
"description": "Callback fired when a tree item is expanded or collapsed.",
"typeDescriptions": {
- "event": "The event source of the callback.",
+ "event": "The DOM event that triggered the change.",
"itemId": "The itemId of the modified item.",
"isExpanded": "true
if the item has just been expanded, false
if it has just been collapsed."
}
@@ -52,7 +55,7 @@
"onItemFocus": {
"description": "Callback fired when tree items are focused.",
"typeDescriptions": {
- "event": "The event source of the callback Warning : This is a generic event not a focus event.",
+ "event": "The DOM event that triggered the change. Warning : This is a generic event not a focus event.",
"itemId": "The id of the focused item.",
"value": "of the focused item."
}
@@ -60,7 +63,7 @@
"onItemSelectionToggle": {
"description": "Callback fired when a tree item is selected or deselected.",
"typeDescriptions": {
- "event": "The event source of the callback.",
+ "event": "The DOM event that triggered the change.",
"itemId": "The itemId of the modified item.",
"isSelected": "true
if the item has just been selected, false
if it has just been deselected."
}
@@ -68,7 +71,7 @@
"onSelectedItemsChange": {
"description": "Callback fired when tree items are selected/deselected.",
"typeDescriptions": {
- "event": "The event source of the callback",
+ "event": "The DOM event that triggered the change.",
"itemIds": "The ids of the selected items. When multiSelect
is true
, this is an array of strings; when false (default) a string."
}
},
diff --git a/package.json b/package.json
index 17f5824b329e..4593a9e15917 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "version": "7.7.1",
+ "version": "7.9.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -66,7 +66,7 @@
"clean:node_modules": "rimraf --glob \"**/node_modules\""
},
"devDependencies": {
- "@argos-ci/core": "^2.2.0",
+ "@argos-ci/core": "^2.3.0",
"@babel/cli": "^7.24.7",
"@babel/core": "^7.24.7",
"@babel/node": "^7.24.7",
@@ -85,39 +85,37 @@
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
- "@mnajdova/enzyme-adapter-react-18": "^0.2.0",
- "@mui/icons-material": "^5.15.20",
- "@mui/internal-markdown": "^1.0.5",
- "@mui/internal-test-utils": "^1.0.1",
- "@mui/material": "^5.15.20",
- "@mui/monorepo": "github:mui/material-ui#22c5206a9e8191b2f81131d6978a0958e55b7032",
- "@mui/utils": "^5.15.20",
+ "@mui/icons-material": "^5.16.0",
+ "@mui/internal-markdown": "^1.0.7",
+ "@mui/internal-test-utils": "^1.0.4",
+ "@mui/material": "^5.16.0",
+ "@mui/monorepo": "github:mui/material-ui#43cb32700af699cd48baaf6922fbe21343f30e2f",
+ "@mui/utils": "^5.16.0",
"@next/eslint-plugin-next": "14.2.4",
- "@octokit/plugin-retry": "^6.0.1",
- "@octokit/rest": "^20.1.1",
+ "@octokit/plugin-retry": "^7.1.1",
+ "@octokit/rest": "^21.0.0",
"@playwright/test": "^1.44.1",
- "@types/babel__traverse": "^7.20.6",
"@types/babel__core": "^7.20.5",
+ "@types/babel__traverse": "^7.20.6",
"@types/chai": "^4.3.16",
"@types/chai-dom": "^1.11.3",
- "@types/enzyme": "3.10.12",
"@types/fs-extra": "^11.0.4",
"@types/karma": "^6.3.8",
- "@types/lodash": "^4.17.5",
- "@types/mocha": "^10.0.6",
- "@types/node": "^18.19.34",
- "@types/react": "^18.2.60",
- "@types/react-dom": "^18.2.25",
+ "@types/lodash": "^4.17.6",
+ "@types/mocha": "^10.0.7",
+ "@types/node": "^18.19.39",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
"@types/react-test-renderer": "^18.3.0",
"@types/requestidlecallback": "^0.3.7",
"@types/sinon": "^17.0.3",
"@types/yargs": "^17.0.32",
- "@typescript-eslint/eslint-plugin": "^7.13.1",
- "@typescript-eslint/parser": "^7.13.1",
+ "@typescript-eslint/eslint-plugin": "^7.15.0",
+ "@typescript-eslint/parser": "^7.15.0",
"autoprefixer": "^10.4.19",
"axe-core": "4.9.1",
"babel-loader": "^9.1.3",
- "babel-plugin-istanbul": "^6.1.1",
+ "babel-plugin-istanbul": "^7.0.0",
"babel-plugin-module-resolver": "^5.0.2",
"babel-plugin-optimize-clsx": "^2.6.2",
"babel-plugin-react-remove-properties": "^0.3.0",
@@ -130,10 +128,9 @@
"concurrently": "^8.2.2",
"cpy-cli": "^5.0.0",
"cross-env": "^7.0.3",
- "danger": "^12.3.1",
- "date-fns-jalali-v3": "npm:date-fns-jalali@3.6.0-0",
+ "danger": "^12.3.3",
+ "date-fns-jalali-v3": "npm:date-fns-jalali@3.6.0-1",
"date-fns-v3": "npm:date-fns@3.6.0",
- "enzyme": "^3.11.0",
"eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^18.0.0",
@@ -141,12 +138,12 @@
"eslint-import-resolver-webpack": "^0.13.8",
"eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-import": "^2.29.1",
- "eslint-plugin-jsdoc": "^48.2.12",
- "eslint-plugin-jsx-a11y": "^6.8.0",
+ "eslint-plugin-jsdoc": "^48.5.2",
+ "eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-material-ui": "workspace:^",
"eslint-plugin-mocha": "^10.4.3",
"eslint-plugin-prettier": "^5.1.3",
- "eslint-plugin-react": "^7.34.2",
+ "eslint-plugin-react": "^7.34.3",
"eslint-plugin-react-compiler": "0.0.0-experimental-51a85ea-20240601",
"eslint-plugin-react-hooks": "^4.6.2",
"fast-glob": "^3.3.2",
@@ -165,37 +162,37 @@
"karma-parallel": "^0.3.1",
"karma-sourcemap-loader": "^0.4.0",
"karma-webpack": "^5.0.1",
- "lerna": "^8.1.3",
+ "lerna": "^8.1.6",
"lodash": "^4.17.21",
"markdownlint-cli2": "^0.13.0",
- "mocha": "^10.4.0",
+ "mocha": "^10.6.0",
"moment": "^2.30.1",
"moment-timezone": "^0.5.45",
"nyc": "^17.0.0",
"prettier": "^3.3.2",
"pretty-quick": "^4.0.0",
"process": "^0.11.10",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
"remark": "^13.0.0",
- "rimraf": "^5.0.7",
+ "rimraf": "^5.0.8",
"serve": "^14.2.3",
"sinon": "^16.1.3",
"stream-browserify": "^3.0.0",
"string-replace-loader": "^3.1.0",
"terser-webpack-plugin": "^5.3.10",
- "tsx": "^4.15.5",
- "typescript": "^5.4.5",
+ "tsx": "^4.16.2",
+ "typescript": "^5.5.3",
"unist-util-visit": "^2.0.3",
"util": "^0.12.5",
- "webpack": "^5.92.0",
+ "webpack": "^5.92.1",
"webpack-bundle-analyzer": "^4.10.2",
"webpack-cli": "^5.1.4",
"yargs": "^17.7.2"
},
"resolutions": {
- "react-is": "^18.2.0",
- "@types/node": "^18.19.34"
+ "react-is": "^18.3.1",
+ "@types/node": "^18.19.39"
},
"packageManager": "pnpm@9.4.0",
"engines": {
diff --git a/packages/eslint-plugin-material-ui/package.json b/packages/eslint-plugin-material-ui/package.json
index 347ccb8ca25d..4b55439a52ab 100644
--- a/packages/eslint-plugin-material-ui/package.json
+++ b/packages/eslint-plugin-material-ui/package.json
@@ -1,13 +1,13 @@
{
"name": "eslint-plugin-material-ui",
- "version": "6.3.0",
+ "version": "7.8.0",
"private": true,
"description": "Custom eslint rules for MUI X.",
"main": "src/index.js",
"devDependencies": {
"@types/eslint": "^8.56.10",
- "@typescript-eslint/utils": "^7.13.1",
- "@typescript-eslint/parser": "^7.13.1"
+ "@typescript-eslint/utils": "^7.15.0",
+ "@typescript-eslint/parser": "^7.15.0"
},
"scripts": {
"test": "cd ../../ && cross-env NODE_ENV=test mocha 'packages/eslint-plugin-material-ui/**/*.test.js' --timeout 3000"
diff --git a/packages/x-charts-pro/package.json b/packages/x-charts-pro/package.json
index 945293d1ce0b..c8b36be20a67 100644
--- a/packages/x-charts-pro/package.json
+++ b/packages/x-charts-pro/package.json
@@ -2,7 +2,7 @@
"name": "@mui/x-charts-pro",
"version": "7.7.0",
"private": true,
- "description": "The community edition of the Charts components (MUI X).",
+ "description": "The Pro plan edition of the Charts components (MUI X).",
"author": "MUI Team",
"main": "./src/index.ts",
"license": "SEE LICENSE IN LICENSE",
@@ -42,8 +42,8 @@
"dependencies": {
"@babel/runtime": "^7.24.7",
"@mui/base": "^5.0.0-beta.40",
- "@mui/system": "^5.15.20",
- "@mui/utils": "^5.15.20",
+ "@mui/system": "^5.16.0",
+ "@mui/utils": "^5.16.0",
"@mui/x-charts": "workspace:*",
"@mui/x-license": "workspace:*",
"@react-spring/rafz": "^9.7.3",
@@ -81,7 +81,7 @@
"@types/d3-shape": "^3.1.6",
"@types/prop-types": "^15.7.12",
"csstype": "^3.1.3",
- "rimraf": "^5.0.7"
+ "rimraf": "^5.0.8"
},
"exports": {
".": {
diff --git a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx
index 4f474bf9de4b..c701d6aa7146 100644
--- a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx
+++ b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx
@@ -9,10 +9,16 @@ import { ChartsAxisHighlight } from '@mui/x-charts/ChartsAxisHighlight';
import { ChartsTooltip } from '@mui/x-charts/ChartsTooltip';
import { ChartsClipPath } from '@mui/x-charts/ChartsClipPath';
import { useBarChartProps } from '@mui/x-charts/internals';
+import { BarPlotProps } from '@mui/x-charts';
import { ResponsiveChartContainerPro } from '../ResponsiveChartContainerPro';
+import { ZoomSetup } from '../context/ZoomProvider/ZoomSetup';
+import { useZoom } from '../context/ZoomProvider/useZoom';
export interface BarChartProProps extends BarChartProps {
- // TODO: Add zoom props
+ /**
+ * If `true`, the chart will be zoomable.
+ */
+ zoom?: boolean;
}
/**
@@ -27,6 +33,7 @@ export interface BarChartProProps extends BarChartProps {
* - [BarChart API](https://mui.com/x/api/charts/bar-chart/)
*/
const BarChartPro = React.forwardRef(function BarChartPro(props: BarChartProProps, ref) {
+ const { zoom, ...restProps } = props;
const {
chartContainerProps,
barPlotProps,
@@ -39,16 +46,15 @@ const BarChartPro = React.forwardRef(function BarChartPro(props: BarChartProProp
axisHighlightProps,
legendProps,
tooltipProps,
-
children,
- } = useBarChartProps(props);
+ } = useBarChartProps(restProps);
return (
{props.onAxisClick && }
{props.grid && }
-
+
@@ -56,9 +62,16 @@ const BarChartPro = React.forwardRef(function BarChartPro(props: BarChartProProp
{!props.loading && }
+ {zoom && }
{children}
);
});
+function BarChartPlotZoom(props: BarPlotProps) {
+ const { isInteracting } = useZoom();
+
+ return ;
+}
+
export { BarChartPro };
diff --git a/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx b/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx
index 8361a9df7882..8c0e759232b2 100644
--- a/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx
+++ b/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx
@@ -4,7 +4,6 @@ import { ChartContainerProps } from '@mui/x-charts/ChartContainer';
import { ChartsSurface } from '@mui/x-charts/ChartsSurface';
import { HighlightedProvider, ZAxisContextProvider } from '@mui/x-charts/context';
import {
- CartesianContextProvider,
ChartsAxesGradients,
ColorProvider,
DrawingProvider,
@@ -14,6 +13,8 @@ import {
} from '@mui/x-charts/internals';
import { useLicenseVerifier } from '@mui/x-license/useLicenseVerifier';
import { getReleaseInfo } from '../internals/utils/releaseInfo';
+import { CartesianContextProviderPro } from '../context/CartesianProviderPro';
+import { ZoomProvider } from '../context/ZoomProvider';
const releaseInfo = getReleaseInfo();
@@ -63,35 +64,37 @@ const ChartContainerPro = React.forwardRef(function ChartContainer(
dataset={dataset}
seriesFormatters={seriesFormatters}
>
-
-
-
-
-
+
+
+
+
-
- {children}
-
-
-
-
-
+
+
+ {children}
+
+
+
+
+
+
diff --git a/packages/x-charts-pro/src/Heatmap/DefaultHeatmapTooltip.tsx b/packages/x-charts-pro/src/Heatmap/DefaultHeatmapTooltip.tsx
new file mode 100644
index 000000000000..7fc3974a8a8f
--- /dev/null
+++ b/packages/x-charts-pro/src/Heatmap/DefaultHeatmapTooltip.tsx
@@ -0,0 +1,68 @@
+import * as React from 'react';
+import clsx from 'clsx';
+import {
+ ChartsItemContentProps,
+ ChartsTooltipPaper,
+ ChartsTooltipTable,
+ ChartsTooltipRow,
+ ChartsTooltipCell,
+ ChartsTooltipMark,
+} from '@mui/x-charts/ChartsTooltip';
+import { useXAxis, useYAxis } from '@mui/x-charts/hooks';
+import { getLabel } from '@mui/x-charts/internals';
+
+/**
+ * @ignore - do not document.
+ */
+export function DefaultHeatmapTooltip(props: ChartsItemContentProps<'heatmap'>) {
+ const { series, itemData, sx, classes, getColor } = props;
+
+ const xAxis = useXAxis();
+ const yAxis = useYAxis();
+
+ if (itemData.dataIndex === undefined || !series.data[itemData.dataIndex]) {
+ return null;
+ }
+
+ const color = getColor(itemData.dataIndex);
+
+ const valueItem = series.data[itemData.dataIndex];
+ const [xIndex, yIndex] = valueItem;
+
+ const formattedX =
+ xAxis.valueFormatter?.(xAxis.data![xIndex], { location: 'tooltip' }) ??
+ xAxis.data![xIndex].toLocaleString();
+ const formattedY =
+ yAxis.valueFormatter?.(yAxis.data![yIndex], { location: 'tooltip' }) ??
+ yAxis.data![yIndex].toLocaleString();
+ const formattedValue = series.valueFormatter(valueItem, { dataIndex: itemData.dataIndex });
+
+ const seriesLabel = getLabel(series.label, 'tooltip');
+
+ return (
+
+
+
+
+ {formattedX}
+ {formattedX && formattedY && }
+ {formattedY}
+
+
+
+
+
+
+
+
+ {seriesLabel}
+
+
+ {formattedValue}
+
+
+
+
+
+ );
+}
diff --git a/packages/x-charts-pro/src/Heatmap/Heatmap.tsx b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx
new file mode 100644
index 000000000000..af5f928f8330
--- /dev/null
+++ b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx
@@ -0,0 +1,204 @@
+import * as React from 'react';
+import { interpolateRgbBasis } from 'd3-interpolate';
+import useId from '@mui/utils/useId';
+import { ChartsAxis, ChartsAxisProps } from '@mui/x-charts/ChartsAxis';
+import {
+ ChartsTooltip,
+ ChartsTooltipProps,
+ ChartsTooltipSlotProps,
+ ChartsTooltipSlots,
+} from '@mui/x-charts/ChartsTooltip';
+import {
+ MakeOptional,
+ ChartsAxisSlots,
+ ChartsAxisSlotProps,
+ ChartsXAxisProps,
+ ChartsYAxisProps,
+ AxisConfig,
+} from '@mui/x-charts/internals';
+import { ChartsClipPath } from '@mui/x-charts/ChartsClipPath';
+import {
+ ChartsOnAxisClickHandler,
+ ChartsOnAxisClickHandlerProps,
+} from '@mui/x-charts/ChartsOnAxisClickHandler';
+import {
+ ChartsOverlay,
+ ChartsOverlayProps,
+ ChartsOverlaySlotProps,
+ ChartsOverlaySlots,
+} from '@mui/x-charts/ChartsOverlay';
+import {
+ ResponsiveChartContainerPro,
+ ResponsiveChartContainerProProps,
+} from '../ResponsiveChartContainerPro';
+import { HeatmapSeriesType } from '../models/seriesType/heatmap';
+import { HeatmapPlot } from './HeatmapPlot';
+import { plugin as heatmapPlugin } from './plugin';
+import { DefaultHeatmapTooltip } from './DefaultHeatmapTooltip';
+import { HeatmapItemSlotProps, HeatmapItemSlots } from './HeatmapItem';
+
+export interface HeatmapSlots
+ extends ChartsAxisSlots,
+ Omit, 'axisContent'>,
+ ChartsOverlaySlots,
+ HeatmapItemSlots {}
+export interface HeatmapSlotProps
+ extends ChartsAxisSlotProps,
+ Omit, 'axisContent'>,
+ ChartsOverlaySlotProps,
+ HeatmapItemSlotProps {}
+
+export interface HeatmapProps
+ extends Omit,
+ Omit,
+ Omit,
+ ChartsOnAxisClickHandlerProps {
+ /**
+ * The configuration of the x-axes.
+ * If not provided, a default axis config is used.
+ * An array of [[AxisConfig]] objects.
+ */
+ xAxis: MakeOptional, 'id' | 'scaleType'>[];
+ /**
+ * The configuration of the y-axes.
+ * If not provided, a default axis config is used.
+ * An array of [[AxisConfig]] objects.
+ */
+ yAxis: MakeOptional, 'id' | 'scaleType'>[];
+ /**
+ * The series to display in the bar chart.
+ * An array of [[HeatmapSeriesType]] objects.
+ */
+ series: MakeOptional[];
+ /**
+ * The configuration of the tooltip.
+ * @see See {@link https://mui.com/x/react-charts/tooltip/ tooltip docs} for more details.
+ */
+ tooltip?: ChartsTooltipProps<'heatmap'>;
+ /**
+ * Overridable component slots.
+ * @default {}
+ */
+ slots?: HeatmapSlots;
+ /**
+ * The props used for each component slot.
+ * @default {}
+ */
+ slotProps?: HeatmapSlotProps;
+}
+
+// The GnBu: https://github.com/d3/d3-scale-chromatic/blob/main/src/sequential-multi/GnBu.js
+const defaultColorMap = interpolateRgbBasis([
+ '#f7fcf0',
+ '#e0f3db',
+ '#ccebc5',
+ '#a8ddb5',
+ '#7bccc4',
+ '#4eb3d3',
+ '#2b8cbe',
+ '#0868ac',
+ '#084081',
+]);
+
+export const Heatmap = React.forwardRef(function Heatmap(props: HeatmapProps, ref) {
+ const {
+ xAxis,
+ yAxis,
+ zAxis,
+ series,
+ width,
+ height,
+ margin,
+ colors,
+ dataset,
+ sx,
+ tooltip,
+ topAxis,
+ leftAxis,
+ rightAxis,
+ bottomAxis,
+ onAxisClick,
+ children,
+ slots,
+ slotProps,
+ loading,
+ highlightedItem,
+ onHighlightChange,
+ } = props;
+
+ const id = useId();
+ const clipPathId = `${id}-clip-path`;
+
+ const defaultizedXAxis = React.useMemo(
+ () => xAxis.map((axis) => ({ scaleType: 'band' as const, categoryGapRatio: 0, ...axis })),
+ [xAxis],
+ );
+
+ const defaultizedYAxis = React.useMemo(
+ () => yAxis.map((axis) => ({ scaleType: 'band' as const, categoryGapRatio: 0, ...axis })),
+ [yAxis],
+ );
+
+ const defaultizedZAxis = React.useMemo(
+ () =>
+ zAxis ?? [
+ {
+ colorMap: {
+ type: 'continuous',
+ min: 0,
+ max: 100,
+ color: defaultColorMap,
+ },
+ } as const,
+ ],
+ [zAxis],
+ );
+
+ return (
+ ({
+ type: 'heatmap',
+ ...s,
+ }))}
+ width={width}
+ height={height}
+ margin={margin}
+ xAxis={defaultizedXAxis}
+ yAxis={defaultizedYAxis}
+ zAxis={defaultizedZAxis}
+ colors={colors}
+ dataset={dataset}
+ sx={sx}
+ disableAxisListener
+ highlightedItem={highlightedItem}
+ onHighlightChange={onHighlightChange}
+ >
+ {onAxisClick && }
+
+
+
+
+
+
+ {!loading && (
+
+ )}
+
+ {children}
+
+ );
+});
diff --git a/packages/x-charts-pro/src/Heatmap/HeatmapItem.tsx b/packages/x-charts-pro/src/Heatmap/HeatmapItem.tsx
new file mode 100644
index 000000000000..c82c4b27ba2c
--- /dev/null
+++ b/packages/x-charts-pro/src/Heatmap/HeatmapItem.tsx
@@ -0,0 +1,102 @@
+import * as React from 'react';
+import { styled } from '@mui/material/styles';
+import { useSlotProps } from '@mui/base/utils';
+import composeClasses from '@mui/utils/composeClasses';
+import { useItemHighlighted } from '@mui/x-charts/context';
+import { useInteractionItemProps, SeriesId } from '@mui/x-charts/internals';
+import { HeatmapClasses, getHeatmapUtilityClass } from './heatmapClasses';
+
+export interface HeatmapItemSlots {
+ /**
+ * The component that renders the heatmap cell.
+ * @default HeatmapCell
+ */
+ cell?: React.ElementType;
+}
+
+export interface HeatmapItemSlotProps {
+ cell?: Partial>;
+}
+
+export interface HeatmapItemProps {
+ dataIndex: number;
+ seriesId: SeriesId;
+ value: number;
+ width: number;
+ height: number;
+ x: number;
+ y: number;
+ color: string;
+ /**
+ * The props used for each component slot.
+ * @default {}
+ */
+ slotProps?: HeatmapItemSlotProps;
+ /**
+ * Overridable component slots.
+ * @default {}
+ */
+ slots?: HeatmapItemSlots;
+}
+
+export interface HeatmapItemOwnerState {
+ seriesId: SeriesId;
+ dataIndex: number;
+ color: string;
+ isFaded: boolean;
+ isHighlighted: boolean;
+ classes?: Partial;
+}
+
+const HeatmapCell = styled('rect', {
+ name: 'MuiHeatmap',
+ slot: 'Cell',
+ overridesResolver: (_, styles) => styles.arc,
+})<{ ownerState: HeatmapItemOwnerState }>(({ ownerState }) => ({
+ filter:
+ (ownerState.isHighlighted && 'saturate(120%)') ||
+ (ownerState.isFaded && 'saturate(80%)') ||
+ undefined,
+ fill: ownerState.color,
+ shapeRendering: 'crispEdges',
+}));
+
+const useUtilityClasses = (ownerState: HeatmapItemOwnerState) => {
+ const { classes, seriesId, isFaded, isHighlighted } = ownerState;
+ const slots = {
+ cell: ['cell', `series-${seriesId}`, isFaded && 'faded', isHighlighted && 'highlighted'],
+ };
+ return composeClasses(slots, getHeatmapUtilityClass, classes);
+};
+
+export function HeatmapItem(props: HeatmapItemProps) {
+ const { seriesId, dataIndex, color, value, slotProps = {}, slots = {}, ...other } = props;
+
+ const getInteractionItemProps = useInteractionItemProps();
+ const { isFaded, isHighlighted } = useItemHighlighted({
+ seriesId,
+ dataIndex,
+ });
+
+ const ownerState = {
+ seriesId,
+ dataIndex,
+ color,
+ value,
+ isFaded,
+ isHighlighted,
+ };
+ const classes = useUtilityClasses(ownerState);
+
+ const Cell = slots?.cell ?? HeatmapCell;
+ const cellProps = useSlotProps({
+ elementType: Cell,
+ additionalProps: { ...getInteractionItemProps({ type: 'heatmap', seriesId, dataIndex }) },
+ externalForwardedProps: { ...other },
+ externalSlotProps: slotProps.cell,
+ ownerState,
+ className: classes.cell,
+ });
+
+ return | ;
+}
diff --git a/packages/x-charts-pro/src/Heatmap/HeatmapPlot.tsx b/packages/x-charts-pro/src/Heatmap/HeatmapPlot.tsx
new file mode 100644
index 000000000000..42c9d8e0fe4a
--- /dev/null
+++ b/packages/x-charts-pro/src/Heatmap/HeatmapPlot.tsx
@@ -0,0 +1,49 @@
+import * as React from 'react';
+import { useXScale, useYScale, useZColorScale } from '@mui/x-charts/hooks';
+import { useHeatmapSeries } from '../hooks/useSeries';
+import { HeatmapItem, HeatmapItemProps } from './HeatmapItem';
+
+export interface HeatmapPlotProps extends Pick {}
+
+export function HeatmapPlot(props: HeatmapPlotProps) {
+ const xScale = useXScale<'band'>();
+ const yScale = useYScale<'band'>();
+ const colorScale = useZColorScale()!;
+ const series = useHeatmapSeries();
+
+ const xDomain = xScale.domain();
+ const yDomain = yScale.domain();
+
+ if (!series || series.seriesOrder.length === 0) {
+ return null;
+ }
+ const seriesToDisplay = series.series[series.seriesOrder[0]];
+
+ return (
+
+ {seriesToDisplay.data.map(([xIndex, yIndex, value], dataIndex) => {
+ const x = xScale(xDomain[xIndex]);
+ const y = yScale(yDomain[yIndex]);
+ const color = colorScale?.(value);
+ if (x === undefined || y === undefined || !color) {
+ return null;
+ }
+ return (
+
+ );
+ })}
+
+ );
+}
diff --git a/packages/x-charts-pro/src/Heatmap/extremums.ts b/packages/x-charts-pro/src/Heatmap/extremums.ts
new file mode 100644
index 000000000000..a0bdab36b726
--- /dev/null
+++ b/packages/x-charts-pro/src/Heatmap/extremums.ts
@@ -0,0 +1,9 @@
+import { ExtremumGetter } from '@mui/x-charts/internals';
+
+export const getBaseExtremum: ExtremumGetter<'heatmap'> = (params) => {
+ const { axis } = params;
+
+ const minX = Math.min(...(axis.data ?? []));
+ const maxX = Math.max(...(axis.data ?? []));
+ return [minX, maxX];
+};
diff --git a/packages/x-charts-pro/src/Heatmap/formatter.ts b/packages/x-charts-pro/src/Heatmap/formatter.ts
new file mode 100644
index 000000000000..17e0b2a68ae3
--- /dev/null
+++ b/packages/x-charts-pro/src/Heatmap/formatter.ts
@@ -0,0 +1,23 @@
+import { Formatter, SeriesId } from '@mui/x-charts/internals';
+import { DefaultizedHeatmapSeriesType } from '../models/seriesType/heatmap';
+
+const formatter: Formatter<'heatmap'> = (params) => {
+ const { series, seriesOrder } = params;
+
+ const defaultizedSeries: Record = {};
+ Object.keys(series).forEach((seriesId) => {
+ defaultizedSeries[seriesId] = {
+ // Defaultize the data and the value formatter.
+ valueFormatter: (v) => v[2].toString(),
+ data: [],
+ ...series[seriesId],
+ };
+ });
+
+ return {
+ series: defaultizedSeries,
+ seriesOrder,
+ };
+};
+
+export default formatter;
diff --git a/packages/x-charts-pro/src/Heatmap/getColor.ts b/packages/x-charts-pro/src/Heatmap/getColor.ts
new file mode 100644
index 000000000000..f51d38225023
--- /dev/null
+++ b/packages/x-charts-pro/src/Heatmap/getColor.ts
@@ -0,0 +1,24 @@
+import { DefaultizedSeriesType } from '@mui/x-charts';
+import { AxisDefaultized, ZAxisDefaultized } from '@mui/x-charts/internals';
+
+export default function getColor(
+ series: DefaultizedSeriesType<'heatmap'>,
+ xAxis?: AxisDefaultized,
+ yAxis?: AxisDefaultized,
+ zAxis?: ZAxisDefaultized,
+) {
+ const zColorScale = zAxis?.colorScale;
+
+ if (zColorScale) {
+ return (dataIndex: number) => {
+ const value = series.data[dataIndex];
+ const color = zColorScale(value[2]);
+ if (color === null) {
+ return '';
+ }
+ return color;
+ };
+ }
+
+ return () => '';
+}
diff --git a/packages/x-charts-pro/src/Heatmap/heatmapClasses.ts b/packages/x-charts-pro/src/Heatmap/heatmapClasses.ts
new file mode 100644
index 000000000000..2f50b4bdfa59
--- /dev/null
+++ b/packages/x-charts-pro/src/Heatmap/heatmapClasses.ts
@@ -0,0 +1,28 @@
+import {
+ unstable_generateUtilityClass as generateUtilityClass,
+ unstable_generateUtilityClasses as generateUtilityClasses,
+} from '@mui/utils';
+
+export interface HeatmapClasses {
+ /** Styles applied to the heatmap cells. */
+ cell: string;
+ /** Styles applied to the cell element if highlighted. */
+ highlighted: string;
+ /** Styles applied to the cell element if faded. */
+ faded: string;
+}
+
+export type HeatmapClassKey = keyof HeatmapClasses;
+
+export function getHeatmapUtilityClass(slot: string) {
+ // Those should be common to all charts
+ if (['highlighted', 'faded'].includes(slot)) {
+ return generateUtilityClass('Charts', slot);
+ }
+ return generateUtilityClass('MuiHeatmap', slot);
+}
+export const heatmapClasses: HeatmapClasses = {
+ ...generateUtilityClasses('MuiHeatmap', ['cell']),
+ highlighted: 'Charts-highlighted',
+ faded: 'Charts-faded',
+};
diff --git a/packages/x-charts-pro/src/Heatmap/index.ts b/packages/x-charts-pro/src/Heatmap/index.ts
new file mode 100644
index 000000000000..042c1a78bacb
--- /dev/null
+++ b/packages/x-charts-pro/src/Heatmap/index.ts
@@ -0,0 +1,4 @@
+export { Heatmap as UnstableHeatmap } from './Heatmap';
+export { HeatmapPlot as UnstableHeatmapPlot } from './HeatmapPlot';
+export * from './DefaultHeatmapTooltip';
+export * from './heatmapClasses';
diff --git a/packages/x-charts-pro/src/Heatmap/plugin.ts b/packages/x-charts-pro/src/Heatmap/plugin.ts
new file mode 100644
index 000000000000..c6d7b42077af
--- /dev/null
+++ b/packages/x-charts-pro/src/Heatmap/plugin.ts
@@ -0,0 +1,12 @@
+import { ChartsPluginType } from '@mui/x-charts/models';
+import { getBaseExtremum } from './extremums';
+import formatter from './formatter';
+import getColor from './getColor';
+
+export const plugin: ChartsPluginType<'heatmap'> = {
+ seriesType: 'heatmap',
+ seriesFormatter: formatter,
+ colorProcessor: getColor,
+ xExtremumGetter: getBaseExtremum,
+ yExtremumGetter: getBaseExtremum,
+};
diff --git a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx
index 384a56b509a4..108fcb795a57 100644
--- a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx
+++ b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx
@@ -15,10 +15,16 @@ import { ChartsLegend } from '@mui/x-charts/ChartsLegend';
import { ChartsTooltip } from '@mui/x-charts/ChartsTooltip';
import { ChartsClipPath } from '@mui/x-charts/ChartsClipPath';
import { useLineChartProps } from '@mui/x-charts/internals';
+import { MarkPlotProps } from '@mui/x-charts';
import { ResponsiveChartContainerPro } from '../ResponsiveChartContainerPro';
+import { ZoomSetup } from '../context/ZoomProvider/ZoomSetup';
+import { useZoom } from '../context/ZoomProvider/useZoom';
export interface LineChartProProps extends LineChartProps {
- // TODO: Add zoom props
+ /**
+ * If `true`, the chart will be zoomable.
+ */
+ zoom?: boolean;
}
/**
@@ -32,6 +38,7 @@ export interface LineChartProProps extends LineChartProps {
* - [LineChart API](https://mui.com/x/api/charts/line-chart/)
*/
const LineChartPro = React.forwardRef(function LineChartPro(props: LineChartProProps, ref) {
+ const { zoom, ...restProps } = props;
const {
chartContainerProps,
axisClickHandlerProps,
@@ -47,9 +54,8 @@ const LineChartPro = React.forwardRef(function LineChartPro(props: LineChartProP
lineHighlightPlotProps,
legendProps,
tooltipProps,
-
children,
- } = useLineChartProps(props);
+ } = useLineChartProps(restProps);
return (
@@ -62,14 +68,20 @@ const LineChartPro = React.forwardRef(function LineChartPro(props: LineChartProP
-
+
{!props.loading && }
+ {zoom && }
{children}
);
});
+function MarkPlotZoom(props: MarkPlotProps) {
+ const { isInteracting } = useZoom();
+ return ;
+}
+
export { LineChartPro };
diff --git a/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.test.tsx b/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.test.tsx
index 007e7217a8e3..4d5e4565a271 100644
--- a/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.test.tsx
+++ b/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.test.tsx
@@ -22,7 +22,7 @@ describe(' - License', () => {
).toErrorDev(['MUI X: Missing license key.']);
await waitFor(() => {
- expect(screen.findAllByText('MUI X Missing license key')).to.not.equal(null);
+ expect(screen.findAllByText('MUI X Missing license key')).not.to.equal(null);
});
});
});
diff --git a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx
index ad91bb1f9005..557bbf228be6 100644
--- a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx
+++ b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx
@@ -10,9 +10,13 @@ import { ChartsAxisHighlight } from '@mui/x-charts/ChartsAxisHighlight';
import { ChartsTooltip } from '@mui/x-charts/ChartsTooltip';
import { useScatterChartProps } from '@mui/x-charts/internals';
import { ResponsiveChartContainerPro } from '../ResponsiveChartContainerPro';
+import { ZoomSetup } from '../context/ZoomProvider/ZoomSetup';
export interface ScatterChartProProps extends ScatterChartProps {
- // TODO: Add zoom props
+ /**
+ * If `true`, the chart will be zoomable.
+ */
+ zoom?: boolean;
}
/**
@@ -29,6 +33,7 @@ const ScatterChartPro = React.forwardRef(function ScatterChartPro(
props: ScatterChartProProps,
ref,
) {
+ const { zoom, ...restProps } = props;
const {
chartContainerProps,
zAxisProps,
@@ -41,7 +46,7 @@ const ScatterChartPro = React.forwardRef(function ScatterChartPro(
axisHighlightProps,
tooltipProps,
children,
- } = useScatterChartProps(props);
+ } = useScatterChartProps(restProps);
return (
@@ -53,6 +58,7 @@ const ScatterChartPro = React.forwardRef(function ScatterChartPro(
{!props.loading && }
+ {zoom && }
{children}
diff --git a/packages/x-charts-pro/src/context/CartesianProviderPro/CartesianProviderPro.tsx b/packages/x-charts-pro/src/context/CartesianProviderPro/CartesianProviderPro.tsx
new file mode 100644
index 000000000000..a3b30b79037d
--- /dev/null
+++ b/packages/x-charts-pro/src/context/CartesianProviderPro/CartesianProviderPro.tsx
@@ -0,0 +1,59 @@
+import * as React from 'react';
+import {
+ useDrawingArea,
+ useSeries,
+ CartesianContext,
+ CartesianContextProviderProps,
+ cartesianProviderUtils,
+} from '@mui/x-charts/internals';
+import { useZoom } from '../ZoomProvider/useZoom';
+
+const { normalizeAxis, computeValue } = cartesianProviderUtils;
+
+export interface CartesianContextProviderProProps extends CartesianContextProviderProps {}
+
+function CartesianContextProviderPro(props: CartesianContextProviderProProps) {
+ const {
+ xAxis: inXAxis,
+ yAxis: inYAxis,
+ dataset,
+ xExtremumGetters,
+ yExtremumGetters,
+ children,
+ } = props;
+
+ const formattedSeries = useSeries();
+ const drawingArea = useDrawingArea();
+ const { zoomRange } = useZoom();
+
+ const xAxis = React.useMemo(() => normalizeAxis(inXAxis, dataset, 'x'), [inXAxis, dataset]);
+
+ const yAxis = React.useMemo(() => normalizeAxis(inYAxis, dataset, 'y'), [inYAxis, dataset]);
+
+ const xValues = React.useMemo(
+ () => computeValue(drawingArea, formattedSeries, xAxis, xExtremumGetters, 'x', zoomRange),
+ [drawingArea, formattedSeries, xAxis, xExtremumGetters, zoomRange],
+ );
+
+ const yValues = React.useMemo(
+ () => computeValue(drawingArea, formattedSeries, yAxis, yExtremumGetters, 'y'),
+ [drawingArea, formattedSeries, yAxis, yExtremumGetters],
+ );
+
+ const value = React.useMemo(
+ () => ({
+ isInitialized: true,
+ data: {
+ xAxis: xValues.axis,
+ yAxis: yValues.axis,
+ xAxisIds: xValues.axisIds,
+ yAxisIds: yValues.axisIds,
+ },
+ }),
+ [xValues, yValues],
+ );
+
+ return {children} ;
+}
+
+export { CartesianContextProviderPro };
diff --git a/packages/x-charts-pro/src/context/CartesianProviderPro/index.ts b/packages/x-charts-pro/src/context/CartesianProviderPro/index.ts
new file mode 100644
index 000000000000..6b24b2347017
--- /dev/null
+++ b/packages/x-charts-pro/src/context/CartesianProviderPro/index.ts
@@ -0,0 +1 @@
+export * from './CartesianProviderPro';
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/ZoomContext.ts b/packages/x-charts-pro/src/context/ZoomProvider/ZoomContext.ts
new file mode 100644
index 000000000000..aa7ae3688708
--- /dev/null
+++ b/packages/x-charts-pro/src/context/ZoomProvider/ZoomContext.ts
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import { Initializable } from '@mui/x-charts/internals';
+
+export type ZoomState = {
+ zoomRange: [number, number];
+ setZoomRange: (range: [number, number]) => void;
+ isInteracting: boolean;
+ setIsInteracting: (isInteracting: boolean) => void;
+};
+
+export const ZoomContext = React.createContext>({
+ isInitialized: false,
+ data: {
+ zoomRange: [0, 100],
+ setZoomRange: () => {},
+ isInteracting: false,
+ setIsInteracting: () => {},
+ },
+});
+
+if (process.env.NODE_ENV !== 'production') {
+ ZoomContext.displayName = 'ZoomContext';
+}
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/ZoomProvider.tsx b/packages/x-charts-pro/src/context/ZoomProvider/ZoomProvider.tsx
new file mode 100644
index 000000000000..a2d235f5a5ef
--- /dev/null
+++ b/packages/x-charts-pro/src/context/ZoomProvider/ZoomProvider.tsx
@@ -0,0 +1,26 @@
+import * as React from 'react';
+import { ZoomContext } from './ZoomContext';
+
+type ZoomProviderProps = {
+ children: React.ReactNode;
+};
+
+export function ZoomProvider({ children }: ZoomProviderProps) {
+ const [zoomRange, setZoomRange] = React.useState<[number, number]>([0, 100]);
+ const [isInteracting, setIsInteracting] = React.useState(false);
+
+ const value = React.useMemo(
+ () => ({
+ isInitialized: true,
+ data: {
+ zoomRange,
+ setZoomRange,
+ isInteracting,
+ setIsInteracting,
+ },
+ }),
+ [zoomRange, setZoomRange, isInteracting, setIsInteracting],
+ );
+
+ return {children} ;
+}
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/ZoomSetup.ts b/packages/x-charts-pro/src/context/ZoomProvider/ZoomSetup.ts
new file mode 100644
index 000000000000..072baa872d42
--- /dev/null
+++ b/packages/x-charts-pro/src/context/ZoomProvider/ZoomSetup.ts
@@ -0,0 +1,10 @@
+import { useSetupPan } from './useSetupPan';
+import { useSetupZoom } from './useSetupZoom';
+
+function ZoomSetup() {
+ useSetupZoom();
+ useSetupPan();
+ return null;
+}
+
+export { ZoomSetup };
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/index.ts b/packages/x-charts-pro/src/context/ZoomProvider/index.ts
new file mode 100644
index 000000000000..04bb323d80ae
--- /dev/null
+++ b/packages/x-charts-pro/src/context/ZoomProvider/index.ts
@@ -0,0 +1,3 @@
+export * from './ZoomContext';
+export * from './ZoomProvider';
+export * from './useSetupZoom';
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/useSetupPan.ts b/packages/x-charts-pro/src/context/ZoomProvider/useSetupPan.ts
new file mode 100644
index 000000000000..65f954405932
--- /dev/null
+++ b/packages/x-charts-pro/src/context/ZoomProvider/useSetupPan.ts
@@ -0,0 +1,113 @@
+import * as React from 'react';
+import { useDrawingArea, useSvgRef } from '@mui/x-charts/hooks';
+import { getSVGPoint } from '@mui/x-charts/internals';
+import { useZoom } from './useZoom';
+
+const MAX_RANGE = 100;
+const MIN_RANGE = 0;
+
+const isPointOutside = (
+ point: { x: number; y: number },
+ area: { left: number; top: number; width: number; height: number },
+) => {
+ const outsideX = point.x < area.left || point.x > area.left + area.width;
+ const outsideY = point.y < area.top || point.y > area.top + area.height;
+ return outsideX || outsideY;
+};
+
+export const useSetupPan = () => {
+ const { zoomRange, setZoomRange, setIsInteracting } = useZoom();
+ const area = useDrawingArea();
+
+ const svgRef = useSvgRef();
+
+ const isDraggingRef = React.useRef(false);
+ const touchStartRef = React.useRef<{ x: number; minX: number; maxX: number } | null>(null);
+ const eventCacheRef = React.useRef([]);
+
+ React.useEffect(() => {
+ const element = svgRef.current;
+ if (element === null) {
+ return () => {};
+ }
+
+ const handlePan = (event: PointerEvent) => {
+ if (element === null || !isDraggingRef.current || eventCacheRef.current.length > 1) {
+ return;
+ }
+
+ if (touchStartRef.current == null) {
+ return;
+ }
+
+ const point = getSVGPoint(element, event);
+ const movementX = point.x - touchStartRef.current.x;
+
+ const max = touchStartRef.current.maxX;
+ const min = touchStartRef.current.minX;
+ const span = max - min;
+
+ let newMinRange = min - (movementX / area.width) * span;
+ let newMaxRange = max - (movementX / area.width) * span;
+
+ if (newMinRange < MIN_RANGE) {
+ newMinRange = MIN_RANGE;
+ newMaxRange = span;
+ }
+
+ if (newMaxRange > MAX_RANGE) {
+ newMaxRange = MAX_RANGE;
+ newMinRange = MAX_RANGE - span;
+ }
+
+ setZoomRange([newMinRange, newMaxRange]);
+ };
+
+ const handleDown = (event: PointerEvent) => {
+ eventCacheRef.current.push(event);
+ const point = getSVGPoint(element, event);
+
+ if (isPointOutside(point, area)) {
+ return;
+ }
+
+ // If there is only one pointer, prevent selecting text
+ if (eventCacheRef.current.length === 1) {
+ event.preventDefault();
+ }
+
+ isDraggingRef.current = true;
+ setIsInteracting(true);
+
+ touchStartRef.current = {
+ x: point.x,
+ minX: zoomRange[0],
+ maxX: zoomRange[1],
+ };
+ };
+
+ const handleUp = (event: PointerEvent) => {
+ eventCacheRef.current.splice(
+ eventCacheRef.current.findIndex((e) => e.pointerId === event.pointerId),
+ 1,
+ );
+ setIsInteracting(false);
+ isDraggingRef.current = false;
+ touchStartRef.current = null;
+ };
+
+ element.addEventListener('pointerdown', handleDown);
+ document.addEventListener('pointermove', handlePan);
+ document.addEventListener('pointerup', handleUp);
+ document.addEventListener('pointercancel', handleUp);
+ document.addEventListener('pointerleave', handleUp);
+
+ return () => {
+ element.removeEventListener('pointerdown', handleDown);
+ document.removeEventListener('pointermove', handlePan);
+ document.removeEventListener('pointerup', handleUp);
+ document.removeEventListener('pointercancel', handleUp);
+ document.removeEventListener('pointerleave', handleUp);
+ };
+ }, [area, svgRef, isDraggingRef, zoomRange, setZoomRange, setIsInteracting]);
+};
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/useSetupZoom.ts b/packages/x-charts-pro/src/context/ZoomProvider/useSetupZoom.ts
new file mode 100644
index 000000000000..c32ef62edd90
--- /dev/null
+++ b/packages/x-charts-pro/src/context/ZoomProvider/useSetupZoom.ts
@@ -0,0 +1,280 @@
+import * as React from 'react';
+import { useDrawingArea, useSvgRef } from '@mui/x-charts/hooks';
+import { getSVGPoint } from '@mui/x-charts/internals';
+import { useZoom } from './useZoom';
+
+const MAX_RANGE = 100;
+const MIN_RANGE = 0;
+
+const MIN_ALLOWED_SPAN = 10;
+const MAX_ALLOWED_SPAN = 100;
+
+/**
+ * Helper to get the range (in percents of a reference range) corresponding to a given scale.
+ * @param centerRatio {number} The ratio of the point that should not move between the previous and next range.
+ * @param scaleRatio {number} The target scale ratio.
+ * @returns The range to display.
+ */
+const zoomAtPoint = (
+ centerRatio: number,
+ scaleRatio: number,
+ currentRange: readonly [number, number],
+) => {
+ const [minRange, maxRange] = currentRange;
+
+ const point = minRange + centerRatio * (maxRange - minRange);
+
+ let newMinRange = (minRange + point * (scaleRatio - 1)) / scaleRatio;
+ let newMaxRange = (maxRange + point * (scaleRatio - 1)) / scaleRatio;
+
+ let minSpillover = 0;
+ let maxSpillover = 0;
+
+ if (newMinRange < MIN_RANGE) {
+ minSpillover = Math.abs(newMinRange);
+ newMinRange = MIN_RANGE;
+ }
+ if (newMaxRange > MAX_RANGE) {
+ maxSpillover = Math.abs(newMaxRange - MAX_RANGE);
+ newMaxRange = MAX_RANGE;
+ }
+
+ if (minSpillover > 0 && maxSpillover > 0) {
+ return [MIN_RANGE, MAX_RANGE];
+ }
+
+ newMaxRange += minSpillover;
+ newMinRange -= maxSpillover;
+
+ newMinRange = Math.min(MAX_RANGE - MIN_ALLOWED_SPAN, Math.max(MIN_RANGE, newMinRange));
+ newMaxRange = Math.max(MIN_ALLOWED_SPAN, Math.min(MAX_RANGE, newMaxRange));
+
+ return [newMinRange, newMaxRange];
+};
+
+const isPointOutside = (
+ point: { x: number; y: number },
+ area: { left: number; top: number; width: number; height: number },
+) => {
+ const outsideX = point.x < area.left || point.x > area.left + area.width;
+ const outsideY = point.y < area.top || point.y > area.top + area.height;
+ return outsideX || outsideY;
+};
+
+export const useSetupZoom = () => {
+ const { zoomRange, setZoomRange } = useZoom();
+ const area = useDrawingArea();
+
+ const svgRef = useSvgRef();
+ const eventCacheRef = React.useRef([]);
+ const eventPrevDiff = React.useRef(0);
+
+ React.useEffect(() => {
+ const element = svgRef.current;
+ if (element === null) {
+ return () => {};
+ }
+
+ const wheelHandler = (event: WheelEvent) => {
+ if (element === null) {
+ return;
+ }
+
+ const point = getSVGPoint(element, event);
+
+ if (isPointOutside(point, area)) {
+ return;
+ }
+
+ event.preventDefault();
+
+ const centerRatio = getHorizontalCenterRatio(point, area);
+
+ // TODO: make step a config option.
+ const step = 5;
+ const { scaleRatio, isZoomIn } = getWheelScaleRatio(event, step);
+
+ const [newMinRange, newMaxRange] = zoomAtPoint(centerRatio, scaleRatio, zoomRange);
+
+ // TODO: make span a config option.
+ if (!isSpanValid(newMinRange, newMaxRange, isZoomIn)) {
+ return;
+ }
+
+ setZoomRange([newMinRange, newMaxRange]);
+ };
+
+ function pointerDownHandler(event: PointerEvent) {
+ eventCacheRef.current.push(event);
+ }
+
+ function pointerMoveHandler(event: PointerEvent) {
+ if (element === null) {
+ return;
+ }
+
+ const index = eventCacheRef.current.findIndex(
+ (cachedEv) => cachedEv.pointerId === event.pointerId,
+ );
+ eventCacheRef.current[index] = event;
+
+ // If two pointers are down, check for pinch gestures
+ if (eventCacheRef.current.length === 2) {
+ // TODO: make step configurable
+ const step = 5;
+ const { scaleRatio, isZoomIn, curDiff, firstEvent } = getPinchScaleRatio(
+ eventCacheRef.current,
+ eventPrevDiff.current,
+ step,
+ );
+
+ // If the scale ratio is 0, it means the pinch gesture is not valid.
+ if (scaleRatio === 0) {
+ eventPrevDiff.current = curDiff;
+ return;
+ }
+
+ const point = getSVGPoint(element, firstEvent);
+
+ const centerRatio = getHorizontalCenterRatio(point, area);
+
+ const [newMinRange, newMaxRange] = zoomAtPoint(centerRatio, scaleRatio, zoomRange);
+
+ // TODO: make span a config option.
+ if (!isSpanValid(newMinRange, newMaxRange, isZoomIn)) {
+ eventPrevDiff.current = curDiff;
+ return;
+ }
+
+ eventPrevDiff.current = curDiff;
+ setZoomRange([newMinRange, newMaxRange]);
+ }
+ }
+
+ function pointerUpHandler(event: PointerEvent) {
+ eventCacheRef.current.splice(
+ eventCacheRef.current.findIndex((e) => e.pointerId === event.pointerId),
+ 1,
+ );
+
+ if (eventCacheRef.current.length < 2) {
+ eventPrevDiff.current = 0;
+ }
+ }
+
+ element.addEventListener('wheel', wheelHandler);
+ element.addEventListener('pointerdown', pointerDownHandler);
+ element.addEventListener('pointermove', pointerMoveHandler);
+ element.addEventListener('pointerup', pointerUpHandler);
+ element.addEventListener('pointercancel', pointerUpHandler);
+ element.addEventListener('pointerout', pointerUpHandler);
+ element.addEventListener('pointerleave', pointerUpHandler);
+
+ // Prevent zooming the entire page on touch devices
+ element.addEventListener('touchstart', preventDefault);
+ element.addEventListener('touchmove', preventDefault);
+
+ return () => {
+ element.removeEventListener('wheel', wheelHandler);
+ element.removeEventListener('pointerdown', pointerDownHandler);
+ element.removeEventListener('pointermove', pointerMoveHandler);
+ element.removeEventListener('pointerup', pointerUpHandler);
+ element.removeEventListener('pointercancel', pointerUpHandler);
+ element.removeEventListener('pointerout', pointerUpHandler);
+ element.removeEventListener('pointerleave', pointerUpHandler);
+ element.removeEventListener('touchstart', preventDefault);
+ element.removeEventListener('touchmove', preventDefault);
+ };
+ }, [svgRef, setZoomRange, zoomRange, area]);
+};
+
+/**
+ * Checks if the new span is valid.
+ */
+function isSpanValid(minRange: number, maxRange: number, isZoomIn: boolean) {
+ const newSpanPercent = maxRange - minRange;
+
+ // TODO: make span a config option.
+ if (
+ (isZoomIn && newSpanPercent < MIN_ALLOWED_SPAN) ||
+ (!isZoomIn && newSpanPercent > MAX_ALLOWED_SPAN)
+ ) {
+ return false;
+ }
+
+ return true;
+}
+
+function getMultiplier(event: WheelEvent) {
+ const ctrlMultiplier = event.ctrlKey ? 3 : 1;
+
+ // DeltaMode: 0 is pixel, 1 is line, 2 is page
+ // This is defined by the browser.
+ if (event.deltaMode === 1) {
+ return 1 * ctrlMultiplier;
+ }
+ if (event.deltaMode) {
+ return 10 * ctrlMultiplier;
+ }
+ return 0.2 * ctrlMultiplier;
+}
+
+/**
+ * Get the scale ratio and if it's a zoom in or out from a wheel event.
+ */
+function getWheelScaleRatio(event: WheelEvent, step: number) {
+ const deltaY = -event.deltaY;
+ const multiplier = getMultiplier(event);
+ const scaledStep = (step * multiplier * deltaY) / 1000;
+ // Clamp the scale ratio between 0.1 and 1.9 so that the zoom is not too big or too small.
+ const scaleRatio = Math.min(Math.max(1 + scaledStep, 0.1), 1.9);
+ const isZoomIn = deltaY > 0;
+ return { scaleRatio, isZoomIn };
+}
+
+/**
+ * Get the scale ratio and if it's a zoom in or out from a pinch gesture.
+ */
+function getPinchScaleRatio(eventCache: PointerEvent[], prevDiff: number, step: number) {
+ const scaledStep = step / 1000;
+ let scaleRatio: number = 0;
+ let isZoomIn: boolean = false;
+
+ const [firstEvent, secondEvent] = eventCache;
+
+ // Calculate the distance between the two pointers
+ const curDiff = Math.hypot(
+ firstEvent.pageX - secondEvent.pageX,
+ firstEvent.pageY - secondEvent.pageY,
+ );
+
+ const hasMoved = prevDiff > 0;
+
+ if (hasMoved && curDiff > prevDiff) {
+ // The distance between the two pointers has increased
+ scaleRatio = 1 + scaledStep;
+ isZoomIn = true;
+ }
+ if (hasMoved && curDiff < prevDiff) {
+ // The distance between the two pointers has decreased
+ scaleRatio = 1 - scaledStep;
+ isZoomIn = false;
+ }
+
+ return { scaleRatio, isZoomIn, curDiff, firstEvent };
+}
+
+/**
+ * Get the ratio of the point in the horizontal center of the area.
+ */
+function getHorizontalCenterRatio(
+ point: { x: number; y: number },
+ area: { left: number; width: number },
+) {
+ const { left, width } = area;
+ return (point.x - left) / width;
+}
+
+function preventDefault(event: TouchEvent) {
+ event.preventDefault();
+}
diff --git a/packages/x-charts-pro/src/context/ZoomProvider/useZoom.ts b/packages/x-charts-pro/src/context/ZoomProvider/useZoom.ts
new file mode 100644
index 000000000000..59bda96ca395
--- /dev/null
+++ b/packages/x-charts-pro/src/context/ZoomProvider/useZoom.ts
@@ -0,0 +1,7 @@
+import * as React from 'react';
+import { ZoomContext } from './ZoomContext';
+
+export const useZoom = () => {
+ const { data } = React.useContext(ZoomContext);
+ return data;
+};
diff --git a/packages/x-charts-pro/src/hooks/index.ts b/packages/x-charts-pro/src/hooks/index.ts
new file mode 100644
index 000000000000..efe4654a2e1f
--- /dev/null
+++ b/packages/x-charts-pro/src/hooks/index.ts
@@ -0,0 +1 @@
+export { useHeatmapSeries as unstable_useHeatmapSeries } from './useSeries';
diff --git a/packages/x-charts-pro/src/hooks/useSeries.ts b/packages/x-charts-pro/src/hooks/useSeries.ts
new file mode 100644
index 000000000000..81ff3ebc30cb
--- /dev/null
+++ b/packages/x-charts-pro/src/hooks/useSeries.ts
@@ -0,0 +1,15 @@
+import * as React from 'react';
+import { useSeries } from '@mui/x-charts/internals';
+
+/**
+ * Get access to the internal state of heatmap series.
+ * The returned object contains:
+ * - series: a mapping from ids to series attributes.
+ * - seriesOrder: the array of series ids.
+ * @returns { series: Record; seriesOrder: SeriesId[]; } | undefined heatmapSeries
+ */
+export function useHeatmapSeries() {
+ const series = useSeries();
+
+ return React.useMemo(() => series.heatmap, [series.heatmap]);
+}
diff --git a/packages/x-charts-pro/src/index.ts b/packages/x-charts-pro/src/index.ts
index 63807ba5f1f2..eade0525b940 100644
--- a/packages/x-charts-pro/src/index.ts
+++ b/packages/x-charts-pro/src/index.ts
@@ -1,3 +1,6 @@
+import {} from './typeOverloads/modules';
+
+// exports from MIT package
export * from '@mui/x-charts/constants';
export * from '@mui/x-charts/context';
export * from '@mui/x-charts/hooks';
@@ -23,8 +26,10 @@ export * from '@mui/x-charts/SparkLineChart';
export * from '@mui/x-charts/Gauge';
export * from '@mui/x-charts/ChartsSurface';
+// Pro components
+export * from './Heatmap';
export * from './ResponsiveChartContainerPro';
+export * from './ChartContainerPro';
export * from './ScatterChartPro';
export * from './BarChartPro';
export * from './LineChartPro';
-export * from './ChartContainerPro';
diff --git a/packages/x-charts-pro/src/models/index.ts b/packages/x-charts-pro/src/models/index.ts
new file mode 100644
index 000000000000..d06c5e1be13a
--- /dev/null
+++ b/packages/x-charts-pro/src/models/index.ts
@@ -0,0 +1 @@
+export * from './seriesType';
diff --git a/packages/x-charts-pro/src/models/seriesType/heatmap.ts b/packages/x-charts-pro/src/models/seriesType/heatmap.ts
new file mode 100644
index 000000000000..9444f4af60a2
--- /dev/null
+++ b/packages/x-charts-pro/src/models/seriesType/heatmap.ts
@@ -0,0 +1,39 @@
+import {
+ DefaultizedProps,
+ CommonDefaultizedProps,
+ CommonSeriesType,
+ CartesianSeriesType,
+} from '@mui/x-charts/internals';
+
+export type HeatmapValueType = [number, number, number];
+
+export interface HeatmapSeriesType
+ extends Omit, 'color'>,
+ CartesianSeriesType {
+ type: 'heatmap';
+ /**
+ * Data associated to each bar.
+ */
+ data?: HeatmapValueType[];
+ /**
+ * The key used to retrieve data from the dataset.
+ */
+ dataKey?: string;
+ /**
+ * The label to display on the tooltip or the legend. It can be a string or a function.
+ */
+ label?: string | ((location: 'tooltip' | 'legend') => string);
+}
+
+/**
+ * An object that allows to identify a single bar.
+ * Used for item interaction
+ */
+export type HeatmapItemIdentifier = {
+ type: 'heatmap';
+ seriesId: DefaultizedHeatmapSeriesType['id'];
+ dataIndex: number;
+};
+
+export interface DefaultizedHeatmapSeriesType
+ extends DefaultizedProps {}
diff --git a/packages/x-charts-pro/src/models/seriesType/index.ts b/packages/x-charts-pro/src/models/seriesType/index.ts
new file mode 100644
index 000000000000..8b355086c208
--- /dev/null
+++ b/packages/x-charts-pro/src/models/seriesType/index.ts
@@ -0,0 +1 @@
+export * from './heatmap';
diff --git a/packages/x-charts-pro/src/typeOverloads/index.ts b/packages/x-charts-pro/src/typeOverloads/index.ts
new file mode 100644
index 000000000000..4f6953ed9a94
--- /dev/null
+++ b/packages/x-charts-pro/src/typeOverloads/index.ts
@@ -0,0 +1 @@
+export {} from './modules';
diff --git a/packages/x-charts-pro/src/typeOverloads/modules.ts b/packages/x-charts-pro/src/typeOverloads/modules.ts
new file mode 100644
index 000000000000..f9a2db62ac1a
--- /dev/null
+++ b/packages/x-charts-pro/src/typeOverloads/modules.ts
@@ -0,0 +1,18 @@
+import { DefaultizedProps } from '@mui/x-charts/internals';
+import {
+ HeatmapItemIdentifier,
+ HeatmapSeriesType,
+ DefaultizedHeatmapSeriesType,
+} from '../models/seriesType/heatmap';
+
+declare module '@mui/x-charts/internals' {
+ interface ChartsSeriesConfig {
+ heatmap: {
+ seriesInput: DefaultizedProps;
+ series: DefaultizedHeatmapSeriesType;
+ seriesProp: HeatmapSeriesType;
+ itemIdentifier: HeatmapItemIdentifier;
+ cartesian: true;
+ };
+ }
+}
diff --git a/packages/x-charts/package.json b/packages/x-charts/package.json
index 3630e0d32fe4..49e3b1339945 100644
--- a/packages/x-charts/package.json
+++ b/packages/x-charts/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/x-charts",
- "version": "7.7.1",
+ "version": "7.9.0",
"description": "The community edition of the Charts components (MUI X).",
"author": "MUI Team",
"main": "./src/index.js",
@@ -41,8 +41,8 @@
"dependencies": {
"@babel/runtime": "^7.24.7",
"@mui/base": "^5.0.0-beta.40",
- "@mui/system": "^5.15.20",
- "@mui/utils": "^5.15.20",
+ "@mui/system": "^5.16.0",
+ "@mui/utils": "^5.16.0",
"@react-spring/rafz": "^9.7.3",
"@react-spring/web": "^9.7.3",
"clsx": "^2.1.1",
@@ -69,7 +69,7 @@
}
},
"devDependencies": {
- "@mui/internal-test-utils": "^1.0.1",
+ "@mui/internal-test-utils": "^1.0.4",
"@react-spring/core": "^9.7.3",
"@react-spring/shared": "^9.7.3",
"@types/d3-color": "^3.1.3",
@@ -79,7 +79,7 @@
"@types/d3-shape": "^3.1.6",
"@types/prop-types": "^15.7.12",
"csstype": "^3.1.3",
- "rimraf": "^5.0.7"
+ "rimraf": "^5.0.8"
},
"exports": {
".": {
diff --git a/packages/x-charts/src/BarChart/BarChart.tsx b/packages/x-charts/src/BarChart/BarChart.tsx
index 9cafc9ab03e1..1a07d17ba047 100644
--- a/packages/x-charts/src/BarChart/BarChart.tsx
+++ b/packages/x-charts/src/BarChart/BarChart.tsx
@@ -40,13 +40,13 @@ export interface BarChartSlots
extends ChartsAxisSlots,
BarPlotSlots,
ChartsLegendSlots,
- ChartsTooltipSlots,
+ ChartsTooltipSlots<'bar'>,
ChartsOverlaySlots {}
export interface BarChartSlotProps
extends ChartsAxisSlotProps,
BarPlotSlotProps,
ChartsLegendSlotProps,
- ChartsTooltipSlotProps,
+ ChartsTooltipSlotProps<'bar'>,
ChartsOverlaySlotProps {}
export interface BarChartProps
@@ -64,7 +64,7 @@ export interface BarChartProps
* The configuration of the tooltip.
* @see See {@link https://mui.com/x/react-charts/tooltip/ tooltip docs} for more details.
*/
- tooltip?: ChartsTooltipProps;
+ tooltip?: ChartsTooltipProps<'bar'>;
/**
* Option to display a cartesian grid in the background.
*/
diff --git a/packages/x-charts/src/BarChart/useBarChartProps.ts b/packages/x-charts/src/BarChart/useBarChartProps.ts
index b8c9e00ceea9..4370f5d67389 100644
--- a/packages/x-charts/src/BarChart/useBarChartProps.ts
+++ b/packages/x-charts/src/BarChart/useBarChartProps.ts
@@ -1,6 +1,16 @@
import useId from '@mui/utils/useId';
import type { BarChartProps } from './BarChart';
import { DEFAULT_X_AXIS_KEY, DEFAULT_Y_AXIS_KEY } from '../constants';
+import { ResponsiveChartContainerProps } from '../ResponsiveChartContainer';
+import { BarPlotProps } from './BarPlot';
+import { ChartsOnAxisClickHandlerProps } from '../ChartsOnAxisClickHandler';
+import { ChartsGridProps } from '../ChartsGrid';
+import { ChartsClipPathProps } from '../ChartsClipPath';
+import { ChartsOverlayProps } from '../ChartsOverlay';
+import { ChartsAxisProps } from '../ChartsAxis';
+import { ChartsAxisHighlightProps } from '../ChartsAxisHighlight';
+import { ChartsLegendProps } from '../ChartsLegend';
+import { ChartsTooltipProps } from '../ChartsTooltip';
/**
* A helper function that extracts BarChartProps from the input props
@@ -57,7 +67,7 @@ export const useBarChartProps = (props: BarChartProps) => {
),
} as const;
- const chartContainerProps = {
+ const chartContainerProps: ResponsiveChartContainerProps = {
series: series.map((s) => ({
type: 'bar' as const,
...s,
@@ -84,7 +94,7 @@ export const useBarChartProps = (props: BarChartProps) => {
!onAxisClick,
};
- const barPlotProps = {
+ const barPlotProps: BarPlotProps = {
onItemClick,
slots,
slotProps,
@@ -93,11 +103,11 @@ export const useBarChartProps = (props: BarChartProps) => {
barLabel,
};
- const axisClickHandlerProps = {
+ const axisClickHandlerProps: ChartsOnAxisClickHandlerProps = {
onAxisClick,
};
- const gridProps = {
+ const gridProps: ChartsGridProps = {
vertical: grid?.vertical,
horizontal: grid?.horizontal,
};
@@ -106,17 +116,17 @@ export const useBarChartProps = (props: BarChartProps) => {
clipPath: `url(#${clipPathId})`,
};
- const clipPathProps = {
+ const clipPathProps: ChartsClipPathProps = {
id: clipPathId,
};
- const overlayProps = {
+ const overlayProps: ChartsOverlayProps = {
slots,
slotProps,
loading,
};
- const chartsAxisProps = {
+ const chartsAxisProps: ChartsAxisProps = {
topAxis,
leftAxis,
rightAxis,
@@ -125,18 +135,18 @@ export const useBarChartProps = (props: BarChartProps) => {
slotProps,
};
- const axisHighlightProps = {
+ const axisHighlightProps: ChartsAxisHighlightProps = {
...(hasHorizontalSeries ? ({ y: 'band' } as const) : ({ x: 'band' } as const)),
...axisHighlight,
};
- const legendProps = {
+ const legendProps: ChartsLegendProps = {
...legend,
slots,
slotProps,
};
- const tooltipProps = {
+ const tooltipProps: ChartsTooltipProps<'bar'> = {
...tooltip,
slots,
slotProps,
diff --git a/packages/x-charts/src/ChartContainer/ChartContainer.tsx b/packages/x-charts/src/ChartContainer/ChartContainer.tsx
index fb4431c77881..1305694424e5 100644
--- a/packages/x-charts/src/ChartContainer/ChartContainer.tsx
+++ b/packages/x-charts/src/ChartContainer/ChartContainer.tsx
@@ -369,6 +369,8 @@ ChartContainer.propTypes = {
data: PropTypes.array,
dataKey: PropTypes.string,
id: PropTypes.string,
+ max: PropTypes.number,
+ min: PropTypes.number,
}),
),
} as any;
diff --git a/packages/x-charts/src/ChartsAxisHighlight/ChartsAxisHighlight.tsx b/packages/x-charts/src/ChartsAxisHighlight/ChartsAxisHighlight.tsx
index 6485c9c3f4d6..38f77bcc6b36 100644
--- a/packages/x-charts/src/ChartsAxisHighlight/ChartsAxisHighlight.tsx
+++ b/packages/x-charts/src/ChartsAxisHighlight/ChartsAxisHighlight.tsx
@@ -80,11 +80,34 @@ function ChartsAxisHighlight(props: ChartsAxisHighlightProps) {
const getXPosition = getValueToPositionMapper(xScale);
const getYPosition = getValueToPositionMapper(yScale);
+
+ const axisX = axis.x;
+ const axisY = axis.y;
+
+ const isBandScaleX = xAxisHighlight === 'band' && axisX !== null && isBandScale(xScale);
+ const isBandScaleY = yAxisHighlight === 'band' && axisY !== null && isBandScale(yScale);
+
+ if (process.env.NODE_ENV !== 'production') {
+ const isXError = isBandScaleX && xScale(axisX.value) === undefined;
+ const isYError = isBandScaleY && yScale(axisY.value) === undefined;
+
+ if (isXError || isYError) {
+ console.error(
+ [
+ `MUI X Charts: The position value provided for the axis is not valid for the current scale.`,
+ `This probably means something is wrong with the data passed to the chart.`,
+ `The ChartsAxisHighlight component will not be displayed.`,
+ ].join('\n'),
+ );
+ }
+ }
+
return (
- {xAxisHighlight === 'band' && axis.x !== null && isBandScale(xScale) && (
+ {isBandScaleX && xScale(axisX.value) !== undefined && (
)}
- {yAxisHighlight === 'band' && axis.y !== null && isBandScale(yScale) && (
+ {isBandScaleY && yScale(axisY.value) !== undefined && (
({}));
+})(() => ({
+ // This prevents default touch actions when using the svg on mobile devices.
+ // For example, prevent page scroll & zoom.
+ touchAction: 'none',
+}));
const ChartsSurface = React.forwardRef(function ChartsSurface(
props: ChartsSurfaceProps,
diff --git a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx
index e4e9487f7ab7..4784d5559bf7 100644
--- a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx
+++ b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx
@@ -1,5 +1,4 @@
import * as React from 'react';
-import PropTypes from 'prop-types';
import { SxProps, Theme } from '@mui/material/styles';
import { useSlotProps } from '@mui/base/utils';
import { AxisInteractionData } from '../context/InteractionProvider';
@@ -45,6 +44,9 @@ export type ChartsAxisContentProps = {
sx?: SxProps;
};
+/**
+ * @ignore - internal component.
+ */
function ChartsAxisTooltipContent(props: {
axisData: AxisInteractionData;
content?: React.ElementType;
@@ -128,58 +130,4 @@ function ChartsAxisTooltipContent(props: {
return ;
}
-ChartsAxisTooltipContent.propTypes = {
- // ----------------------------- Warning --------------------------------
- // | These PropTypes are generated from the TypeScript type definitions |
- // | To update them edit the TypeScript types and run "pnpm proptypes" |
- // ----------------------------------------------------------------------
- axisData: PropTypes.shape({
- x: PropTypes.shape({
- index: PropTypes.number,
- value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string])
- .isRequired,
- }),
- y: PropTypes.shape({
- index: PropTypes.number,
- value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string])
- .isRequired,
- }),
- }).isRequired,
- classes: PropTypes.object.isRequired,
- content: PropTypes.elementType,
- contentProps: PropTypes.shape({
- axis: PropTypes.object,
- axisData: PropTypes.shape({
- x: PropTypes.shape({
- index: PropTypes.number,
- value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string])
- .isRequired,
- }),
- y: PropTypes.shape({
- index: PropTypes.number,
- value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string])
- .isRequired,
- }),
- }),
- axisValue: PropTypes.oneOfType([
- PropTypes.instanceOf(Date),
- PropTypes.number,
- PropTypes.string,
- ]),
- classes: PropTypes.object,
- dataIndex: PropTypes.number,
- series: PropTypes.arrayOf(PropTypes.object),
- sx: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
- PropTypes.func,
- PropTypes.object,
- ]),
- }),
- sx: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
- PropTypes.func,
- PropTypes.object,
- ]),
-} as any;
-
export { ChartsAxisTooltipContent };
diff --git a/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx
index f4356bbcfc1a..099921b4bf33 100644
--- a/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx
+++ b/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx
@@ -1,5 +1,4 @@
import * as React from 'react';
-import PropTypes from 'prop-types';
import { SxProps, Theme } from '@mui/material/styles';
import { useSlotProps } from '@mui/base/utils';
import { ItemInteractionData } from '../context/InteractionProvider';
@@ -11,7 +10,7 @@ import { ZAxisContext } from '../context/ZAxisContextProvider';
import { useColorProcessor } from '../hooks/useColor';
import { useSeries } from '../hooks/useSeries';
-export type ChartsItemContentProps = {
+export interface ChartsItemContentProps {
/**
* The data used to identify the triggered item.
*/
@@ -31,15 +30,22 @@ export type ChartsItemContentProps
*/
getColor: (dataIndex: number) => string;
sx?: SxProps;
-};
+}
-function ChartsItemTooltipContent(props: {
+export interface ChartsItemTooltipContentProps {
itemData: ItemInteractionData;
content?: React.ElementType>;
contentProps?: Partial>;
sx?: SxProps;
classes: ChartsItemContentProps['classes'];
-}) {
+}
+
+/**
+ * @ignore - internal component.
+ */
+function ChartsItemTooltipContent(
+ props: ChartsItemTooltipContentProps,
+) {
const { content, itemData, sx, classes, contentProps } = props;
const series = useSeries()[itemData.type]!.series[itemData.seriesId] as ChartSeriesDefaultized;
@@ -76,38 +82,4 @@ function ChartsItemTooltipContent(props: {
return ;
}
-ChartsItemTooltipContent.propTypes = {
- // ----------------------------- Warning --------------------------------
- // | These PropTypes are generated from the TypeScript type definitions |
- // | To update them edit the TypeScript types and run "pnpm proptypes" |
- // ----------------------------------------------------------------------
- classes: PropTypes.object.isRequired,
- content: PropTypes.elementType,
- contentProps: PropTypes.shape({
- classes: PropTypes.object,
- getColor: PropTypes.func,
- itemData: PropTypes.shape({
- dataIndex: PropTypes.number,
- seriesId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
- type: PropTypes.oneOf(['bar', 'line', 'pie', 'scatter']).isRequired,
- }),
- series: PropTypes.object,
- sx: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
- PropTypes.func,
- PropTypes.object,
- ]),
- }),
- itemData: PropTypes.shape({
- dataIndex: PropTypes.number,
- seriesId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
- type: PropTypes.oneOf(['bar', 'line', 'pie', 'scatter']).isRequired,
- }).isRequired,
- sx: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
- PropTypes.func,
- PropTypes.object,
- ]),
-} as any;
-
export { ChartsItemTooltipContent };
diff --git a/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx b/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx
index 1b321df5522d..5f2d8fb96b2e 100644
--- a/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx
+++ b/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx
@@ -28,7 +28,7 @@ export type PopperProps = BasePopperProps & {
sx?: SxProps;
};
-export interface ChartsTooltipSlots {
+export interface ChartsTooltipSlots {
/**
* Custom component for the tooltip popper.
* @default ChartsTooltipRoot
@@ -43,16 +43,16 @@ export interface ChartsTooltipSlots {
* Custom component for displaying tooltip content when triggered by item event.
* @default DefaultChartsItemTooltipContent
*/
- itemContent?: React.ElementType;
+ itemContent?: React.ElementType>;
}
-export interface ChartsTooltipSlotProps {
+export interface ChartsTooltipSlotProps {
popper?: Partial;
axisContent?: Partial;
- itemContent?: Partial;
+ itemContent?: Partial>;
}
-export type ChartsTooltipProps = {
+export interface ChartsTooltipProps {
/**
* Select the kind of tooltip to display
* - 'item': Shows data about the item below the mouse.
@@ -79,15 +79,17 @@ export type ChartsTooltipProps = {
* Overridable component slots.
* @default {}
*/
- slots?: ChartsTooltipSlots;
+ slots?: ChartsTooltipSlots;
/**
* The props used for each component slot.
* @default {}
*/
- slotProps?: ChartsTooltipSlotProps;
-};
+ slotProps?: ChartsTooltipSlotProps;
+}
-const useUtilityClasses = (ownerState: { classes: ChartsTooltipProps['classes'] }) => {
+const useUtilityClasses = (ownerState: {
+ classes: ChartsTooltipProps['classes'];
+}) => {
const { classes } = ownerState;
const slots = {
@@ -122,7 +124,7 @@ const ChartsTooltipRoot = styled(Popper, {
*
* - [ChartsTooltip API](https://mui.com/x/api/charts/charts-tool-tip/)
*/
-function ChartsTooltip(props: ChartsTooltipProps) {
+function ChartsTooltip(props: ChartsTooltipProps) {
const themeProps = useThemeProps({
props,
name: 'MuiChartsTooltip',
@@ -146,8 +148,17 @@ function ChartsTooltip(props: ChartsTooltipProps) {
externalSlotProps: slotProps?.popper,
additionalProps: {
open: popperOpen,
- placement: 'right-start' as const,
+ placement:
+ mousePosition?.pointerType === 'mouse' ? ('right-start' as const) : ('top' as const),
anchorEl: generateVirtualElement(mousePosition),
+ modifiers: [
+ {
+ name: 'offset',
+ options: {
+ offset: [0, mousePosition?.pointerType === 'touch' ? 40 - mousePosition.height : 0],
+ },
+ },
+ ],
},
ownerState: {},
});
@@ -162,9 +173,9 @@ function ChartsTooltip(props: ChartsTooltipProps) {
{trigger === 'item' ? (
}
- content={slots?.itemContent ?? itemContent}
- contentProps={slotProps?.itemContent}
+ itemData={displayedData as ItemInteractionData}
+ content={(slots?.itemContent ?? itemContent) as any}
+ contentProps={slotProps?.itemContent as Partial>}
sx={{ mx: 2 }}
classes={classes}
/>
diff --git a/packages/x-charts/src/ChartsTooltip/ChartsTooltipTable.ts b/packages/x-charts/src/ChartsTooltip/ChartsTooltipTable.ts
index 1e1073914c63..a83a70ae9e5b 100644
--- a/packages/x-charts/src/ChartsTooltip/ChartsTooltipTable.ts
+++ b/packages/x-charts/src/ChartsTooltip/ChartsTooltipTable.ts
@@ -2,6 +2,9 @@ import { styled } from '@mui/material/styles';
import { shouldForwardProp } from '@mui/system';
import { chartsTooltipClasses } from './chartsTooltipClasses';
+/**
+ * @ignore - internal component.
+ */
export const ChartsTooltipPaper = styled('div', {
name: 'MuiChartsTooltip',
slot: 'Container',
@@ -13,6 +16,9 @@ export const ChartsTooltipPaper = styled('div', {
borderRadius: theme.shape.borderRadius,
}));
+/**
+ * @ignore - internal component.
+ */
export const ChartsTooltipTable = styled('table', {
name: 'MuiChartsTooltip',
slot: 'Table',
@@ -23,6 +29,9 @@ export const ChartsTooltipTable = styled('table', {
},
}));
+/**
+ * @ignore - internal component.
+ */
export const ChartsTooltipRow = styled('tr', {
name: 'MuiChartsTooltip',
slot: 'Row',
@@ -35,6 +44,9 @@ export const ChartsTooltipRow = styled('tr', {
},
}));
+/**
+ * @ignore - internal component.
+ */
export const ChartsTooltipCell = styled('td', {
name: 'MuiChartsTooltip',
slot: 'Cell',
@@ -56,6 +68,9 @@ export const ChartsTooltipCell = styled('td', {
},
}));
+/**
+ * @ignore - internal component.
+ */
export const ChartsTooltipMark = styled('div', {
name: 'MuiChartsTooltip',
slot: 'Mark',
diff --git a/packages/x-charts/src/ChartsTooltip/index.ts b/packages/x-charts/src/ChartsTooltip/index.ts
index f1ec9209697d..dc13976d1d75 100644
--- a/packages/x-charts/src/ChartsTooltip/index.ts
+++ b/packages/x-charts/src/ChartsTooltip/index.ts
@@ -6,3 +6,5 @@ export * from './ChartsItemTooltipContent';
export * from './DefaultChartsAxisTooltipContent';
export * from './DefaultChartsItemTooltipContent';
+
+export * from './ChartsTooltipTable';
diff --git a/packages/x-charts/src/ChartsTooltip/utils.tsx b/packages/x-charts/src/ChartsTooltip/utils.tsx
index 95e2d849abbb..8a7ad4a359a9 100644
--- a/packages/x-charts/src/ChartsTooltip/utils.tsx
+++ b/packages/x-charts/src/ChartsTooltip/utils.tsx
@@ -3,7 +3,14 @@ import { AxisInteractionData, ItemInteractionData } from '../context/Interaction
import { ChartSeriesType } from '../models/seriesType/config';
import { useSvgRef } from '../hooks';
-export function generateVirtualElement(mousePosition: { x: number; y: number } | null) {
+type MousePosition = {
+ x: number;
+ y: number;
+ pointerType: 'mouse' | 'touch' | 'pen';
+ height: number;
+};
+
+export function generateVirtualElement(mousePosition: MousePosition | null) {
if (mousePosition === null) {
return {
getBoundingClientRect: () => ({
@@ -20,18 +27,20 @@ export function generateVirtualElement(mousePosition: { x: number; y: number } |
};
}
const { x, y } = mousePosition;
+ const boundingBox = {
+ width: 0,
+ height: 0,
+ x,
+ y,
+ top: y,
+ right: x,
+ bottom: y,
+ left: x,
+ };
return {
getBoundingClientRect: () => ({
- width: 0,
- height: 0,
- x,
- y,
- top: y,
- right: x,
- bottom: y,
- left: x,
- toJSON: () =>
- JSON.stringify({ width: 0, height: 0, x, y, top: y, right: x, bottom: y, left: x }),
+ ...boundingBox,
+ toJSON: () => JSON.stringify(boundingBox),
}),
};
}
@@ -40,7 +49,7 @@ export function useMouseTracker() {
const svgRef = useSvgRef();
// Use a ref to avoid rerendering on every mousemove event.
- const [mousePosition, setMousePosition] = React.useState(null);
+ const [mousePosition, setMousePosition] = React.useState(null);
React.useEffect(() => {
const element = svgRef.current;
@@ -52,23 +61,23 @@ export function useMouseTracker() {
setMousePosition(null);
};
- const handleMove = (event: MouseEvent | TouchEvent) => {
- const target = 'targetTouches' in event ? event.targetTouches[0] : event;
+ const handleMove = (event: PointerEvent) => {
setMousePosition({
- x: target.clientX,
- y: target.clientY,
+ x: event.clientX,
+ y: event.clientY,
+ height: event.height,
+ pointerType: event.pointerType as MousePosition['pointerType'],
});
};
- element.addEventListener('mouseout', handleOut);
- element.addEventListener('mousemove', handleMove);
- element.addEventListener('touchend', handleOut);
- element.addEventListener('touchmove', handleMove);
+ element.addEventListener('pointerdown', handleMove);
+ element.addEventListener('pointermove', handleMove);
+ element.addEventListener('pointerup', handleOut);
+
return () => {
- element.removeEventListener('mouseout', handleOut);
- element.removeEventListener('mousemove', handleMove);
- element.addEventListener('touchend', handleOut);
- element.addEventListener('touchmove', handleMove);
+ element.removeEventListener('pointerdown', handleMove);
+ element.removeEventListener('pointermove', handleMove);
+ element.removeEventListener('pointerup', handleOut);
};
}, [svgRef]);
diff --git a/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx b/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx
index d9812c0b3baa..da1cba1cdac4 100644
--- a/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx
+++ b/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx
@@ -5,7 +5,7 @@ import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
import { InteractionContext } from '../context/InteractionProvider';
import { useCartesianContext } from '../context/CartesianProvider';
import { getValueToPositionMapper } from '../hooks/useScale';
-import { getSVGPoint } from '../internals/utils';
+import { getSVGPoint } from '../internals/getSVGPoint';
import { ScatterItemIdentifier } from '../models';
import { SeriesId } from '../models/seriesType/common';
import { useDrawingArea, useSvgRef } from '../hooks';
@@ -97,7 +97,7 @@ function ChartsVoronoiHandler(props: ChartsVoronoiHandlerProps) {
| 'outside-voronoi-max-radius'
| 'no-point-found' {
// Get mouse coordinate in global SVG space
- const svgPoint = getSVGPoint(svgRef.current!, event);
+ const svgPoint = getSVGPoint(element, event);
const outsideX = svgPoint.x < left || svgPoint.x > left + width;
const outsideY = svgPoint.y < top || svgPoint.y > top + height;
@@ -180,12 +180,12 @@ function ChartsVoronoiHandler(props: ChartsVoronoiHandlerProps) {
onItemClick(event, { type: 'scatter', seriesId, dataIndex });
};
- element.addEventListener('mouseout', handleMouseOut);
- element.addEventListener('mousemove', handleMouseMove);
+ element.addEventListener('pointerout', handleMouseOut);
+ element.addEventListener('pointermove', handleMouseMove);
element.addEventListener('click', handleMouseClick);
return () => {
- element.removeEventListener('mouseout', handleMouseOut);
- element.removeEventListener('mousemove', handleMouseMove);
+ element.removeEventListener('pointerout', handleMouseOut);
+ element.removeEventListener('pointermove', handleMouseMove);
element.removeEventListener('click', handleMouseClick);
};
}, [
diff --git a/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx b/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx
index ea796b2e333e..247a7ba5a7de 100644
--- a/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx
+++ b/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx
@@ -205,38 +205,39 @@ function ChartsXAxis(inProps: ChartsXAxisProps) {
className={classes.root}
>
{!disableLine && (
-
+
)}
- {xTicksWithDimension.map(({ formattedValue, offset, labelOffset, skipLabel }, index) => {
- const xTickLabel = labelOffset ?? 0;
- const yTickLabel = positionSign * (tickSize + 3);
- return (
-
- {!disableTicks && (
-
- )}
+ {xTicksWithDimension
+ .filter((tick) => tick.offset >= left - 1 && tick.offset <= left + width + 1)
+ .map(({ formattedValue, offset, labelOffset, skipLabel }, index) => {
+ const xTickLabel = labelOffset ?? 0;
+ const yTickLabel = positionSign * (tickSize + 3);
- {formattedValue !== undefined && !skipLabel && (
-
- )}
-
- );
- })}
+ const showTick = offset >= left - 1 && offset <= left + width + 1;
+ const showTickLabel =
+ offset + xTickLabel >= left - 1 && offset + xTickLabel <= left + width + 1;
+ return (
+
+ {!disableTicks && showTick && (
+
+ )}
+
+ {formattedValue !== undefined && !skipLabel && showTickLabel && (
+
+ )}
+
+ );
+ })}
{label && (
diff --git a/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx b/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx
index 44475ad90f1f..b26587275373 100644
--- a/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx
+++ b/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx
@@ -74,6 +74,8 @@ function ChartsYAxis(inProps: ChartsYAxisProps) {
} = defaultizedProps;
const theme = useTheme();
+ const isRTL = theme.direction === 'rtl';
+
const classes = useUtilityClasses({ ...defaultizedProps, theme });
const { left, top, width, height } = useDrawingArea();
@@ -101,13 +103,14 @@ function ChartsYAxis(inProps: ChartsYAxisProps) {
const TickLabel = slots?.axisTickLabel ?? ChartsText;
const Label = slots?.axisLabel ?? ChartsText;
+ const revertAnchor = (!isRTL && position === 'right') || (isRTL && position !== 'right');
const axisTickLabelProps = useSlotProps({
elementType: TickLabel,
externalSlotProps: slotProps?.axisTickLabel,
additionalProps: {
style: {
fontSize: tickFontSize,
- textAnchor: position === 'right' ? 'start' : 'end',
+ textAnchor: revertAnchor ? 'start' : 'end',
dominantBaseline: 'central',
...tickLabelStyle,
},
diff --git a/packages/x-charts/src/LineChart/LineChart.tsx b/packages/x-charts/src/LineChart/LineChart.tsx
index 3a865af2044c..fbce1924fe3c 100644
--- a/packages/x-charts/src/LineChart/LineChart.tsx
+++ b/packages/x-charts/src/LineChart/LineChart.tsx
@@ -50,7 +50,7 @@ export interface LineChartSlots
MarkPlotSlots,
LineHighlightPlotSlots,
ChartsLegendSlots,
- ChartsTooltipSlots,
+ ChartsTooltipSlots<'line'>,
ChartsOverlaySlots {}
export interface LineChartSlotProps
extends ChartsAxisSlotProps,
@@ -59,7 +59,7 @@ export interface LineChartSlotProps
MarkPlotSlotProps,
LineHighlightPlotSlotProps,
ChartsLegendSlotProps,
- ChartsTooltipSlotProps,
+ ChartsTooltipSlotProps<'line'>,
ChartsOverlaySlotProps {}
export interface LineChartProps
@@ -77,7 +77,7 @@ export interface LineChartProps
* @see See {@link https://mui.com/x/react-charts/tooltip/ tooltip docs} for more details.
* @default { trigger: 'item' }
*/
- tooltip?: ChartsTooltipProps;
+ tooltip?: ChartsTooltipProps<'line'>;
/**
* Option to display a cartesian grid in the background.
*/
diff --git a/packages/x-charts/src/LineChart/MarkPlot.tsx b/packages/x-charts/src/LineChart/MarkPlot.tsx
index ceecfbb0b6f7..7498c3f3eb4f 100644
--- a/packages/x-charts/src/LineChart/MarkPlot.tsx
+++ b/packages/x-charts/src/LineChart/MarkPlot.tsx
@@ -9,6 +9,7 @@ import { LineItemIdentifier } from '../models/seriesType/line';
import { cleanId } from '../internals/utils';
import getColor from './getColor';
import { useLineSeries } from '../hooks/useSeries';
+import { useDrawingArea } from '../hooks/useDrawingArea';
export interface MarkPlotSlots {
mark?: React.JSXElementConstructor;
@@ -58,6 +59,7 @@ function MarkPlot(props: MarkPlotProps) {
const seriesData = useLineSeries();
const axisData = useCartesianContext();
const chartId = useChartId();
+ const { left, width } = useDrawingArea();
const Mark = slots?.mark ?? MarkElement;
@@ -89,11 +91,10 @@ function MarkPlot(props: MarkPlotProps) {
const yScale = yAxis[yAxisKey].scale;
const xData = xAxis[xAxisKey].data;
- const xRange = xAxis[xAxisKey].scale.range();
const yRange = yScale.range();
const isInRange = ({ x, y }: { x: number; y: number }) => {
- if (x < Math.min(...xRange) || x > Math.max(...xRange)) {
+ if (x < left || x > left + width) {
return false;
}
if (y < Math.min(...yRange) || y > Math.max(...yRange)) {
diff --git a/packages/x-charts/src/LineChart/useLineChartProps.ts b/packages/x-charts/src/LineChart/useLineChartProps.ts
index 226061be2a17..47bbcbc7bd42 100644
--- a/packages/x-charts/src/LineChart/useLineChartProps.ts
+++ b/packages/x-charts/src/LineChart/useLineChartProps.ts
@@ -1,6 +1,19 @@
import useId from '@mui/utils/useId';
import { DEFAULT_X_AXIS_KEY } from '../constants';
import type { LineChartProps } from './LineChart';
+import { ResponsiveChartContainerProps } from '../ResponsiveChartContainer';
+import { ChartsOnAxisClickHandlerProps } from '../ChartsOnAxisClickHandler';
+import { ChartsGridProps } from '../ChartsGrid';
+import { ChartsClipPathProps } from '../ChartsClipPath';
+import { AreaPlotProps } from './AreaPlot';
+import { LinePlotProps } from './LinePlot';
+import { MarkPlotProps } from './MarkPlot';
+import { ChartsOverlayProps } from '../ChartsOverlay';
+import { ChartsAxisProps } from '../ChartsAxis';
+import { ChartsAxisHighlightProps } from '../ChartsAxisHighlight';
+import { LineHighlightPlotProps } from './LineHighlightPlot';
+import { ChartsLegendProps } from '../ChartsLegend';
+import { ChartsTooltipProps } from '../ChartsTooltip';
/**
* A helper function that extracts LineChartProps from the input props
@@ -45,7 +58,7 @@ export const useLineChartProps = (props: LineChartProps) => {
const id = useId();
const clipPathId = `${id}-clip-path`;
- const chartContainerProps = {
+ const chartContainerProps: ResponsiveChartContainerProps = {
series: series.map((s) => ({
disableHighlight: !!disableLineItemHighlight,
type: 'line' as const,
@@ -77,11 +90,11 @@ export const useLineChartProps = (props: LineChartProps) => {
!onAxisClick,
};
- const axisClickHandlerProps = {
+ const axisClickHandlerProps: ChartsOnAxisClickHandlerProps = {
onAxisClick,
};
- const gridProps = {
+ const gridProps: ChartsGridProps = {
vertical: grid?.vertical,
horizontal: grid?.horizontal,
};
@@ -90,38 +103,38 @@ export const useLineChartProps = (props: LineChartProps) => {
clipPath: `url(#${clipPathId})`,
};
- const clipPathProps = {
+ const clipPathProps: ChartsClipPathProps = {
id: clipPathId,
};
- const areaPlotProps = {
+ const areaPlotProps: AreaPlotProps = {
slots,
slotProps,
- onAreaClick,
+ onItemClick: onAreaClick,
skipAnimation,
};
- const linePlotProps = {
+ const linePlotProps: LinePlotProps = {
slots,
slotProps,
- onLineClick,
+ onItemClick: onLineClick,
skipAnimation,
};
- const markPlotProps = {
+ const markPlotProps: MarkPlotProps = {
slots,
slotProps,
- onMarkClick,
+ onItemClick: onMarkClick,
skipAnimation,
};
- const overlayProps = {
+ const overlayProps: ChartsOverlayProps = {
slots,
slotProps,
loading,
};
- const chartsAxisProps = {
+ const chartsAxisProps: ChartsAxisProps = {
topAxis,
leftAxis,
rightAxis,
@@ -130,23 +143,23 @@ export const useLineChartProps = (props: LineChartProps) => {
slotProps,
};
- const axisHighlightProps = {
+ const axisHighlightProps: ChartsAxisHighlightProps = {
x: 'line' as const,
...axisHighlight,
};
- const lineHighlightPlotProps = {
+ const lineHighlightPlotProps: LineHighlightPlotProps = {
slots,
slotProps,
};
- const legendProps = {
+ const legendProps: ChartsLegendProps = {
...legend,
slots,
slotProps,
};
- const tooltipProps = {
+ const tooltipProps: ChartsTooltipProps<'line'> = {
...tooltip,
slots,
slotProps,
diff --git a/packages/x-charts/src/PieChart/PieChart.tsx b/packages/x-charts/src/PieChart/PieChart.tsx
index bf00ee9effaa..856052afdc28 100644
--- a/packages/x-charts/src/PieChart/PieChart.tsx
+++ b/packages/x-charts/src/PieChart/PieChart.tsx
@@ -41,14 +41,14 @@ export interface PieChartSlots
extends ChartsAxisSlots,
PiePlotSlots,
ChartsLegendSlots,
- ChartsTooltipSlots,
+ ChartsTooltipSlots<'pie'>,
ChartsOverlaySlots {}
export interface PieChartSlotProps
extends ChartsAxisSlotProps,
PiePlotSlotProps,
ChartsLegendSlotProps,
- ChartsTooltipSlotProps,
+ ChartsTooltipSlotProps<'pie'>,
ChartsOverlaySlotProps {}
export interface PieChartProps
@@ -81,7 +81,7 @@ export interface PieChartProps
* @see See {@link https://mui.com/x/react-charts/tooltip/ tooltip docs} for more details.
* @default { trigger: 'item' }
*/
- tooltip?: ChartsTooltipProps;
+ tooltip?: ChartsTooltipProps<'pie'>;
/**
* The configuration of axes highlight.
* @see See {@link https://mui.com/x/react-charts/tooltip/#highlights highlight docs} for more details.
diff --git a/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx b/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx
index d33fbcf1590e..8ea518934c3b 100644
--- a/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx
+++ b/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx
@@ -288,6 +288,8 @@ ResponsiveChartContainer.propTypes = {
data: PropTypes.array,
dataKey: PropTypes.string,
id: PropTypes.string,
+ max: PropTypes.number,
+ min: PropTypes.number,
}),
),
} as any;
diff --git a/packages/x-charts/src/ScatterChart/Scatter.tsx b/packages/x-charts/src/ScatterChart/Scatter.tsx
index 782c58ef531e..abead767285e 100644
--- a/packages/x-charts/src/ScatterChart/Scatter.tsx
+++ b/packages/x-charts/src/ScatterChart/Scatter.tsx
@@ -10,6 +10,7 @@ import { useInteractionItemProps } from '../hooks/useInteractionItemProps';
import { InteractionContext } from '../context/InteractionProvider';
import { D3Scale } from '../models/axis';
import { useHighlighted } from '../context';
+import { useDrawingArea } from '../hooks/useDrawingArea';
export interface ScatterProps {
series: DefaultizedScatterSeriesType;
@@ -42,6 +43,8 @@ export interface ScatterProps {
function Scatter(props: ScatterProps) {
const { series, xScale, yScale, color, colorGetter, markerSize, onItemClick } = props;
+ const { left, width } = useDrawingArea();
+
const { useVoronoiInteraction } = React.useContext(InteractionContext);
const skipInteractionHandlers = useVoronoiInteraction || series.disableHover;
@@ -51,11 +54,9 @@ function Scatter(props: ScatterProps) {
const cleanData = React.useMemo(() => {
const getXPosition = getValueToPositionMapper(xScale);
const getYPosition = getValueToPositionMapper(yScale);
- const xRange = xScale.range();
+
const yRange = yScale.range();
- const minXRange = Math.min(...xRange);
- const maxXRange = Math.max(...xRange);
const minYRange = Math.min(...yRange);
const maxYRange = Math.max(...yRange);
@@ -73,7 +74,7 @@ function Scatter(props: ScatterProps) {
const x = getXPosition(scatterPoint.x);
const y = getYPosition(scatterPoint.y);
- const isInRange = x >= minXRange && x <= maxXRange && y >= minYRange && y <= maxYRange;
+ const isInRange = x >= left && x <= left + width && y >= minYRange && y <= maxYRange;
const pointCtx = { type: 'scatter' as const, seriesId: series.id, dataIndex: i };
@@ -100,13 +101,15 @@ function Scatter(props: ScatterProps) {
}, [
xScale,
yScale,
+ left,
+ width,
series.data,
series.id,
+ isHighlighted,
+ isFaded,
getInteractionItemProps,
- color,
colorGetter,
- isFaded,
- isHighlighted,
+ color,
]);
return (
diff --git a/packages/x-charts/src/ScatterChart/ScatterChart.tsx b/packages/x-charts/src/ScatterChart/ScatterChart.tsx
index f86111b66408..84ca039c4f4c 100644
--- a/packages/x-charts/src/ScatterChart/ScatterChart.tsx
+++ b/packages/x-charts/src/ScatterChart/ScatterChart.tsx
@@ -45,13 +45,13 @@ export interface ScatterChartSlots
extends ChartsAxisSlots,
ScatterPlotSlots,
ChartsLegendSlots,
- ChartsTooltipSlots,
+ ChartsTooltipSlots<'scatter'>,
ChartsOverlaySlots {}
export interface ScatterChartSlotProps
extends ChartsAxisSlotProps,
ScatterPlotSlotProps,
ChartsLegendSlotProps,
- ChartsTooltipSlotProps,
+ ChartsTooltipSlotProps<'scatter'>,
ChartsOverlaySlotProps {}
export interface ScatterChartProps
@@ -70,7 +70,7 @@ export interface ScatterChartProps
* @see See {@link https://mui.com/x/react-charts/tooltip/ tooltip docs} for more details.
* @default { trigger: 'item' }
*/
- tooltip?: ChartsTooltipProps;
+ tooltip?: ChartsTooltipProps<'scatter'>;
/**
* The configuration of axes highlight.
* @see See {@link https://mui.com/x/react-charts/tooltip/#highlights highlight docs} for more details.
@@ -497,6 +497,8 @@ ScatterChart.propTypes = {
data: PropTypes.array,
dataKey: PropTypes.string,
id: PropTypes.string,
+ max: PropTypes.number,
+ min: PropTypes.number,
}),
),
} as any;
diff --git a/packages/x-charts/src/ScatterChart/useScatterChartProps.ts b/packages/x-charts/src/ScatterChart/useScatterChartProps.ts
index 9f57a8c01c74..b3fe96a66fc6 100644
--- a/packages/x-charts/src/ScatterChart/useScatterChartProps.ts
+++ b/packages/x-charts/src/ScatterChart/useScatterChartProps.ts
@@ -1,4 +1,12 @@
+import { ChartsAxisProps } from '../ChartsAxis';
+import { ChartsAxisHighlightProps } from '../ChartsAxisHighlight';
+import { ChartsGridProps } from '../ChartsGrid';
+import { ChartsLegendProps } from '../ChartsLegend';
+import { ChartsOverlayProps } from '../ChartsOverlay';
+import { ChartsTooltipProps } from '../ChartsTooltip';
import type { ChartsVoronoiHandlerProps } from '../ChartsVoronoiHandler';
+import { ResponsiveChartContainerProps } from '../ResponsiveChartContainer';
+import { ZAxisContextProviderProps } from '../context';
import type { ScatterChartProps } from './ScatterChart';
import type { ScatterPlotProps } from './ScatterPlot';
@@ -39,7 +47,7 @@ export const useScatterChartProps = (props: ScatterChartProps) => {
onHighlightChange,
} = props;
- const chartContainerProps = {
+ const chartContainerProps: ResponsiveChartContainerProps = {
series: series.map((s) => ({ type: 'scatter' as const, ...s })),
width,
height,
@@ -51,14 +59,14 @@ export const useScatterChartProps = (props: ScatterChartProps) => {
highlightedItem,
onHighlightChange,
};
- const zAxisProps = {
+ const zAxisProps: Omit = {
zAxis,
};
- const voronoiHandlerProps = {
+ const voronoiHandlerProps: ChartsVoronoiHandlerProps = {
voronoiMaxRadius,
onItemClick: onItemClick as ChartsVoronoiHandlerProps['onItemClick'],
};
- const chartsAxisProps = {
+ const chartsAxisProps: ChartsAxisProps = {
topAxis,
leftAxis,
rightAxis,
@@ -67,36 +75,36 @@ export const useScatterChartProps = (props: ScatterChartProps) => {
slotProps,
};
- const gridProps = {
+ const gridProps: ChartsGridProps = {
vertical: grid?.vertical,
horizontal: grid?.horizontal,
};
- const scatterPlotProps = {
+ const scatterPlotProps: ScatterPlotProps = {
onItemClick: disableVoronoi ? (onItemClick as ScatterPlotProps['onItemClick']) : undefined,
slots,
slotProps,
};
- const overlayProps = {
+ const overlayProps: ChartsOverlayProps = {
loading,
slots,
slotProps,
};
- const legendProps = {
+ const legendProps: ChartsLegendProps = {
...legend,
slots,
slotProps,
};
- const axisHighlightProps = {
+ const axisHighlightProps: ChartsAxisHighlightProps = {
y: 'none' as const,
x: 'none' as const,
...axisHighlight,
};
- const tooltipProps = {
+ const tooltipProps: ChartsTooltipProps<'scatter'> = {
trigger: 'item' as const,
...tooltip,
slots,
diff --git a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx
index 028db19f4acf..d46ac24e85bd 100644
--- a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx
+++ b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx
@@ -30,14 +30,14 @@ export interface SparkLineChartSlots
MarkPlotSlots,
LineHighlightPlotSlots,
Omit,
- ChartsTooltipSlots {}
+ ChartsTooltipSlots<'line' | 'bar'> {}
export interface SparkLineChartSlotProps
extends AreaPlotSlotProps,
LinePlotSlotProps,
MarkPlotSlotProps,
LineHighlightPlotSlotProps,
BarPlotSlotProps,
- ChartsTooltipSlotProps {}
+ ChartsTooltipSlotProps<'line' | 'bar'> {}
export interface SparkLineChartProps
extends Omit<
@@ -54,7 +54,7 @@ export interface SparkLineChartProps
* Notice it is a single [[AxisConfig]] object, not an array of configuration.
*/
yAxis?: MakeOptional, 'id'>;
- tooltip?: ChartsTooltipProps;
+ tooltip?: ChartsTooltipProps<'line' | 'bar'>;
axisHighlight?: ChartsAxisHighlightProps;
/**
* Type of plot used.
diff --git a/packages/x-charts/src/context/CartesianProvider/computeValue.ts b/packages/x-charts/src/context/CartesianProvider/computeValue.ts
index 053b51cf1afd..0d7da4f5b40d 100644
--- a/packages/x-charts/src/context/CartesianProvider/computeValue.ts
+++ b/packages/x-charts/src/context/CartesianProvider/computeValue.ts
@@ -1,4 +1,4 @@
-import { scaleBand, scalePoint } from 'd3-scale';
+import { scaleBand, scalePoint, scaleTime } from 'd3-scale';
import { DEFAULT_X_AXIS_KEY, DEFAULT_Y_AXIS_KEY } from '../../constants';
import { AxisConfig, ScaleName } from '../../models';
import {
@@ -27,6 +27,29 @@ const getRange = (drawingArea: DrawingArea, axisName: 'x' | 'y', isReverse?: boo
return isReverse ? range.reverse() : range;
};
+const zoomedScaleRange = (scaleRange: [number, number] | number[], zoomRange: [number, number]) => {
+ const rangeGap = scaleRange[1] - scaleRange[0];
+ const zoomGap = zoomRange[1] - zoomRange[0];
+
+ // If current zoom show the scale between p1 and p2 percents
+ // The range should be extended by adding [0, p1] and [p2, 100] segments
+ const min = scaleRange[0] - (zoomRange[0] * rangeGap) / zoomGap;
+ const max = scaleRange[1] + ((100 - zoomRange[1]) * rangeGap) / zoomGap;
+
+ return [min, max];
+};
+const isDateData = (data?: any[]): data is Date[] => data?.[0] instanceof Date;
+
+function createDateFormatter(
+ axis: AxisConfig<'band' | 'point', any, ChartsAxisProps>,
+ range: number[],
+): AxisConfig<'band' | 'point', any, ChartsAxisProps>['valueFormatter'] {
+ const timeScale = scaleTime(axis.data!, range);
+
+ return (v, { location }) =>
+ location === 'tick' ? timeScale.tickFormat(axis.tickNumber)(v) : `${v.toLocaleString()}`;
+}
+
const DEFAULT_CATEGORY_GAP_RATIO = 0.2;
const DEFAULT_BAR_GAP_RATIO = 0.1;
@@ -36,6 +59,7 @@ export function computeValue(
axis: MakeOptional, 'id'>[] | undefined,
extremumGetters: { [K in CartesianChartSeriesType]?: ExtremumGetter },
axisName: 'y',
+ zoomRange?: [number, number],
): {
axis: DefaultizedAxisConfig;
axisIds: string[];
@@ -46,6 +70,7 @@ export function computeValue(
inAxis: MakeOptional, 'id'>[] | undefined,
extremumGetters: { [K in CartesianChartSeriesType]?: ExtremumGetter },
axisName: 'x',
+ zoomRange?: [number, number],
): {
axis: DefaultizedAxisConfig;
axisIds: string[];
@@ -56,6 +81,7 @@ export function computeValue(
inAxis: MakeOptional, 'id'>[] | undefined,
extremumGetters: { [K in CartesianChartSeriesType]?: ExtremumGetter },
axisName: 'x' | 'y',
+ zoomRange: [number, number] = [0, 100],
) {
const DEFAULT_AXIS_KEY = axisName === 'x' ? DEFAULT_X_AXIS_KEY : DEFAULT_Y_AXIS_KEY;
@@ -84,12 +110,13 @@ export function computeValue(
const barGapRatio = axis.barGapRatio ?? DEFAULT_BAR_GAP_RATIO;
// Reverse range because ordinal scales are presented from top to bottom on y-axis
const scaleRange = axisName === 'x' ? range : [range[1], range[0]];
+ const zoomedRange = zoomedScaleRange(scaleRange, zoomRange);
completeAxis[axis.id] = {
categoryGapRatio,
barGapRatio,
...axis,
- scale: scaleBand(axis.data!, scaleRange)
+ scale: scaleBand(axis.data!, zoomedRange)
.paddingInner(categoryGapRatio)
.paddingOuter(categoryGapRatio / 2),
tickNumber: axis.data!.length,
@@ -99,13 +126,19 @@ export function computeValue(
? getOrdinalColorScale({ values: axis.data, ...axis.colorMap })
: getColorScale(axis.colorMap)),
};
+
+ if (isDateData(axis.data)) {
+ const dateFormatter = createDateFormatter(axis, scaleRange);
+ completeAxis[axis.id].valueFormatter = axis.valueFormatter ?? dateFormatter;
+ }
}
if (isPointScaleConfig(axis)) {
const scaleRange = axisName === 'x' ? range : [...range].reverse();
+ const zoomedRange = zoomedScaleRange(scaleRange, zoomRange);
completeAxis[axis.id] = {
...axis,
- scale: scalePoint(axis.data!, scaleRange),
+ scale: scalePoint(axis.data!, zoomedRange),
tickNumber: axis.data!.length,
colorScale:
axis.colorMap &&
@@ -113,6 +146,11 @@ export function computeValue(
? getOrdinalColorScale({ values: axis.data, ...axis.colorMap })
: getColorScale(axis.colorMap)),
};
+
+ if (isDateData(axis.data)) {
+ const dateFormatter = createDateFormatter(axis, scaleRange);
+ completeAxis[axis.id].valueFormatter = axis.valueFormatter ?? dateFormatter;
+ }
}
if (axis.scaleType === 'band' || axis.scaleType === 'point') {
// Could be merged with the two previous "if conditions" but then TS does not get that `axis.scaleType` can't be `band` or `point`.
@@ -122,9 +160,13 @@ export function computeValue(
const scaleType = axis.scaleType ?? ('linear' as const);
const extremums = [axis.min ?? minData, axis.max ?? maxData];
- const tickNumber = getTickNumber({ ...axis, range, domain: extremums });
+ const rawTickNumber = getTickNumber({ ...axis, range, domain: extremums });
+ const tickNumber = rawTickNumber / ((zoomRange[1] - zoomRange[0]) / 100);
+
+ const zoomedRange = zoomedScaleRange(range, zoomRange);
- const scale = getScale(scaleType, extremums, range).nice(tickNumber);
+ // TODO: move nice to prop? Disable when there is zoom?
+ const scale = getScale(scaleType, extremums, zoomedRange).nice(rawTickNumber);
const [minDomain, maxDomain] = scale.domain();
const domain = [axis.min ?? minDomain, axis.max ?? maxDomain];
diff --git a/packages/x-charts/src/context/ZAxisContextProvider.tsx b/packages/x-charts/src/context/ZAxisContextProvider.tsx
index fa24fd59a979..413dc0fad32f 100644
--- a/packages/x-charts/src/context/ZAxisContextProvider.tsx
+++ b/packages/x-charts/src/context/ZAxisContextProvider.tsx
@@ -69,7 +69,11 @@ function ZAxisContextProvider(props: ZAxisContextProviderProps) {
axis.colorMap &&
(axis.colorMap.type === 'ordinal' && axis.data
? getOrdinalColorScale({ values: axis.data, ...axis.colorMap })
- : getColorScale(axis.colorMap)),
+ : getColorScale(
+ axis.colorMap.type === 'continuous'
+ ? { min: axis.min, max: axis.max, ...axis.colorMap }
+ : axis.colorMap,
+ )),
};
});
@@ -127,6 +131,8 @@ ZAxisContextProvider.propTypes = {
data: PropTypes.array,
dataKey: PropTypes.string,
id: PropTypes.string,
+ max: PropTypes.number,
+ min: PropTypes.number,
}),
),
} as any;
diff --git a/packages/x-charts/src/hooks/index.ts b/packages/x-charts/src/hooks/index.ts
index 148bf66d030b..7f6e5dcdbdaf 100644
--- a/packages/x-charts/src/hooks/index.ts
+++ b/packages/x-charts/src/hooks/index.ts
@@ -1,6 +1,7 @@
export * from './useDrawingArea';
export * from './useChartId';
export * from './useScale';
+export * from './useAxis';
export * from './useColorScale';
export * from './useSvgRef';
export {
diff --git a/packages/x-charts/src/hooks/useAxis.ts b/packages/x-charts/src/hooks/useAxis.ts
new file mode 100644
index 000000000000..d3f3fd2325ba
--- /dev/null
+++ b/packages/x-charts/src/hooks/useAxis.ts
@@ -0,0 +1,17 @@
+import { useCartesianContext } from '../context/CartesianProvider';
+
+export function useXAxis(identifier?: number | string) {
+ const { xAxis, xAxisIds } = useCartesianContext();
+
+ const id = typeof identifier === 'string' ? identifier : xAxisIds[identifier ?? 0];
+
+ return xAxis[id];
+}
+
+export function useYAxis(identifier?: number | string) {
+ const { yAxis, yAxisIds } = useCartesianContext();
+
+ const id = typeof identifier === 'string' ? identifier : yAxisIds[identifier ?? 0];
+
+ return yAxis[id];
+}
diff --git a/packages/x-charts/src/hooks/useAxisEvents.ts b/packages/x-charts/src/hooks/useAxisEvents.ts
index 855ec5985c1a..2679b53e313a 100644
--- a/packages/x-charts/src/hooks/useAxisEvents.ts
+++ b/packages/x-charts/src/hooks/useAxisEvents.ts
@@ -3,7 +3,7 @@ import { InteractionContext } from '../context/InteractionProvider';
import { useCartesianContext } from '../context/CartesianProvider';
import { isBandScale } from '../internals/isBandScale';
import { AxisDefaultized } from '../models/axis';
-import { getSVGPoint } from '../internals/utils';
+import { getSVGPoint } from '../internals/getSVGPoint';
import { useSvgRef } from './useSvgRef';
import { useDrawingArea } from './useDrawingArea';
@@ -31,10 +31,7 @@ export const useAxisEvents = (disableAxisListener: boolean) => {
return () => {};
}
- const getUpdate = (axisConfig: AxisDefaultized, mouseValue: number) => {
- if (usedXAxis === null) {
- return null;
- }
+ function getNewAxisState(axisConfig: AxisDefaultized, mouseValue: number) {
const { scale, data: axisData, reverse } = axisConfig;
if (!isBandScale(scale)) {
@@ -93,7 +90,7 @@ export const useAxisEvents = (disableAxisListener: boolean) => {
index: dataIndex,
value: axisData![dataIndex],
};
- };
+ }
const handleOut = () => {
mousePosition.current = {
@@ -105,7 +102,7 @@ export const useAxisEvents = (disableAxisListener: boolean) => {
const handleMove = (event: MouseEvent | TouchEvent) => {
const target = 'targetTouches' in event ? event.targetTouches[0] : event;
- const svgPoint = getSVGPoint(svgRef.current!, target);
+ const svgPoint = getSVGPoint(element, target);
mousePosition.current = {
x: svgPoint.x,
@@ -118,21 +115,34 @@ export const useAxisEvents = (disableAxisListener: boolean) => {
dispatch({ type: 'exitChart' });
return;
}
- const newStateX = getUpdate(xAxis[usedXAxis], svgPoint.x);
- const newStateY = getUpdate(yAxis[usedYAxis], svgPoint.y);
+ const newStateX = getNewAxisState(xAxis[usedXAxis], svgPoint.x);
+ const newStateY = getNewAxisState(yAxis[usedYAxis], svgPoint.y);
dispatch({ type: 'updateAxis', data: { x: newStateX, y: newStateY } });
};
- element.addEventListener('mouseout', handleOut);
- element.addEventListener('mousemove', handleMove);
- element.addEventListener('touchend', handleOut);
- element.addEventListener('touchmove', handleMove);
+ const handleDown = (event: PointerEvent) => {
+ const target = event.currentTarget;
+ if (!target) {
+ return;
+ }
+
+ if ((target as HTMLElement).hasPointerCapture(event.pointerId)) {
+ (target as HTMLElement).releasePointerCapture(event.pointerId);
+ }
+ };
+
+ element.addEventListener('pointerdown', handleDown);
+ element.addEventListener('pointermove', handleMove);
+ element.addEventListener('pointerout', handleOut);
+ element.addEventListener('pointercancel', handleOut);
+ element.addEventListener('pointerleave', handleOut);
return () => {
- element.removeEventListener('mouseout', handleOut);
- element.removeEventListener('mousemove', handleMove);
- element.removeEventListener('touchend', handleOut);
- element.removeEventListener('touchmove', handleMove);
+ element.removeEventListener('pointerdown', handleDown);
+ element.removeEventListener('pointermove', handleMove);
+ element.removeEventListener('pointerout', handleOut);
+ element.removeEventListener('pointercancel', handleOut);
+ element.removeEventListener('pointerleave', handleOut);
};
}, [
svgRef,
diff --git a/packages/x-charts/src/hooks/useColor.ts b/packages/x-charts/src/hooks/useColor.ts
index c124c75cd796..68b6ecc59fa5 100644
--- a/packages/x-charts/src/hooks/useColor.ts
+++ b/packages/x-charts/src/hooks/useColor.ts
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { ChartSeriesType } from '../internals';
+import { ChartSeriesType } from '../models/seriesType/config';
import { ColorContext } from '../context/ColorProvider';
import { ColorProcessorsConfig } from '../models/plugin';
diff --git a/packages/x-charts/src/hooks/useInteractionItemProps.ts b/packages/x-charts/src/hooks/useInteractionItemProps.ts
index 07d9dc7dccbd..12ef0ce071c4 100644
--- a/packages/x-charts/src/hooks/useInteractionItemProps.ts
+++ b/packages/x-charts/src/hooks/useInteractionItemProps.ts
@@ -11,7 +11,12 @@ export const useInteractionItemProps = (skip?: boolean) => {
return () => ({});
}
const getInteractionItemProps = (data: SeriesItemIdentifier) => {
- const onMouseEnter = () => {
+ const onPointerDown = (event: React.PointerEvent) => {
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
+ event.currentTarget.releasePointerCapture(event.pointerId);
+ }
+ };
+ const onPointerEnter = () => {
dispatchInteraction({
type: 'enterItem',
data,
@@ -21,13 +26,15 @@ export const useInteractionItemProps = (skip?: boolean) => {
dataIndex: data.dataIndex,
});
};
- const onMouseLeave = () => {
+ const onPointerLeave = (event: React.PointerEvent) => {
+ event.currentTarget.releasePointerCapture(event.pointerId);
dispatchInteraction({ type: 'leaveItem', data });
clearHighlighted();
};
return {
- onMouseEnter,
- onMouseLeave,
+ onPointerEnter,
+ onPointerLeave,
+ onPointerDown,
};
};
return getInteractionItemProps;
diff --git a/packages/x-charts/src/hooks/useScale.ts b/packages/x-charts/src/hooks/useScale.ts
index 3af08f437bbf..62755e68f8cd 100644
--- a/packages/x-charts/src/hooks/useScale.ts
+++ b/packages/x-charts/src/hooks/useScale.ts
@@ -1,6 +1,6 @@
-import { useCartesianContext } from '../context/CartesianProvider';
import { isBandScale } from '../internals/isBandScale';
import { AxisScaleConfig, D3Scale, ScaleName } from '../models/axis';
+import { useXAxis, useYAxis } from './useAxis';
/**
* For a given scale return a function that map value to their position.
@@ -10,7 +10,7 @@ import { AxisScaleConfig, D3Scale, ScaleName } from '../models/axis';
*/
export function getValueToPositionMapper(scale: D3Scale) {
if (isBandScale(scale)) {
- return (value: any) => scale(value)! + scale.bandwidth() / 2;
+ return (value: any) => (scale(value) ?? 0) + scale.bandwidth() / 2;
}
return (value: any) => scale(value) as number;
}
@@ -18,19 +18,15 @@ export function getValueToPositionMapper(scale: D3Scale) {
export function useXScale(
identifier?: number | string,
): AxisScaleConfig[S]['scale'] {
- const { xAxis, xAxisIds } = useCartesianContext();
+ const axis = useXAxis(identifier);
- const id = typeof identifier === 'string' ? identifier : xAxisIds[identifier ?? 0];
-
- return xAxis[id].scale;
+ return axis.scale;
}
export function useYScale(
identifier?: number | string,
): AxisScaleConfig[S]['scale'] {
- const { yAxis, yAxisIds } = useCartesianContext();
-
- const id = typeof identifier === 'string' ? identifier : yAxisIds[identifier ?? 0];
+ const axis = useYAxis(identifier);
- return yAxis[id].scale;
+ return axis.scale;
}
diff --git a/packages/x-charts/src/hooks/useTicks.ts b/packages/x-charts/src/hooks/useTicks.ts
index 8049ebe780d1..ecc42b5727fe 100644
--- a/packages/x-charts/src/hooks/useTicks.ts
+++ b/packages/x-charts/src/hooks/useTicks.ts
@@ -101,8 +101,12 @@ export function useTicks(
if (scale.bandwidth() > 0) {
// scale type = 'band'
+ const filteredDomain =
+ (typeof tickInterval === 'function' && domain.filter(tickInterval)) ||
+ (typeof tickInterval === 'object' && tickInterval) ||
+ domain;
return [
- ...domain.map((value) => ({
+ ...filteredDomain.map((value) => ({
value,
formattedValue: valueFormatter?.(value, { location: 'tick' }) ?? `${value}`,
offset:
diff --git a/packages/x-charts/src/internals/getSVGPoint.ts b/packages/x-charts/src/internals/getSVGPoint.ts
new file mode 100644
index 000000000000..379752cdb307
--- /dev/null
+++ b/packages/x-charts/src/internals/getSVGPoint.ts
@@ -0,0 +1,11 @@
+/**
+ * Transform mouse event position to coordinates inside the SVG.
+ * @param svg The SVG element
+ * @param event The mouseEvent to transform
+ */
+export function getSVGPoint(svg: SVGSVGElement, event: Pick) {
+ const pt = svg.createSVGPoint();
+ pt.x = event.clientX;
+ pt.y = event.clientY;
+ return pt.matrixTransform(svg.getScreenCTM()!.inverse());
+}
diff --git a/packages/x-charts/src/internals/index.ts b/packages/x-charts/src/internals/index.ts
index ba3ecc5de8a8..924017fab51c 100644
--- a/packages/x-charts/src/internals/index.ts
+++ b/packages/x-charts/src/internals/index.ts
@@ -7,6 +7,8 @@ export * from '../ResponsiveChartContainer/ResizableContainer';
// hooks
export { useReducedMotion } from '../hooks/useReducedMotion';
export { useSeries } from '../hooks/useSeries';
+export { useInteractionItemProps } from '../hooks/useInteractionItemProps';
+export { useDrawingArea } from '../hooks/useDrawingArea';
export { useChartContainerHooks } from '../ChartContainer/useChartContainerHooks';
export { useScatterChartProps } from '../ScatterChart/useScatterChartProps';
export { useLineChartProps } from '../LineChart/useLineChartProps';
@@ -15,6 +17,8 @@ export { useBarChartProps } from '../BarChart/useBarChartProps';
// utils
export * from './defaultizeValueFormatter';
export * from './configInit';
+export * from './getLabel';
+export * from './getSVGPoint';
// contexts
diff --git a/packages/x-charts/src/internals/utils.ts b/packages/x-charts/src/internals/utils.ts
index 2896ea166b64..1517334d6e2a 100644
--- a/packages/x-charts/src/internals/utils.ts
+++ b/packages/x-charts/src/internals/utils.ts
@@ -9,18 +9,6 @@ export function getSymbol(shape: SymbolsTypes): number {
type Without = { [P in Exclude]?: never };
export type XOR = T | U extends object ? (Without & U) | (Without & T) : T | U;
-/**
- * Transform mouse event position to coordinates inside the SVG.
- * @param svg The SVG element
- * @param event The mouseEvent to transform
- */
-export function getSVGPoint(svg: SVGSVGElement, event: Pick) {
- const pt = svg.createSVGPoint();
- pt.x = event.clientX;
- pt.y = event.clientY;
- return pt.matrixTransform(svg.getScreenCTM()!.inverse());
-}
-
/**
* Helper that converts values and percentages into values.
* @param value The value provided by the developer. Can either be a number or a string with '%' or 'px'.
diff --git a/packages/x-charts/src/models/axis.ts b/packages/x-charts/src/models/axis.ts
index 2ca13a4836ff..651612e1ec52 100644
--- a/packages/x-charts/src/models/axis.ts
+++ b/packages/x-charts/src/models/axis.ts
@@ -156,7 +156,7 @@ export interface ChartsXAxisProps extends ChartsAxisProps {
position?: 'top' | 'bottom';
}
-export type ScaleName = 'linear' | 'band' | 'point' | 'log' | 'pow' | 'sqrt' | 'time' | 'utc';
+export type ScaleName = keyof AxisScaleConfig;
export type ContinuousScaleName = 'linear' | 'log' | 'pow' | 'sqrt' | 'time' | 'utc';
export interface AxisScaleConfig {
diff --git a/packages/x-charts/src/models/colorMapping.ts b/packages/x-charts/src/models/colorMapping.ts
index ba5dd13ecd5a..f14cf41cf89a 100644
--- a/packages/x-charts/src/models/colorMapping.ts
+++ b/packages/x-charts/src/models/colorMapping.ts
@@ -13,7 +13,7 @@ export interface ContinuousColorConfig {
/**
* The colors to render. It can be an array with the extremum colors, or an interpolation function.
*/
- color: [string, string] | ((t: number) => string);
+ color: readonly [string, string] | ((t: number) => string);
}
export interface PiecewiseColorConfig {
diff --git a/packages/x-charts/src/models/z-axis.ts b/packages/x-charts/src/models/z-axis.ts
index 0a493f5d8f68..194622049534 100644
--- a/packages/x-charts/src/models/z-axis.ts
+++ b/packages/x-charts/src/models/z-axis.ts
@@ -8,6 +8,14 @@ export interface ZAxisConfig {
* The key used to retrieve `data` from the `dataset` prop.
*/
dataKey?: string;
+ /**
+ * The minimal value of the scale.
+ */
+ min?: number;
+ /**
+ * The maximal value of the scale.
+ */
+ max?: number;
colorMap?: OrdinalColorConfig | ContinuousColorConfig | PiecewiseColorConfig;
}
diff --git a/packages/x-charts/src/themeAugmentation/props.d.ts b/packages/x-charts/src/themeAugmentation/props.d.ts
index 0189a5b19e67..7dc62f397400 100644
--- a/packages/x-charts/src/themeAugmentation/props.d.ts
+++ b/packages/x-charts/src/themeAugmentation/props.d.ts
@@ -13,6 +13,7 @@ import { LineChartProps } from '../LineChart/LineChart';
import { ScatterProps } from '../ScatterChart/Scatter';
import { ScatterChartProps } from '../ScatterChart/ScatterChart';
import { ChartsXAxisProps, ChartsYAxisProps } from '../models/axis';
+import { ChartSeriesType } from '../models/seriesType/config';
export interface ChartsComponentsPropsList {
MuiChartsAxis: ChartsAxisProps;
@@ -22,7 +23,7 @@ export interface ChartsComponentsPropsList {
MuiChartsClipPath: ChartsClipPathProps;
MuiChartsGrid: ChartsGridProps;
MuiChartsLegend: ChartsLegendProps;
- MuiChartsTooltip: ChartsTooltipProps;
+ MuiChartsTooltip: ChartsTooltipProps;
MuiChartsSurface: ChartsSurfaceProps;
// BarChart components
diff --git a/packages/x-codemod/package.json b/packages/x-codemod/package.json
index 2b97f82feb4e..1f87794b337f 100644
--- a/packages/x-codemod/package.json
+++ b/packages/x-codemod/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/x-codemod",
- "version": "7.7.0",
+ "version": "7.9.0",
"bin": "./codemod.js",
"private": false,
"author": "MUI Team",
@@ -35,15 +35,14 @@
"@babel/core": "^7.24.7",
"@babel/runtime": "^7.24.7",
"@babel/traverse": "^7.24.7",
- "jscodeshift": "0.13.1",
- "jscodeshift-add-imports": "^1.0.10",
+ "jscodeshift": "0.16.1",
"yargs": "^17.7.2"
},
"devDependencies": {
- "@types/jscodeshift": "^0.11.5",
+ "@types/jscodeshift": "^0.11.11",
"dayjs": "^1.11.11",
"moment-timezone": "^0.5.45",
- "rimraf": "^5.0.7"
+ "rimraf": "^5.0.8"
},
"sideEffects": false,
"publishConfig": {
diff --git a/packages/x-codemod/src/codemod.ts b/packages/x-codemod/src/codemod.ts
index 99580a0b38c9..1ecceba57d8e 100755
--- a/packages/x-codemod/src/codemod.ts
+++ b/packages/x-codemod/src/codemod.ts
@@ -72,7 +72,7 @@ Not all use cases are covered by codemods. In some scenarios, like props spreadi
For example, if a codemod tries to rename a prop, but this prop is hidden with the spread operator, it won't be transformed as expected.
-After running the codemods, make sure to test your application and that you don't have any console errors.
+After running the codemods, make sure to test your application and that you don't have any formatting or console errors.
`);
const jscodeshiftProcess = childProcess.spawnSync('node', args, { stdio: 'inherit' });
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/actual-community-root-imports.spec.tsx b/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/actual-community-root-imports.spec.tsx
index d86c75b8498b..72f7d3f7c3f0 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/actual-community-root-imports.spec.tsx
+++ b/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/actual-community-root-imports.spec.tsx
@@ -7,6 +7,7 @@ import {
GridFilterItemProps,
} from '@mui/x-data-grid';
+// prettier-ignore
function App({ column, hideMenu }: GridFilterItemProps) {
return (
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/actual-premium-root-imports.spec.tsx b/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/actual-premium-root-imports.spec.tsx
index 373cef5160ff..711e1e82945d 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/actual-premium-root-imports.spec.tsx
+++ b/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/actual-premium-root-imports.spec.tsx
@@ -9,6 +9,7 @@ import {
GridFilterItemProps,
} from '@mui/x-data-grid-premium';
+// prettier-ignore
function App({ column, hideMenu }: GridFilterItemProps) {
return (
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/actual-pro-root-imports.spec.tsx b/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/actual-pro-root-imports.spec.tsx
index 6b342d1530e2..8edf7f57624e 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/actual-pro-root-imports.spec.tsx
+++ b/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/actual-pro-root-imports.spec.tsx
@@ -8,6 +8,7 @@ import {
GridFilterItemProps,
} from '@mui/x-data-grid-pro';
+// prettier-ignore
function App({ column, hideMenu }: GridFilterItemProps) {
return (
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/expected-community-root-imports.spec.tsx b/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/expected-community-root-imports.spec.tsx
index 70823212a0dd..98144b1db844 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/expected-community-root-imports.spec.tsx
+++ b/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/expected-community-root-imports.spec.tsx
@@ -7,13 +7,14 @@ import {
GridColumnMenuItemProps,
} from '@mui/x-data-grid';
+// prettier-ignore
function App({ column, hideMenu }: GridColumnMenuItemProps) {
return (
-
+ (
-
+ )
);
}
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/expected-premium-root-imports.spec.tsx b/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/expected-premium-root-imports.spec.tsx
index 93e452123d61..bf7d1a63e435 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/expected-premium-root-imports.spec.tsx
+++ b/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/expected-premium-root-imports.spec.tsx
@@ -9,15 +9,16 @@ import {
GridColumnMenuItemProps,
} from '@mui/x-data-grid-premium';
+// prettier-ignore
function App({ column, hideMenu }: GridColumnMenuItemProps) {
return (
-
+ (
-
+ )
);
}
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/expected-pro-root-imports.spec.tsx b/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/expected-pro-root-imports.spec.tsx
index 4ca25b6153b1..6fee5b0a9cdd 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/expected-pro-root-imports.spec.tsx
+++ b/packages/x-codemod/src/v6.0.0/data-grid/column-menu-components-rename/expected-pro-root-imports.spec.tsx
@@ -8,14 +8,15 @@ import {
GridColumnMenuItemProps,
} from '@mui/x-data-grid-pro';
+// prettier-ignore
function App({ column, hideMenu }: GridColumnMenuItemProps) {
return (
-
+ (
-
+ )
);
}
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/preset-safe/expected.spec.js b/packages/x-codemod/src/v6.0.0/data-grid/preset-safe/expected.spec.js
index 41477addc458..f9fae32f4404 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/preset-safe/expected.spec.js
+++ b/packages/x-codemod/src/v6.0.0/data-grid/preset-safe/expected.spec.js
@@ -9,7 +9,7 @@ function App({ column, hideMenu, apiRef, handleEvent }) {
event.defaultMuiPrevented = true;
};
return (
-
+ (
-
+ )
);
}
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/remove-disableExtendRowFullWidth-prop/expected.spec.js b/packages/x-codemod/src/v6.0.0/data-grid/remove-disableExtendRowFullWidth-prop/expected.spec.js
index 21890a001622..808794c5a841 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/remove-disableExtendRowFullWidth-prop/expected.spec.js
+++ b/packages/x-codemod/src/v6.0.0/data-grid/remove-disableExtendRowFullWidth-prop/expected.spec.js
@@ -5,11 +5,11 @@ import { DataGridPremium } from '@mui/x-data-grid-premium';
function App() {
return (
-
+ (
-
+ )
);
}
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/remove-stabilized-experimentalFeatures/expected.spec.js b/packages/x-codemod/src/v6.0.0/data-grid/remove-stabilized-experimentalFeatures/expected.spec.js
index b1e7d1fab199..4d325b05607f 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/remove-stabilized-experimentalFeatures/expected.spec.js
+++ b/packages/x-codemod/src/v6.0.0/data-grid/remove-stabilized-experimentalFeatures/expected.spec.js
@@ -5,7 +5,7 @@ import { DataGridPremium } from '@mui/x-data-grid-premium';
function App() {
return (
-
+ (
-
+ )
);
}
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/rename-components-to-slots/expected.spec.js b/packages/x-codemod/src/v6.0.0/data-grid/rename-components-to-slots/expected.spec.js
index d0b73a667a41..352cda988bb7 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/rename-components-to-slots/expected.spec.js
+++ b/packages/x-codemod/src/v6.0.0/data-grid/rename-components-to-slots/expected.spec.js
@@ -5,7 +5,7 @@ import { Button, Checkbox, TextField } from '@mui/material';
export default function App() {
return (
- )
);
};
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/rename-filter-item-props/actual.spec.tsx b/packages/x-codemod/src/v6.0.0/data-grid/rename-filter-item-props/actual.spec.tsx
index a814c59a1771..6ff73a803484 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/rename-filter-item-props/actual.spec.tsx
+++ b/packages/x-codemod/src/v6.0.0/data-grid/rename-filter-item-props/actual.spec.tsx
@@ -11,6 +11,7 @@ const rows = [
{ id: 3, column: 'c', name: 'James', score: 300 },
];
+// prettier-ignore
function App() {
const [proFilterModel, setProFilterModel] = React.useState({
items: [
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/rename-filter-item-props/expected.spec.tsx b/packages/x-codemod/src/v6.0.0/data-grid/rename-filter-item-props/expected.spec.tsx
index fa41bdc0b0dc..f3ec4e5f0d1d 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/rename-filter-item-props/expected.spec.tsx
+++ b/packages/x-codemod/src/v6.0.0/data-grid/rename-filter-item-props/expected.spec.tsx
@@ -11,6 +11,7 @@ const rows = [
{ id: 3, column: 'c', name: 'James', score: 300 },
];
+// prettier-ignore
function App() {
const [proFilterModel, setProFilterModel] = React.useState({
items: [
@@ -31,7 +32,7 @@ function App() {
],
});
return (
-
+ (
-
+ )
);
}
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/rename-linkOperators-logicOperators/expected.spec.js b/packages/x-codemod/src/v6.0.0/data-grid/rename-linkOperators-logicOperators/expected.spec.js
index ac53997eca3b..591a632901f9 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/rename-linkOperators-logicOperators/expected.spec.js
+++ b/packages/x-codemod/src/v6.0.0/data-grid/rename-linkOperators-logicOperators/expected.spec.js
@@ -38,7 +38,7 @@ function App ({ apiRef, initialState }) {
const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(1);
const localeText = apiRef.current.getLocaleText('filterPanelLogicOperator');
return (
-
+ (
-
+ )
);
}
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/replace-onCellFocusOut-prop/expected.spec.js b/packages/x-codemod/src/v6.0.0/data-grid/replace-onCellFocusOut-prop/expected.spec.js
index 7501bace84fc..6d5753ccf0bd 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/replace-onCellFocusOut-prop/expected.spec.js
+++ b/packages/x-codemod/src/v6.0.0/data-grid/replace-onCellFocusOut-prop/expected.spec.js
@@ -9,7 +9,7 @@ function App() {
event.defaultMuiPrevented = true;
};
return (
-
+ (
-
+ )
);
}
diff --git a/packages/x-codemod/src/v6.0.0/data-grid/row-selection-props-rename/expected.spec.js b/packages/x-codemod/src/v6.0.0/data-grid/row-selection-props-rename/expected.spec.js
index 6c32968793cc..db5d4bd8092a 100644
--- a/packages/x-codemod/src/v6.0.0/data-grid/row-selection-props-rename/expected.spec.js
+++ b/packages/x-codemod/src/v6.0.0/data-grid/row-selection-props-rename/expected.spec.js
@@ -5,7 +5,7 @@ import { DataGridPremium } from '@mui/x-data-grid-premium'
function App () {
return (
-
+ (
{}}
@@ -33,7 +33,7 @@ function App () {
showColumnVerticalBorder
columnHeaderHeight={56}
/>
-
+ )
);
}
diff --git a/packages/x-codemod/src/v6.0.0/pickers/preset-safe/expected.spec.js b/packages/x-codemod/src/v6.0.0/pickers/preset-safe/expected.spec.js
index ab2e84985496..fde3443109c5 100644
--- a/packages/x-codemod/src/v6.0.0/pickers/preset-safe/expected.spec.js
+++ b/packages/x-codemod/src/v6.0.0/pickers/preset-safe/expected.spec.js
@@ -10,7 +10,7 @@ const theme = createTheme({});
function App() {
return (
-
+ (
-
+ )
);
}
diff --git a/packages/x-codemod/src/v6.0.0/pickers/view-components-rename/actual-community-nested-imports.spec.tsx b/packages/x-codemod/src/v6.0.0/pickers/view-components-rename/actual-community-nested-imports.spec.tsx
index cedfe91decf7..005ae673eff3 100644
--- a/packages/x-codemod/src/v6.0.0/pickers/view-components-rename/actual-community-nested-imports.spec.tsx
+++ b/packages/x-codemod/src/v6.0.0/pickers/view-components-rename/actual-community-nested-imports.spec.tsx
@@ -43,6 +43,7 @@ import {
getCalendarPickerSkeletonUtilityClass,
} from '@mui/x-date-pickers/CalendarPickerSkeleton';
+// prettier-ignore
function App() {
type DateProps = CalendarPickerProps;
type MonthProps = MonthPickerProps;
diff --git a/packages/x-codemod/src/v6.0.0/pickers/view-components-rename/expected-community-nested-imports.spec.tsx b/packages/x-codemod/src/v6.0.0/pickers/view-components-rename/expected-community-nested-imports.spec.tsx
index b3f1a2f1f3fe..c62416dda35e 100644
--- a/packages/x-codemod/src/v6.0.0/pickers/view-components-rename/expected-community-nested-imports.spec.tsx
+++ b/packages/x-codemod/src/v6.0.0/pickers/view-components-rename/expected-community-nested-imports.spec.tsx
@@ -43,6 +43,7 @@ import {
getDayCalendarSkeletonUtilityClass,
} from '@mui/x-date-pickers/DayCalendarSkeleton';
+// prettier-ignore
function App() {
type DateProps = DateCalendarProps;
type MonthProps = MonthCalendarProps;
@@ -57,12 +58,12 @@ function App() {
getDayCalendarSkeletonUtilityClass('root');
return (
-
+ (
{}} />
{}} />
{}} />
{}} />
-
+ )
);
}
diff --git a/packages/x-codemod/src/v6.0.0/preset-safe/expected.spec.js b/packages/x-codemod/src/v6.0.0/preset-safe/expected.spec.js
index f7ac5f8c1d41..62bd3c968096 100644
--- a/packages/x-codemod/src/v6.0.0/preset-safe/expected.spec.js
+++ b/packages/x-codemod/src/v6.0.0/preset-safe/expected.spec.js
@@ -10,7 +10,7 @@ const theme = createTheme({});
function App() {
return (
-
+ (
-
+ )
);
}
diff --git a/packages/x-codemod/src/v7.0.0/data-grid/preset-safe/expected.spec.js b/packages/x-codemod/src/v7.0.0/data-grid/preset-safe/expected.spec.js
index 179570a7c8d6..46659a519cbe 100644
--- a/packages/x-codemod/src/v7.0.0/data-grid/preset-safe/expected.spec.js
+++ b/packages/x-codemod/src/v7.0.0/data-grid/preset-safe/expected.spec.js
@@ -5,7 +5,7 @@ import { Button, Checkbox, TextField } from '@mui/material';
export default function App() {
return (
- )
);
};
diff --git a/packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/expected.spec.js b/packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/expected.spec.js
index 85baca51c531..d1a1271fa523 100644
--- a/packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/expected.spec.js
+++ b/packages/x-codemod/src/v7.0.0/data-grid/remove-stabilized-experimentalFeatures/expected.spec.js
@@ -5,12 +5,12 @@ import { DataGridPremium } from '@mui/x-data-grid-premium';
function App() {
return (
-
+ (
-
+ )
);
}
diff --git a/packages/x-codemod/src/v7.0.0/data-grid/rename-cell-selection-props/expected.spec.js b/packages/x-codemod/src/v7.0.0/data-grid/rename-cell-selection-props/expected.spec.js
index 98d4af7b179a..811da03b32f8 100644
--- a/packages/x-codemod/src/v7.0.0/data-grid/rename-cell-selection-props/expected.spec.js
+++ b/packages/x-codemod/src/v7.0.0/data-grid/rename-cell-selection-props/expected.spec.js
@@ -5,11 +5,11 @@ import { DataGridPremium } from '@mui/x-data-grid-premium'
function App () {
return (
- {}}
- />
+ />)
);
}
diff --git a/packages/x-codemod/src/v7.0.0/preset-safe/expected.spec.js b/packages/x-codemod/src/v7.0.0/preset-safe/expected.spec.js
index cb903bbf3063..42467180d72c 100644
--- a/packages/x-codemod/src/v7.0.0/preset-safe/expected.spec.js
+++ b/packages/x-codemod/src/v7.0.0/preset-safe/expected.spec.js
@@ -9,7 +9,7 @@ const theme = createTheme({});
function App() {
return (
-
+ (
-
+ )
);
}
diff --git a/packages/x-data-grid-generator/package.json b/packages/x-data-grid-generator/package.json
index ce85b0dcf6da..99f993d5a7fd 100644
--- a/packages/x-data-grid-generator/package.json
+++ b/packages/x-data-grid-generator/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/x-data-grid-generator",
- "version": "7.7.1",
+ "version": "7.9.0",
"description": "Generate fake data for demo purposes only.",
"author": "MUI Team",
"main": "src/index.ts",
@@ -38,12 +38,11 @@
"@mui/x-data-grid-premium": "workspace:*",
"chance": "^1.1.11",
"clsx": "^2.1.1",
- "lru-cache": "^7.18.3"
+ "lru-cache": "^10.3.0"
},
"devDependencies": {
"@types/chance": "^1.1.6",
- "@types/lru-cache": "^7.10.10",
- "rimraf": "^5.0.7"
+ "rimraf": "^5.0.8"
},
"peerDependencies": {
"@mui/icons-material": "^5.4.1",
diff --git a/packages/x-data-grid-generator/src/hooks/index.ts b/packages/x-data-grid-generator/src/hooks/index.ts
index 06e612fa89c8..84dd7368aea4 100644
--- a/packages/x-data-grid-generator/src/hooks/index.ts
+++ b/packages/x-data-grid-generator/src/hooks/index.ts
@@ -2,3 +2,6 @@ export * from './useDemoData';
export * from './useBasicDemoData';
export * from './useMovieData';
export * from './useQuery';
+export * from './useMockServer';
+export { loadServerRows } from './serverUtils';
+export type { QueryOptions } from './serverUtils';
diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts
new file mode 100644
index 000000000000..095893126643
--- /dev/null
+++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts
@@ -0,0 +1,492 @@
+import {
+ GridRowModel,
+ GridFilterModel,
+ GridSortModel,
+ GridLogicOperator,
+ GridFilterOperator,
+ GridColDef,
+ GridRowId,
+ GridPaginationModel,
+ GridValidRowModel,
+} from '@mui/x-data-grid-pro';
+import { GridStateColDef } from '@mui/x-data-grid-pro/internals';
+import { UseDemoDataOptions } from './useDemoData';
+import { randomInt } from '../services/random-generator';
+
+export interface FakeServerResponse {
+ returnedRows: GridRowModel[];
+ nextCursor?: string;
+ hasNextPage?: boolean;
+ totalRowCount: number;
+}
+
+export interface PageInfo {
+ totalRowCount?: number;
+ nextCursor?: string;
+ hasNextPage?: boolean;
+ pageSize?: number;
+}
+
+export interface DefaultServerOptions {
+ minDelay: number;
+ maxDelay: number;
+ useCursorPagination?: boolean;
+}
+
+export type ServerOptions = Partial;
+
+export interface QueryOptions {
+ cursor?: GridRowId;
+ page?: number;
+ pageSize?: number;
+ filterModel?: GridFilterModel;
+ sortModel?: GridSortModel;
+ firstRowToRender?: number;
+ lastRowToRender?: number;
+}
+
+export interface ServerSideQueryOptions {
+ cursor?: GridRowId;
+ paginationModel?: GridPaginationModel;
+ groupKeys?: string[];
+ filterModel?: GridFilterModel;
+ sortModel?: GridSortModel;
+ firstRowToRender?: number;
+ lastRowToRender?: number;
+}
+
+export const DEFAULT_DATASET_OPTIONS: UseDemoDataOptions = {
+ dataSet: 'Commodity',
+ rowLength: 100,
+ maxColumns: 6,
+};
+
+declare const DISABLE_CHANCE_RANDOM: any;
+export const disableDelay = typeof DISABLE_CHANCE_RANDOM !== 'undefined' && DISABLE_CHANCE_RANDOM;
+
+export const DEFAULT_SERVER_OPTIONS: DefaultServerOptions = {
+ minDelay: disableDelay ? 0 : 100,
+ maxDelay: disableDelay ? 0 : 300,
+ useCursorPagination: true,
+};
+
+const apiRef = {} as any;
+
+const simplifiedValueGetter = (field: string, colDef: GridColDef) => (row: GridRowModel) => {
+ return colDef.valueGetter?.(row[row.id] as never, row, colDef, apiRef) || row[field];
+};
+
+const getRowComparator = (
+ sortModel: GridSortModel | undefined,
+ columnsWithDefaultColDef: GridColDef[],
+) => {
+ if (!sortModel) {
+ const comparator = () => 0;
+ return comparator;
+ }
+ const sortOperators = sortModel.map((sortItem) => {
+ const columnField = sortItem.field;
+ const colDef = columnsWithDefaultColDef.find(({ field }) => field === columnField) as any;
+ return {
+ ...sortItem,
+ valueGetter: simplifiedValueGetter(columnField, colDef),
+ sortComparator: colDef.sortComparator,
+ };
+ });
+
+ const comparator = (row1: GridRowModel, row2: GridRowModel) =>
+ sortOperators.reduce((acc, { valueGetter, sort, sortComparator }) => {
+ if (acc !== 0) {
+ return acc;
+ }
+ const v1 = valueGetter(row1);
+ const v2 = valueGetter(row2);
+ return sort === 'desc' ? -1 * sortComparator(v1, v2) : sortComparator(v1, v2);
+ }, 0);
+
+ return comparator;
+};
+
+const buildQuickFilterApplier = (filterModel: GridFilterModel, columns: GridColDef[]) => {
+ const quickFilterValues = filterModel.quickFilterValues?.filter(Boolean) ?? [];
+ if (quickFilterValues.length === 0) {
+ return null;
+ }
+
+ const appliersPerField = [] as {
+ column: GridColDef;
+ appliers: {
+ fn: null | ((...args: any[]) => boolean);
+ }[];
+ }[];
+
+ const stubApiRef = {
+ current: {
+ getRowFormattedValue: (row: GridValidRowModel, c: GridColDef) => {
+ const field = c.field;
+ return row[field];
+ },
+ },
+ };
+
+ columns.forEach((column) => {
+ const getApplyQuickFilterFn = column?.getApplyQuickFilterFn;
+
+ if (getApplyQuickFilterFn) {
+ appliersPerField.push({
+ column,
+ appliers: quickFilterValues.map((quickFilterValue) => {
+ return {
+ fn: getApplyQuickFilterFn(
+ quickFilterValue,
+ column as GridStateColDef,
+ stubApiRef as any,
+ ),
+ };
+ }),
+ });
+ }
+ });
+
+ return function isRowMatchingQuickFilter(
+ row: GridValidRowModel,
+ shouldApplyFilter?: (field: string) => boolean,
+ ) {
+ const result = {} as Record;
+
+ /* eslint-disable no-labels */
+ outer: for (let v = 0; v < quickFilterValues.length; v += 1) {
+ const filterValue = quickFilterValues[v];
+
+ for (let i = 0; i < appliersPerField.length; i += 1) {
+ const { column, appliers } = appliersPerField[i];
+ const { field } = column;
+
+ if (shouldApplyFilter && !shouldApplyFilter(field)) {
+ continue;
+ }
+
+ const applier = appliers[v];
+ const value = row[field];
+
+ if (applier.fn === null) {
+ continue;
+ }
+ const isMatching = applier.fn(value, row, column, stubApiRef);
+
+ if (isMatching) {
+ result[filterValue] = true;
+ continue outer;
+ }
+ }
+
+ result[filterValue] = false;
+ }
+ /* eslint-enable no-labels */
+
+ return result;
+ };
+};
+
+const getQuicklyFilteredRows = (
+ rows: GridRowModel[],
+ filterModel: GridFilterModel | undefined,
+ columnsWithDefaultColDef: GridColDef[],
+) => {
+ if (filterModel === undefined || filterModel.quickFilterValues?.length === 0) {
+ return rows;
+ }
+
+ const isRowMatchingQuickFilter = buildQuickFilterApplier(filterModel, columnsWithDefaultColDef);
+
+ if (isRowMatchingQuickFilter) {
+ return rows.filter((row) => {
+ const result = isRowMatchingQuickFilter(row);
+ return filterModel.quickFilterLogicOperator === GridLogicOperator.And
+ ? Object.values(result).every(Boolean)
+ : Object.values(result).some(Boolean);
+ });
+ }
+ return rows;
+};
+
+const getFilteredRows = (
+ rows: GridRowModel[],
+ filterModel: GridFilterModel | undefined,
+ columnsWithDefaultColDef: GridColDef[],
+) => {
+ if (filterModel === undefined || filterModel.items.length === 0) {
+ return rows;
+ }
+
+ const valueGetters = filterModel.items.map(({ field }) =>
+ simplifiedValueGetter(
+ field,
+ columnsWithDefaultColDef.find((column) => column.field === field) as any,
+ ),
+ );
+
+ const filterFunctions = filterModel.items.map((filterItem) => {
+ const { field, operator } = filterItem;
+ const colDef: GridColDef = columnsWithDefaultColDef.find(
+ (column) => column.field === field,
+ ) as any;
+
+ if (!colDef.filterOperators) {
+ throw new Error(`MUI: No filter operator found for column '${field}'.`);
+ }
+ const filterOperator: any = colDef.filterOperators.find(
+ ({ value }: GridFilterOperator) => operator === value,
+ );
+
+ let parsedValue = filterItem.value;
+
+ if (colDef.valueParser) {
+ const parser = colDef.valueParser;
+ parsedValue = Array.isArray(filterItem.value)
+ ? filterItem.value?.map((x) => parser(x, {}, colDef, apiRef))
+ : parser(filterItem.value, {}, colDef, apiRef);
+ }
+
+ return filterOperator.getApplyFilterFn({ filterItem, value: parsedValue }, colDef);
+ });
+
+ if (filterModel.logicOperator === GridLogicOperator.Or) {
+ return rows.filter((row: GridRowModel) =>
+ filterModel.items.some((_, index) => {
+ const value = valueGetters[index](row);
+ return filterFunctions[index] === null ? true : filterFunctions[index](value);
+ }),
+ );
+ }
+ return rows.filter((row: GridRowModel) =>
+ filterModel.items.every((_, index) => {
+ const value = valueGetters[index](row);
+ return filterFunctions[index] === null ? true : filterFunctions[index](value);
+ }),
+ );
+};
+
+/**
+ * Simulates server data loading
+ */
+export const loadServerRows = (
+ rows: GridRowModel[],
+ queryOptions: QueryOptions,
+ serverOptions: ServerOptions,
+ columnsWithDefaultColDef: GridColDef[],
+): Promise => {
+ const { minDelay = 100, maxDelay = 300, useCursorPagination } = serverOptions;
+
+ if (maxDelay < minDelay) {
+ throw new Error('serverOptions.minDelay is larger than serverOptions.maxDelay ');
+ }
+ const delay = randomInt(minDelay, maxDelay);
+
+ const { cursor, page = 0, pageSize } = queryOptions;
+
+ let nextCursor;
+ let firstRowIndex;
+ let lastRowIndex;
+
+ let filteredRows = getFilteredRows(rows, queryOptions.filterModel, columnsWithDefaultColDef);
+
+ const rowComparator = getRowComparator(queryOptions.sortModel, columnsWithDefaultColDef);
+ filteredRows = [...filteredRows].sort(rowComparator);
+
+ const totalRowCount = filteredRows.length;
+ if (!pageSize) {
+ firstRowIndex = 0;
+ lastRowIndex = filteredRows.length;
+ } else if (useCursorPagination) {
+ firstRowIndex = cursor ? filteredRows.findIndex(({ id }) => id === cursor) : 0;
+ firstRowIndex = Math.max(firstRowIndex, 0); // if cursor not found return 0
+ lastRowIndex = firstRowIndex + pageSize;
+
+ nextCursor = lastRowIndex >= filteredRows.length ? undefined : filteredRows[lastRowIndex].id;
+ } else {
+ firstRowIndex = page * pageSize;
+ lastRowIndex = (page + 1) * pageSize;
+ }
+ const hasNextPage = lastRowIndex < filteredRows.length - 1;
+ const response: FakeServerResponse = {
+ returnedRows: filteredRows.slice(firstRowIndex, lastRowIndex),
+ hasNextPage,
+ nextCursor,
+ totalRowCount,
+ };
+
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(response);
+ }, delay); // simulate network latency
+ });
+};
+
+interface ProcessTreeDataRowsResponse {
+ rows: GridRowModel[];
+ rootRowCount: number;
+}
+
+const findTreeDataRowChildren = (
+ allRows: GridRowModel[],
+ parentPath: string[],
+ pathKey: string = 'path',
+ depth: number = 1, // the depth of the children to find relative to parentDepth, `-1` to find all
+) => {
+ const parentDepth = parentPath.length;
+ const children = [];
+ for (let i = 0; i < allRows.length; i += 1) {
+ const row = allRows[i];
+ const rowPath = row[pathKey];
+ if (!rowPath) {
+ continue;
+ }
+ if (
+ ((depth < 0 && rowPath.length > parentDepth) || rowPath.length === parentDepth + depth) &&
+ parentPath.every((value, index) => value === rowPath[index])
+ ) {
+ children.push(row);
+ }
+ }
+ return children;
+};
+
+type GetTreeDataFilteredRows = (
+ rows: GridValidRowModel[],
+ filterModel: GridFilterModel | undefined,
+ columnsWithDefaultColDef: GridColDef[],
+) => GridValidRowModel;
+
+const getTreeDataFilteredRows: GetTreeDataFilteredRows = (
+ rows,
+ filterModel,
+ columnsWithDefaultColDef,
+): GridValidRowModel[] => {
+ let filteredRows = [...rows];
+ if (filterModel && filterModel.quickFilterValues?.length! > 0) {
+ filteredRows = getQuicklyFilteredRows(rows, filterModel, columnsWithDefaultColDef);
+ }
+ if ((filterModel?.items.length ?? 0) > 0) {
+ filteredRows = getFilteredRows(filteredRows, filterModel, columnsWithDefaultColDef);
+ }
+
+ if (filteredRows.length === rows.length || filteredRows.length === 0) {
+ return filteredRows;
+ }
+
+ const pathsToIndexesMap = new Map();
+ rows.forEach((row: GridValidRowModel, index: number) => {
+ pathsToIndexesMap.set(row.path.join(','), index);
+ });
+
+ const includedPaths = new Set();
+ filteredRows.forEach((row) => {
+ includedPaths.add(row.path.join(','));
+ });
+
+ const missingChildren: GridValidRowModel[] = [];
+
+ // include missing children of filtered rows
+ filteredRows.forEach((row) => {
+ const path = row.path;
+ if (path) {
+ const children = findTreeDataRowChildren(rows, path, 'path', -1);
+ children.forEach((child) => {
+ const subPath = child.path.join(',');
+ if (!includedPaths.has(subPath)) {
+ missingChildren.push(child);
+ }
+ });
+ }
+ });
+
+ filteredRows = missingChildren.concat(filteredRows);
+
+ const missingParents: GridValidRowModel[] = [];
+
+ // include missing parents of filtered rows
+ filteredRows.forEach((row) => {
+ const path = row.path;
+ if (path) {
+ includedPaths.add(path.join(','));
+ for (let i = 0; i < path.length - 1; i += 1) {
+ const subPath = path.slice(0, i + 1).join(',');
+ if (!includedPaths.has(subPath)) {
+ const index = pathsToIndexesMap.get(subPath);
+ if (index !== undefined) {
+ missingParents.push(rows[index]);
+ includedPaths.add(subPath);
+ }
+ }
+ }
+ }
+ });
+
+ return missingParents.concat(filteredRows);
+};
+
+/**
+ * Simulates server data loading
+ */
+export const processTreeDataRows = (
+ rows: GridRowModel[],
+ queryOptions: ServerSideQueryOptions,
+ serverOptions: ServerOptions,
+ columnsWithDefaultColDef: GridColDef[],
+): Promise => {
+ const { minDelay = 100, maxDelay = 300 } = serverOptions;
+ const pathKey = 'path';
+ // TODO: Support filtering and cursor based pagination
+ if (maxDelay < minDelay) {
+ throw new Error('serverOptions.minDelay is larger than serverOptions.maxDelay ');
+ }
+
+ if (queryOptions.groupKeys == null) {
+ throw new Error('serverOptions.groupKeys must be defined to compute tree data ');
+ }
+
+ const delay = randomInt(minDelay, maxDelay);
+
+ // apply plain filtering
+ const filteredRows = getTreeDataFilteredRows(
+ rows,
+ queryOptions.filterModel,
+ columnsWithDefaultColDef,
+ ) as GridValidRowModel[];
+
+ // get root row count
+ const rootRowCount = findTreeDataRowChildren(filteredRows, []).length;
+
+ // find direct children referring to the `parentPath`
+ const childRows = findTreeDataRowChildren(filteredRows, queryOptions.groupKeys);
+
+ let childRowsWithDescendantCounts = childRows.map((row) => {
+ const descendants = findTreeDataRowChildren(filteredRows, row[pathKey], pathKey, -1);
+ const descendantCount = descendants.length;
+ return { ...row, descendantCount } as GridRowModel;
+ });
+
+ if (queryOptions.sortModel) {
+ // apply sorting
+ const rowComparator = getRowComparator(queryOptions.sortModel, columnsWithDefaultColDef);
+ childRowsWithDescendantCounts = [...childRowsWithDescendantCounts].sort(rowComparator);
+ }
+
+ if (queryOptions.paginationModel && queryOptions.groupKeys.length === 0) {
+ // Only paginate root rows, grid should refetch root rows when `paginationModel` updates
+ const { pageSize, page } = queryOptions.paginationModel;
+ if (pageSize < childRowsWithDescendantCounts.length) {
+ childRowsWithDescendantCounts = childRowsWithDescendantCounts.slice(
+ page * pageSize,
+ (page + 1) * pageSize,
+ );
+ }
+ }
+
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve({ rows: childRowsWithDescendantCounts, rootRowCount });
+ }, delay); // simulate network latency
+ });
+};
diff --git a/packages/x-data-grid-generator/src/hooks/useDemoData.ts b/packages/x-data-grid-generator/src/hooks/useDemoData.ts
index 28ad937181b9..2030e6db8240 100644
--- a/packages/x-data-grid-generator/src/hooks/useDemoData.ts
+++ b/packages/x-data-grid-generator/src/hooks/useDemoData.ts
@@ -1,5 +1,5 @@
import * as React from 'react';
-import LRUCache from 'lru-cache';
+import { LRUCache } from 'lru-cache';
import { GridColumnVisibilityModel } from '@mui/x-data-grid-premium';
import { GridDemoData, getRealGridData } from '../services/real-data-service';
import { getCommodityColumns } from '../columns/commodities.columns';
@@ -37,7 +37,10 @@ export interface UseDemoDataOptions {
// Generate fake data from a seed.
// It's about x20 faster than getRealData.
-async function extrapolateSeed(rowLength: number, data: GridDemoData): Promise {
+export async function extrapolateSeed(
+ rowLength: number,
+ data: GridDemoData,
+): Promise {
return new Promise((resolve) => {
const seed = data.rows;
const rows = data.rows.slice();
@@ -70,7 +73,7 @@ async function extrapolateSeed(rowLength: number, data: GridDemoData): Promise(object: T): T => {
+export const deepFreeze = (object: T): T => {
// Retrieve the property names defined on object
const propNames = Object.getOwnPropertyNames(object);
diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts
new file mode 100644
index 000000000000..cf38b1fa3c85
--- /dev/null
+++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts
@@ -0,0 +1,278 @@
+import * as React from 'react';
+import { LRUCache } from 'lru-cache';
+import {
+ getGridDefaultColumnTypes,
+ GridRowModel,
+ GridGetRowsParams,
+ GridGetRowsResponse,
+ GridColDef,
+ GridInitialState,
+ GridColumnVisibilityModel,
+} from '@mui/x-data-grid-pro';
+import {
+ UseDemoDataOptions,
+ getColumnsFromOptions,
+ extrapolateSeed,
+ deepFreeze,
+} from './useDemoData';
+import { GridColDefGenerator } from '../services/gridColDefGenerator';
+import { getRealGridData, GridDemoData } from '../services/real-data-service';
+import { addTreeDataOptionsToDemoData } from '../services/tree-data-generator';
+import {
+ loadServerRows,
+ processTreeDataRows,
+ DEFAULT_DATASET_OPTIONS,
+ DEFAULT_SERVER_OPTIONS,
+} from './serverUtils';
+import type { ServerOptions } from './serverUtils';
+import { randomInt } from '../services';
+
+const dataCache = new LRUCache({
+ max: 10,
+ ttl: 60 * 5 * 1e3, // 5 minutes
+});
+
+export const BASE_URL = 'https://mui.com/x/api/data-grid';
+
+type UseMockServerResponse = {
+ columns: GridColDef[];
+ initialState: GridInitialState;
+ getGroupKey?: (row: GridRowModel) => string;
+ getChildrenCount?: (row: GridRowModel) => number;
+ fetchRows: (url: string) => Promise;
+ loadNewData: () => void;
+};
+
+function decodeParams(url: string): GridGetRowsParams {
+ const params = new URL(url).searchParams;
+ const decodedParams = {} as any;
+ const array = Array.from(params.entries());
+
+ for (const [key, value] of array) {
+ try {
+ decodedParams[key] = JSON.parse(decodeURIComponent(value));
+ } catch (e) {
+ decodedParams[key] = value;
+ }
+ }
+
+ return decodedParams as GridGetRowsParams;
+}
+
+const getInitialState = (columns: GridColDefGenerator[], groupingField?: string) => {
+ const columnVisibilityModel: GridColumnVisibilityModel = {};
+ columns.forEach((col) => {
+ if (col.hide) {
+ columnVisibilityModel[col.field] = false;
+ }
+ });
+
+ if (groupingField) {
+ columnVisibilityModel![groupingField] = false;
+ }
+
+ return { columns: { columnVisibilityModel } };
+};
+
+const defaultColDef = getGridDefaultColumnTypes();
+
+export const useMockServer = (
+ dataSetOptions?: Partial,
+ serverOptions?: ServerOptions & { verbose?: boolean },
+ shouldRequestsFail?: boolean,
+): UseMockServerResponse => {
+ const [data, setData] = React.useState();
+ const [index, setIndex] = React.useState(0);
+ const shouldRequestsFailRef = React.useRef(shouldRequestsFail ?? false);
+
+ React.useEffect(() => {
+ if (shouldRequestsFail !== undefined) {
+ shouldRequestsFailRef.current = shouldRequestsFail;
+ }
+ }, [shouldRequestsFail]);
+
+ const options = { ...DEFAULT_DATASET_OPTIONS, ...dataSetOptions };
+
+ const columns = React.useMemo(() => {
+ return getColumnsFromOptions({
+ dataSet: options.dataSet,
+ editable: options.editable,
+ maxColumns: options.maxColumns,
+ visibleFields: options.visibleFields,
+ });
+ }, [options.dataSet, options.editable, options.maxColumns, options.visibleFields]);
+
+ const initialState = React.useMemo(
+ () => getInitialState(columns, options.treeData?.groupingField),
+ [columns, options.treeData?.groupingField],
+ );
+
+ const columnsWithDefaultColDef: GridColDef[] = React.useMemo(
+ () =>
+ columns.map((column) => ({
+ ...defaultColDef[column.type || 'string'],
+ ...column,
+ })),
+ [columns],
+ );
+
+ const isTreeData = options.treeData?.groupingField != null;
+
+ const getGroupKey = React.useMemo(() => {
+ if (isTreeData) {
+ return (row: GridRowModel): string => row[options.treeData!.groupingField!];
+ }
+ return undefined;
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [options.treeData?.groupingField, isTreeData]);
+
+ const getChildrenCount = React.useMemo(() => {
+ if (isTreeData) {
+ return (row: GridRowModel): number => row.descendantCount;
+ }
+ return undefined;
+ }, [isTreeData]);
+
+ React.useEffect(() => {
+ const cacheKey = `${options.dataSet}-${options.rowLength}-${index}-${options.maxColumns}`;
+
+ // Cache to allow fast switch between the JavaScript and TypeScript version
+ // of the demos.
+ if (dataCache.has(cacheKey)) {
+ const newData = dataCache.get(cacheKey)!;
+ setData(newData);
+ return undefined;
+ }
+
+ let active = true;
+
+ (async () => {
+ let rowData;
+ const rowLength = options.rowLength;
+ if (rowLength > 1000) {
+ rowData = await getRealGridData(1000, columns);
+ rowData = await extrapolateSeed(rowLength, rowData);
+ } else {
+ rowData = await getRealGridData(rowLength, columns);
+ }
+
+ if (!active) {
+ return;
+ }
+
+ if (isTreeData) {
+ rowData = addTreeDataOptionsToDemoData(rowData, {
+ maxDepth: options.treeData?.maxDepth,
+ groupingField: options.treeData?.groupingField,
+ averageChildren: options.treeData?.averageChildren,
+ });
+ }
+
+ if (process.env.NODE_ENV !== 'production') {
+ deepFreeze(rowData);
+ }
+
+ dataCache.set(cacheKey, rowData);
+ setData(rowData);
+ })();
+
+ return () => {
+ active = false;
+ };
+ }, [
+ columns,
+ isTreeData,
+ options.rowLength,
+ options.treeData?.maxDepth,
+ options.treeData?.groupingField,
+ options.treeData?.averageChildren,
+ options.dataSet,
+ options.maxColumns,
+ index,
+ ]);
+
+ const fetchRows = React.useCallback(
+ async (requestUrl: string): Promise => {
+ if (!data || !requestUrl) {
+ return new Promise((resolve) => {
+ resolve({ rows: [], rowCount: 0 });
+ });
+ }
+ const params = decodeParams(requestUrl);
+ const verbose = serverOptions?.verbose ?? true;
+ // eslint-disable-next-line no-console
+ const print = console.info;
+ if (verbose) {
+ print('MUI X: DATASOURCE REQUEST', params);
+ }
+ let getRowsResponse: GridGetRowsResponse;
+ const serverOptionsWithDefault = {
+ minDelay: serverOptions?.minDelay ?? DEFAULT_SERVER_OPTIONS.minDelay,
+ maxDelay: serverOptions?.maxDelay ?? DEFAULT_SERVER_OPTIONS.maxDelay,
+ useCursorPagination:
+ serverOptions?.useCursorPagination ?? DEFAULT_SERVER_OPTIONS.useCursorPagination,
+ };
+
+ if (shouldRequestsFailRef.current) {
+ const { minDelay, maxDelay } = serverOptionsWithDefault;
+ const delay = randomInt(minDelay, maxDelay);
+ return new Promise((_, reject) => {
+ if (verbose) {
+ print('MUI X: DATASOURCE REQUEST FAILURE', params);
+ }
+ setTimeout(() => reject(new Error('Could not fetch the data')), delay);
+ });
+ }
+
+ if (isTreeData /* || TODO: `isRowGrouping` */) {
+ const { rows, rootRowCount } = await processTreeDataRows(
+ data.rows,
+ params,
+ serverOptionsWithDefault,
+ columnsWithDefaultColDef,
+ );
+
+ getRowsResponse = {
+ rows: rows.slice().map((row) => ({ ...row, path: undefined })),
+ rowCount: rootRowCount,
+ };
+ } else {
+ // plain data
+ const { returnedRows, nextCursor, totalRowCount } = await loadServerRows(
+ data.rows,
+ { ...params, ...params.paginationModel },
+ serverOptionsWithDefault,
+ columnsWithDefaultColDef,
+ );
+ getRowsResponse = { rows: returnedRows, rowCount: totalRowCount, pageInfo: { nextCursor } };
+ }
+
+ return new Promise((resolve) => {
+ if (verbose) {
+ print('MUI X: DATASOURCE RESPONSE', params, getRowsResponse);
+ }
+ resolve(getRowsResponse);
+ });
+ },
+ [
+ data,
+ serverOptions?.verbose,
+ serverOptions?.minDelay,
+ serverOptions?.maxDelay,
+ serverOptions?.useCursorPagination,
+ isTreeData,
+ columnsWithDefaultColDef,
+ ],
+ );
+
+ return {
+ columns: columnsWithDefaultColDef,
+ initialState,
+ getGroupKey,
+ getChildrenCount,
+ fetchRows,
+ loadNewData: () => {
+ setIndex((oldIndex) => oldIndex + 1);
+ },
+ };
+};
diff --git a/packages/x-data-grid-generator/src/hooks/useQuery.ts b/packages/x-data-grid-generator/src/hooks/useQuery.ts
index 55adae6cb6a7..62ae140bcdcd 100644
--- a/packages/x-data-grid-generator/src/hooks/useQuery.ts
+++ b/packages/x-data-grid-generator/src/hooks/useQuery.ts
@@ -1,14 +1,5 @@
import * as React from 'react';
-import {
- getGridDefaultColumnTypes,
- GridRowModel,
- GridFilterModel,
- GridSortModel,
- GridRowId,
- GridLogicOperator,
- GridFilterOperator,
- GridColDef,
-} from '@mui/x-data-grid-pro';
+import { getGridDefaultColumnTypes, GridRowModel } from '@mui/x-data-grid-pro';
import { isDeepEqual } from '@mui/x-data-grid/internals';
import {
useDemoData,
@@ -16,198 +7,8 @@ import {
getColumnsFromOptions,
getInitialState,
} from './useDemoData';
-import { randomInt } from '../services/random-generator';
-
-const apiRef = {} as any;
-
-const simplifiedValueGetter = (field: string, colDef: GridColDef) => (row: GridRowModel) => {
- return colDef.valueGetter?.(row[row.id] as never, row, colDef, apiRef) || row[field];
-};
-
-const getRowComparator = (
- sortModel: GridSortModel | undefined,
- columnsWithDefaultColDef: GridColDef[],
-) => {
- if (!sortModel) {
- const comparator = () => 0;
- return comparator;
- }
- const sortOperators = sortModel.map((sortItem) => {
- const columnField = sortItem.field;
- const colDef = columnsWithDefaultColDef.find(({ field }) => field === columnField) as any;
- return {
- ...sortItem,
- valueGetter: simplifiedValueGetter(columnField, colDef),
- sortComparator: colDef.sortComparator,
- };
- });
-
- const comparator = (row1: GridRowModel, row2: GridRowModel) =>
- sortOperators.reduce((acc, { valueGetter, sort, sortComparator }) => {
- if (acc !== 0) {
- return acc;
- }
- const v1 = valueGetter(row1);
- const v2 = valueGetter(row2);
- return sort === 'desc' ? -1 * sortComparator(v1, v2) : sortComparator(v1, v2);
- }, 0);
-
- return comparator;
-};
-
-const getFilteredRows = (
- rows: GridRowModel[],
- filterModel: GridFilterModel | undefined,
- columnsWithDefaultColDef: GridColDef[],
-) => {
- if (filterModel === undefined || filterModel.items.length === 0) {
- return rows;
- }
-
- const valueGetters = filterModel.items.map(({ field }) =>
- simplifiedValueGetter(
- field,
- columnsWithDefaultColDef.find((column) => column.field === field) as any,
- ),
- );
- const filterFunctions = filterModel.items.map((filterItem) => {
- const { field, operator } = filterItem;
- const colDef = columnsWithDefaultColDef.find((column) => column.field === field) as any;
-
- const filterOperator: any = colDef.filterOperators.find(
- ({ value }: GridFilterOperator) => operator === value,
- );
-
- let parsedValue = filterItem.value;
- if (colDef.valueParser) {
- const parser = colDef.valueParser;
- parsedValue = Array.isArray(filterItem.value)
- ? filterItem.value?.map((x) => parser(x))
- : parser(filterItem.value);
- }
-
- return filterOperator?.getApplyFilterFn({ filterItem, value: parsedValue }, colDef);
- });
-
- if (filterModel.logicOperator === GridLogicOperator.Or) {
- return rows.filter((row: GridRowModel) =>
- filterModel.items.some((_, index) => {
- const value = valueGetters[index](row);
- return filterFunctions[index] === null ? true : filterFunctions[index]({ value });
- }),
- );
- }
- return rows.filter((row: GridRowModel) =>
- filterModel.items.every((_, index) => {
- const value = valueGetters[index](row);
- return filterFunctions[index] === null ? true : filterFunctions[index](value);
- }),
- );
-};
-
-/**
- * Simulates server data loading
- */
-export const loadServerRows = (
- rows: GridRowModel[],
- queryOptions: QueryOptions,
- serverOptions: ServerOptions,
- columnsWithDefaultColDef: GridColDef[],
-): Promise => {
- const { minDelay = 100, maxDelay = 300, useCursorPagination } = serverOptions;
-
- if (maxDelay < minDelay) {
- throw new Error('serverOptions.minDelay is larger than serverOptions.maxDelay ');
- }
- const delay = randomInt(minDelay, maxDelay);
-
- const { cursor, page = 0, pageSize } = queryOptions;
-
- let nextCursor;
- let firstRowIndex;
- let lastRowIndex;
-
- let filteredRows = getFilteredRows(rows, queryOptions.filterModel, columnsWithDefaultColDef);
-
- const rowComparator = getRowComparator(queryOptions.sortModel, columnsWithDefaultColDef);
- filteredRows = [...filteredRows].sort(rowComparator);
-
- const totalRowCount = filteredRows.length;
- if (!pageSize) {
- firstRowIndex = 0;
- lastRowIndex = filteredRows.length;
- } else if (useCursorPagination) {
- firstRowIndex = cursor ? filteredRows.findIndex(({ id }) => id === cursor) : 0;
- firstRowIndex = Math.max(firstRowIndex, 0); // if cursor not found return 0
- lastRowIndex = firstRowIndex + pageSize;
-
- nextCursor = lastRowIndex >= filteredRows.length ? undefined : filteredRows[lastRowIndex].id;
- } else {
- firstRowIndex = page * pageSize;
- lastRowIndex = (page + 1) * pageSize;
- }
- const hasNextPage = lastRowIndex < filteredRows.length - 1;
- const response: FakeServerResponse = {
- returnedRows: filteredRows.slice(firstRowIndex, lastRowIndex),
- nextCursor,
- hasNextPage,
- totalRowCount,
- };
-
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve(response);
- }, delay); // simulate network latency
- });
-};
-
-interface FakeServerResponse {
- returnedRows: GridRowModel[];
- nextCursor?: string;
- hasNextPage: boolean;
- totalRowCount: number;
-}
-
-interface PageInfo {
- totalRowCount?: number;
- nextCursor?: string;
- hasNextPage?: boolean;
- pageSize?: number;
-}
-
-interface DefaultServerOptions {
- minDelay: number;
- maxDelay: number;
- useCursorPagination?: boolean;
-}
-
-type ServerOptions = Partial;
-
-export interface QueryOptions {
- cursor?: GridRowId;
- page?: number;
- pageSize?: number;
- // TODO: implement the behavior liked to following models
- filterModel?: GridFilterModel;
- sortModel?: GridSortModel;
- firstRowToRender?: number;
- lastRowToRender?: number;
-}
-
-const DEFAULT_DATASET_OPTIONS: UseDemoDataOptions = {
- dataSet: 'Commodity',
- rowLength: 100,
- maxColumns: 6,
-};
-
-declare const DISABLE_CHANCE_RANDOM: any;
-const disableDelay = typeof DISABLE_CHANCE_RANDOM !== 'undefined' && DISABLE_CHANCE_RANDOM;
-
-const DEFAULT_SERVER_OPTIONS: DefaultServerOptions = {
- minDelay: disableDelay ? 0 : 100,
- maxDelay: disableDelay ? 0 : 300,
- useCursorPagination: true,
-};
+import { DEFAULT_DATASET_OPTIONS, DEFAULT_SERVER_OPTIONS, loadServerRows } from './serverUtils';
+import type { ServerOptions, QueryOptions, PageInfo } from './serverUtils';
export const createFakeServer = (
dataSetOptions?: Partial,
diff --git a/packages/x-data-grid-premium/package.json b/packages/x-data-grid-premium/package.json
index 732d7f83e502..1f8caf9f7e98 100644
--- a/packages/x-data-grid-premium/package.json
+++ b/packages/x-data-grid-premium/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/x-data-grid-premium",
- "version": "7.7.1",
+ "version": "7.9.0",
"description": "The Premium plan edition of the Data Grid Components (MUI X).",
"author": "MUI Team",
"main": "src/index.ts",
@@ -44,10 +44,11 @@
},
"dependencies": {
"@babel/runtime": "^7.24.7",
- "@mui/system": "^5.15.20",
- "@mui/utils": "^5.15.20",
+ "@mui/system": "^5.16.0",
+ "@mui/utils": "^5.16.0",
"@mui/x-data-grid": "workspace:*",
"@mui/x-data-grid-pro": "workspace:*",
+ "@mui/x-internals": "workspace:*",
"@mui/x-license": "workspace:*",
"@types/format-util": "^1.0.4",
"clsx": "^2.1.1",
@@ -61,10 +62,10 @@
"react-dom": "^17.0.0 || ^18.0.0"
},
"devDependencies": {
- "@mui/internal-test-utils": "^1.0.1",
+ "@mui/internal-test-utils": "^1.0.4",
"@types/prop-types": "^15.7.12",
"date-fns": "^2.30.0",
- "rimraf": "^5.0.7"
+ "rimraf": "^5.0.8"
},
"engines": {
"node": ">=14.0.0"
diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx
index c07ee1336733..13c08a00b7e1 100644
--- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx
+++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx
@@ -64,21 +64,6 @@ const DataGridPremiumRaw = React.forwardRef(function DataGridPremium
);
});
-interface DataGridPremiumComponent {
- (
- props: DataGridPremiumProps & React.RefAttributes,
- ): React.JSX.Element;
- propTypes?: any;
-}
-
-/**
- * Demos:
- * - [DataGridPremium](https://mui.com/x/react-data-grid/demo/)
- *
- * API:
- * - [DataGridPremium API](https://mui.com/x/api/data-grid/data-grid-premium/)
- */
-export const DataGridPremium = React.memo(DataGridPremiumRaw) as DataGridPremiumComponent;
DataGridPremiumRaw.propTypes = {
// ----------------------------- Warning --------------------------------
@@ -1060,4 +1045,32 @@ DataGridPremiumRaw.propTypes = {
* @default false
*/
treeData: PropTypes.bool,
+ unstable_dataSource: PropTypes.shape({
+ getChildrenCount: PropTypes.func,
+ getGroupKey: PropTypes.func,
+ getRows: PropTypes.func.isRequired,
+ updateRow: PropTypes.func,
+ }),
+ unstable_dataSourceCache: PropTypes.shape({
+ clear: PropTypes.func.isRequired,
+ get: PropTypes.func.isRequired,
+ set: PropTypes.func.isRequired,
+ }),
+ unstable_onDataSourceError: PropTypes.func,
} as any;
+
+interface DataGridPremiumComponent {
+ (
+ props: DataGridPremiumProps & React.RefAttributes,
+ ): React.JSX.Element;
+ propTypes?: any;
+}
+
+/**
+ * Demos:
+ * - [DataGridPremium](https://mui.com/x/react-data-grid/demo/)
+ *
+ * API:
+ * - [DataGridPremium API](https://mui.com/x/api/data-grid/data-grid-premium/)
+ */
+export const DataGridPremium = React.memo(DataGridPremiumRaw) as DataGridPremiumComponent;
diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx
index c1d5bf49973a..b61e63a1f927 100644
--- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx
+++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx
@@ -65,6 +65,9 @@ import {
useGridHeaderFiltering,
virtualizationStateInitializer,
useGridVirtualization,
+ useGridDataSourceTreeDataPreProcessors,
+ useGridDataSource,
+ dataSourceStateInitializer,
} from '@mui/x-data-grid-pro/internals';
import { GridApiPremium, GridPrivateApiPremium } from '../models/gridApiPremium';
import { DataGridPremiumProcessedProps } from '../models/dataGridPremiumProps';
@@ -99,6 +102,7 @@ export const useDataGridPremiumComponent = (
useGridRowReorderPreProcessors(apiRef, props);
useGridRowGroupingPreProcessors(apiRef, props);
useGridTreeDataPreProcessors(apiRef, props);
+ useGridDataSourceTreeDataPreProcessors(apiRef, props);
useGridLazyLoaderPreProcessors(apiRef, props);
useGridRowPinningPreProcessors(apiRef);
useGridAggregationPreProcessors(apiRef, props);
@@ -135,10 +139,11 @@ export const useDataGridPremiumComponent = (
useGridInitializeState(columnMenuStateInitializer, apiRef, props);
useGridInitializeState(columnGroupsStateInitializer, apiRef, props);
useGridInitializeState(virtualizationStateInitializer, apiRef, props);
+ useGridInitializeState(dataSourceStateInitializer, apiRef, props);
useGridRowGrouping(apiRef, props);
useGridHeaderFiltering(apiRef, props);
- useGridTreeData(apiRef);
+ useGridTreeData(apiRef, props);
useGridAggregation(apiRef, props);
useGridKeyboardNavigation(apiRef, props);
useGridRowSelection(apiRef, props);
@@ -174,6 +179,7 @@ export const useDataGridPremiumComponent = (
useGridDimensions(apiRef, props);
useGridEvents(apiRef, props);
useGridStatePersistence(apiRef);
+ useGridDataSource(apiRef, props);
useGridVirtualization(apiRef, props);
return apiRef;
diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts
index c77bc51f8c99..0d50f1b0a819 100644
--- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts
+++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts
@@ -1,6 +1,10 @@
import * as React from 'react';
import { useThemeProps } from '@mui/material/styles';
-import { DATA_GRID_PRO_PROPS_DEFAULT_VALUES, GRID_DEFAULT_LOCALE_TEXT } from '@mui/x-data-grid-pro';
+import {
+ DATA_GRID_PRO_PROPS_DEFAULT_VALUES,
+ GRID_DEFAULT_LOCALE_TEXT,
+ DataGridProProps,
+} from '@mui/x-data-grid-pro';
import { computeSlots, useProps } from '@mui/x-data-grid-pro/internals';
import {
DataGridPremiumProps,
@@ -11,6 +15,26 @@ import { GridPremiumSlotsComponent } from '../models';
import { GRID_AGGREGATION_FUNCTIONS } from '../hooks/features/aggregation';
import { DATA_GRID_PREMIUM_DEFAULT_SLOTS_COMPONENTS } from '../constants/dataGridPremiumDefaultSlotsComponents';
+interface GetDataGridPremiumPropsDefaultValues extends DataGridPremiumProps {}
+
+type DataGridProForcedProps = {
+ [key in keyof DataGridProProps]?: DataGridPremiumProcessedProps[key];
+};
+type GetDataGridProForcedProps = (
+ themedProps: GetDataGridPremiumPropsDefaultValues,
+) => DataGridProForcedProps;
+
+const getDataGridPremiumForcedProps: GetDataGridProForcedProps = (themedProps) => ({
+ signature: 'DataGridPremium',
+ ...(themedProps.unstable_dataSource
+ ? {
+ filterMode: 'server',
+ sortingMode: 'server',
+ paginationMode: 'server',
+ }
+ : {}),
+});
+
/**
* The default values of `DataGridPremiumPropsWithDefaultValue` to inject in the props of DataGridPremium.
*/
@@ -63,7 +87,7 @@ export const useDataGridPremiumProps = (inProps: DataGridPremiumProps) => {
...themedProps,
localeText,
slots,
- signature: 'DataGridPremium',
+ ...getDataGridPremiumForcedProps(themedProps),
}),
[themedProps, localeText, slots],
);
diff --git a/packages/x-data-grid-premium/src/models/gridApiPremium.ts b/packages/x-data-grid-premium/src/models/gridApiPremium.ts
index c9a1c7f911a9..999a16685997 100644
--- a/packages/x-data-grid-premium/src/models/gridApiPremium.ts
+++ b/packages/x-data-grid-premium/src/models/gridApiPremium.ts
@@ -8,6 +8,8 @@ import {
GridRowMultiSelectionApi,
GridColumnReorderApi,
GridRowProApi,
+ GridDataSourceApi,
+ GridDataSourcePrivateApi,
} from '@mui/x-data-grid-pro';
import { GridInitialStatePremium, GridStatePremium } from './gridStatePremium';
import type { GridRowGroupingApi, GridExcelExportApi, GridAggregationApi } from '../hooks';
@@ -27,6 +29,7 @@ export interface GridApiPremium
GridExcelExportApi,
GridAggregationApi,
GridRowPinningApi,
+ GridDataSourceApi,
GridCellSelectionApi,
// APIs that are private in Community plan, but public in Pro and Premium plans
GridRowMultiSelectionApi,
@@ -35,4 +38,5 @@ export interface GridApiPremium
export interface GridPrivateApiPremium
extends GridApiPremium,
GridPrivateOnlyApiCommon,
+ GridDataSourcePrivateApi,
GridDetailPanelPrivateApi {}
diff --git a/packages/x-data-grid-premium/src/tests/clipboard.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/clipboard.DataGridPremium.test.tsx
index 1be69796afdd..22cd738034d8 100644
--- a/packages/x-data-grid-premium/src/tests/clipboard.DataGridPremium.test.tsx
+++ b/packages/x-data-grid-premium/src/tests/clipboard.DataGridPremium.test.tsx
@@ -119,12 +119,14 @@ describe(' - Clipboard', () => {
{ id: 2, brand: 'Puma' },
];
render(
- ,
+
+
+
,
);
const cell = getCell(0, 0);
@@ -143,15 +145,17 @@ describe(' - Clipboard', () => {
it('should not escape double quotes when copying multiple cells to clipboard', () => {
render(
- ,
+
+
+
,
);
const cell = getCell(0, 0);
diff --git a/packages/x-data-grid-premium/src/tests/license.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/license.DataGridPremium.test.tsx
index 1773384ed9a0..9879b1d18260 100644
--- a/packages/x-data-grid-premium/src/tests/license.DataGridPremium.test.tsx
+++ b/packages/x-data-grid-premium/src/tests/license.DataGridPremium.test.tsx
@@ -31,7 +31,7 @@ describe(' - License', () => {
]);
await waitFor(() => {
- expect(screen.getByText('MUI X Missing license key')).to.not.equal(null);
+ expect(screen.getByText('MUI X Missing license key')).not.to.equal(null);
});
});
});
diff --git a/packages/x-data-grid-premium/src/tests/rowGrouping.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/rowGrouping.DataGridPremium.test.tsx
index 69f2268255ea..824d610884a2 100644
--- a/packages/x-data-grid-premium/src/tests/rowGrouping.DataGridPremium.test.tsx
+++ b/packages/x-data-grid-premium/src/tests/rowGrouping.DataGridPremium.test.tsx
@@ -34,6 +34,10 @@ import { spy } from 'sinon';
const isJSDOM = /jsdom/.test(window.navigator.userAgent);
+interface BaselineProps extends DataGridPremiumProps {
+ rows: GridRowsProp;
+}
+
const rows: GridRowsProp = [
{ id: 0, category1: 'Cat A', category2: 'Cat 1' },
{ id: 1, category1: 'Cat A', category2: 'Cat 2' },
@@ -51,7 +55,7 @@ const unbalancedRows: GridRowsProp = [
{ id: 5, category1: null },
];
-const baselineProps: DataGridPremiumProps = {
+const baselineProps: BaselineProps = {
autoHeight: isJSDOM,
disableVirtualization: true,
rows,
diff --git a/packages/x-data-grid-premium/tsconfig.build.json b/packages/x-data-grid-premium/tsconfig.build.json
index 3f1870534100..35dae5af1b78 100644
--- a/packages/x-data-grid-premium/tsconfig.build.json
+++ b/packages/x-data-grid-premium/tsconfig.build.json
@@ -14,7 +14,8 @@
"references": [
{ "path": "../x-data-grid/tsconfig.build.json" },
{ "path": "../x-data-grid-pro/tsconfig.build.json" },
- { "path": "../x-license/tsconfig.build.json" }
+ { "path": "../x-license/tsconfig.build.json" },
+ { "path": "../x-internals/tsconfig.build.json" }
],
"include": ["src/**/*.ts*"],
"exclude": ["src/**/*.spec.ts*", "src/**/*.test.ts*"]
diff --git a/packages/x-data-grid-premium/tsconfig.json b/packages/x-data-grid-premium/tsconfig.json
index 6afc65000e94..3769236bfa90 100644
--- a/packages/x-data-grid-premium/tsconfig.json
+++ b/packages/x-data-grid-premium/tsconfig.json
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
- "types": ["@mui/material/themeCssVarsAugmentation", "chai-dom", "mocha"]
+ "types": ["@mui/material/themeCssVarsAugmentation", "chai-dom", "mocha", "node"]
},
"include": ["src/**/*"]
}
diff --git a/packages/x-data-grid-pro/package.json b/packages/x-data-grid-pro/package.json
index 651df24fa461..86126d041862 100644
--- a/packages/x-data-grid-pro/package.json
+++ b/packages/x-data-grid-pro/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/x-data-grid-pro",
- "version": "7.7.1",
+ "version": "7.9.0",
"description": "The Pro plan edition of the Data Grid components (MUI X).",
"author": "MUI Team",
"main": "src/index.ts",
@@ -44,9 +44,10 @@
},
"dependencies": {
"@babel/runtime": "^7.24.7",
- "@mui/system": "^5.15.20",
- "@mui/utils": "^5.15.20",
+ "@mui/system": "^5.16.0",
+ "@mui/utils": "^5.16.0",
"@mui/x-data-grid": "workspace:*",
+ "@mui/x-internals": "workspace:*",
"@mui/x-license": "workspace:*",
"@types/format-util": "^1.0.4",
"clsx": "^2.1.1",
@@ -59,9 +60,9 @@
"react-dom": "^17.0.0 || ^18.0.0"
},
"devDependencies": {
- "@mui/internal-test-utils": "^1.0.1",
+ "@mui/internal-test-utils": "^1.0.4",
"@types/prop-types": "^15.7.12",
- "rimraf": "^5.0.7"
+ "rimraf": "^5.0.8"
},
"engines": {
"node": ">=14.0.0"
diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx
index 824c3c3d6297..1cacac533b70 100644
--- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx
+++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx
@@ -944,4 +944,16 @@ DataGridProRaw.propTypes = {
* @default false
*/
treeData: PropTypes.bool,
+ unstable_dataSource: PropTypes.shape({
+ getChildrenCount: PropTypes.func,
+ getGroupKey: PropTypes.func,
+ getRows: PropTypes.func.isRequired,
+ updateRow: PropTypes.func,
+ }),
+ unstable_dataSourceCache: PropTypes.shape({
+ clear: PropTypes.func.isRequired,
+ get: PropTypes.func.isRequired,
+ set: PropTypes.func.isRequired,
+ }),
+ unstable_onDataSourceError: PropTypes.func,
} as any;
diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx
index 3357ebbaf7eb..d902aa413bb6 100644
--- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx
+++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx
@@ -58,6 +58,7 @@ import {
} from '../hooks/features/columnReorder/useGridColumnReorder';
import { useGridTreeData } from '../hooks/features/treeData/useGridTreeData';
import { useGridTreeDataPreProcessors } from '../hooks/features/treeData/useGridTreeDataPreProcessors';
+import { useGridDataSourceTreeDataPreProcessors } from '../hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors';
import {
useGridColumnPinning,
columnPinningStateInitializer,
@@ -77,6 +78,10 @@ import {
rowPinningStateInitializer,
} from '../hooks/features/rowPinning/useGridRowPinning';
import { useGridRowPinningPreProcessors } from '../hooks/features/rowPinning/useGridRowPinningPreProcessors';
+import {
+ useGridDataSource,
+ dataSourceStateInitializer,
+} from '../hooks/features/dataSource/useGridDataSource';
export const useDataGridProComponent = (
inputApiRef: React.MutableRefObject | undefined,
@@ -90,6 +95,7 @@ export const useDataGridProComponent = (
useGridRowSelectionPreProcessors(apiRef, props);
useGridRowReorderPreProcessors(apiRef, props);
useGridTreeDataPreProcessors(apiRef, props);
+ useGridDataSourceTreeDataPreProcessors(apiRef, props);
useGridLazyLoaderPreProcessors(apiRef, props);
useGridRowPinningPreProcessors(apiRef);
useGridDetailPanelPreProcessors(apiRef, props);
@@ -122,9 +128,10 @@ export const useDataGridProComponent = (
useGridInitializeState(columnMenuStateInitializer, apiRef, props);
useGridInitializeState(columnGroupsStateInitializer, apiRef, props);
useGridInitializeState(virtualizationStateInitializer, apiRef, props);
+ useGridInitializeState(dataSourceStateInitializer, apiRef, props);
useGridHeaderFiltering(apiRef, props);
- useGridTreeData(apiRef);
+ useGridTreeData(apiRef, props);
useGridKeyboardNavigation(apiRef, props);
useGridRowSelection(apiRef, props);
useGridColumnPinning(apiRef, props);
@@ -157,6 +164,7 @@ export const useDataGridProComponent = (
useGridEvents(apiRef, props);
useGridStatePersistence(apiRef);
useGridVirtualization(apiRef, props);
+ useGridDataSource(apiRef, props);
return apiRef;
};
diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts
index 33791fca9b48..b970dfdd4645 100644
--- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts
+++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts
@@ -14,6 +14,24 @@ import {
import { GridProSlotsComponent } from '../models';
import { DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS } from '../constants/dataGridProDefaultSlotsComponents';
+interface GetDataGridProPropsDefaultValues extends DataGridProProps {}
+
+type DataGridProForcedProps = { [key in keyof DataGridProProps]?: DataGridProProcessedProps[key] };
+type GetDataGridProForcedProps = (
+ themedProps: GetDataGridProPropsDefaultValues,
+) => DataGridProForcedProps;
+
+const getDataGridProForcedProps: GetDataGridProForcedProps = (themedProps) => ({
+ signature: 'DataGridPro',
+ ...(themedProps.unstable_dataSource
+ ? {
+ filterMode: 'server',
+ sortingMode: 'server',
+ paginationMode: 'server',
+ }
+ : {}),
+});
+
/**
* The default values of `DataGridProPropsWithDefaultValue` to inject in the props of DataGridPro.
*/
@@ -65,7 +83,7 @@ export const useDataGridProProps = (inProps: DataGr
...themedProps,
localeText,
slots,
- signature: 'DataGridPro',
+ ...getDataGridProForcedProps(themedProps),
}),
[themedProps, localeText, slots],
);
diff --git a/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx
new file mode 100644
index 000000000000..d03c28e675c4
--- /dev/null
+++ b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx
@@ -0,0 +1,132 @@
+import * as React from 'react';
+import { unstable_composeClasses as composeClasses } from '@mui/utils';
+import Box from '@mui/material/Box';
+import Badge from '@mui/material/Badge';
+import {
+ getDataGridUtilityClass,
+ GridRenderCellParams,
+ GridDataSourceGroupNode,
+ useGridSelector,
+} from '@mui/x-data-grid';
+import CircularProgress from '@mui/material/CircularProgress';
+import { useGridRootProps } from '../hooks/utils/useGridRootProps';
+import { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContext';
+import { DataGridProProcessedProps } from '../models/dataGridProProps';
+import { GridPrivateApiPro } from '../models/gridApiPro';
+import { GridStatePro } from '../models/gridStatePro';
+
+type OwnerState = DataGridProProcessedProps;
+
+const useUtilityClasses = (ownerState: OwnerState) => {
+ const { classes } = ownerState;
+
+ const slots = {
+ root: ['treeDataGroupingCell'],
+ toggle: ['treeDataGroupingCellToggle'],
+ loadingContainer: ['treeDataGroupingCellLoadingContainer'],
+ };
+
+ return composeClasses(slots, getDataGridUtilityClass, classes);
+};
+
+interface GridTreeDataGroupingCellProps
+ extends GridRenderCellParams {
+ hideDescendantCount?: boolean;
+ /**
+ * The cell offset multiplier used for calculating cell offset (`rowNode.depth * offsetMultiplier` px).
+ * @default 2
+ */
+ offsetMultiplier?: number;
+}
+
+interface GridTreeDataGroupingCellIconProps
+ extends Pick {
+ descendantCount: number;
+}
+
+function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) {
+ const apiRef = useGridPrivateApiContext() as React.MutableRefObject;
+ const rootProps = useGridRootProps();
+ const classes = useUtilityClasses(rootProps);
+ const { rowNode, id, field, descendantCount } = props;
+
+ const loadingSelector = (state: GridStatePro) => state.dataSource.loading[id] ?? false;
+ const errorSelector = (state: GridStatePro) => state.dataSource.errors[id];
+ const isDataLoading = useGridSelector(apiRef, loadingSelector);
+ const error = useGridSelector(apiRef, errorSelector);
+
+ const handleClick = (event: React.MouseEvent) => {
+ if (!rowNode.childrenExpanded) {
+ // always fetch/get from cache the children when the node is expanded
+ apiRef.current.unstable_dataSource.fetchRows(id);
+ } else {
+ apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded);
+ }
+ apiRef.current.setCellFocus(id, field);
+ event.stopPropagation(); // TODO remove event.stopPropagation
+ };
+
+ const Icon = rowNode.childrenExpanded
+ ? rootProps.slots.treeDataCollapseIcon
+ : rootProps.slots.treeDataExpandIcon;
+
+ if (isDataLoading) {
+ return (
+
+
+
+ );
+ }
+ return descendantCount > 0 ? (
+
+
+
+
+
+
+
+ ) : null;
+}
+
+export function GridDataSourceTreeDataGroupingCell(props: GridTreeDataGroupingCellProps) {
+ const { id, field, formattedValue, rowNode, hideDescendantCount, offsetMultiplier = 2 } = props;
+
+ const rootProps = useGridRootProps();
+ const apiRef = useGridPrivateApiContext();
+ const rowSelector = (state: GridStatePro) => state.rows.dataRowIdToModelLookup[id];
+ const row = useGridSelector(apiRef, rowSelector);
+ const classes = useUtilityClasses(rootProps);
+
+ let descendantCount = 0;
+ if (row) {
+ descendantCount = Math.max(rootProps.unstable_dataSource?.getChildrenCount?.(row) ?? 0, 0);
+ }
+
+ return (
+
+
+
+
+
+ {formattedValue === undefined ? rowNode.groupingKey : formattedValue}
+ {!hideDescendantCount && descendantCount > 0 ? ` (${descendantCount})` : ''}
+
+
+ );
+}
diff --git a/packages/x-data-grid-pro/src/components/GridDetailPanel.tsx b/packages/x-data-grid-pro/src/components/GridDetailPanel.tsx
index 82577308cb75..a37b3e4bc807 100644
--- a/packages/x-data-grid-pro/src/components/GridDetailPanel.tsx
+++ b/packages/x-data-grid-pro/src/components/GridDetailPanel.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { styled } from '@mui/material/styles';
import { GridRowId } from '@mui/x-data-grid';
-import { useResizeObserver } from '@mui/x-data-grid/internals';
+import { useResizeObserver } from '@mui/x-internals/useResizeObserver';
import { useGridRootProps } from '../hooks/utils/useGridRootProps';
import { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContext';
import { DataGridProProcessedProps } from '../models/dataGridProProps';
diff --git a/packages/x-data-grid-pro/src/components/GridTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridTreeDataGroupingCell.tsx
index 8e1e85688d89..673b637ee038 100644
--- a/packages/x-data-grid-pro/src/components/GridTreeDataGroupingCell.tsx
+++ b/packages/x-data-grid-pro/src/components/GridTreeDataGroupingCell.tsx
@@ -13,7 +13,7 @@ import { useGridRootProps } from '../hooks/utils/useGridRootProps';
import { useGridApiContext } from '../hooks/utils/useGridApiContext';
import { DataGridProProcessedProps } from '../models/dataGridProProps';
-type OwnerState = { classes: DataGridProProcessedProps['classes'] };
+type OwnerState = DataGridProProcessedProps;
const useUtilityClasses = (ownerState: OwnerState) => {
const { classes } = ownerState;
@@ -40,8 +40,7 @@ function GridTreeDataGroupingCell(props: GridTreeDataGroupingCellProps) {
const rootProps = useGridRootProps();
const apiRef = useGridApiContext();
- const ownerState: OwnerState = { classes: rootProps.classes };
- const classes = useUtilityClasses(ownerState);
+ const classes = useUtilityClasses(rootProps);
const filteredDescendantCountLookup = useGridSelector(
apiRef,
gridFilteredDescendantCountLookupSelector,
diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts
new file mode 100644
index 000000000000..dde8cad3d39f
--- /dev/null
+++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts
@@ -0,0 +1,53 @@
+import { GridGetRowsParams, GridGetRowsResponse } from '../../../models';
+
+type GridDataSourceCacheDefaultConfig = {
+ /**
+ * Time To Live for each cache entry in milliseconds.
+ * After this time the cache entry will become stale and the next query will result in cache miss.
+ * @default 300000 (5 minutes)
+ */
+ ttl?: number;
+};
+
+function getKey(params: GridGetRowsParams) {
+ return JSON.stringify([
+ params.paginationModel,
+ params.filterModel,
+ params.sortModel,
+ params.groupKeys,
+ ]);
+}
+
+export class GridDataSourceCacheDefault {
+ private cache: Record;
+
+ private ttl: number;
+
+ constructor({ ttl = 300000 }: GridDataSourceCacheDefaultConfig) {
+ this.cache = {};
+ this.ttl = ttl;
+ }
+
+ set(key: GridGetRowsParams, value: GridGetRowsResponse) {
+ const keyString = getKey(key);
+ const expiry = Date.now() + this.ttl;
+ this.cache[keyString] = { value, expiry };
+ }
+
+ get(key: GridGetRowsParams): GridGetRowsResponse | undefined {
+ const keyString = getKey(key);
+ const entry = this.cache[keyString];
+ if (!entry) {
+ return undefined;
+ }
+ if (Date.now() > entry.expiry) {
+ delete this.cache[keyString];
+ return undefined;
+ }
+ return entry.value;
+ }
+
+ clear() {
+ this.cache = {};
+ }
+}
diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts
new file mode 100644
index 000000000000..a7bee9eec1d9
--- /dev/null
+++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts
@@ -0,0 +1,43 @@
+import {
+ GridPaginationModel,
+ gridFilterModelSelector,
+ gridSortModelSelector,
+ gridPaginationModelSelector,
+} from '@mui/x-data-grid';
+import { createSelector } from '@mui/x-data-grid/internals';
+import { GridStatePro } from '../../../models/gridStatePro';
+
+const computeStartEnd = (paginationModel: GridPaginationModel) => {
+ const start = paginationModel.page * paginationModel.pageSize;
+ const end = start + paginationModel.pageSize - 1;
+ return { start, end };
+};
+
+export const gridGetRowsParamsSelector = createSelector(
+ gridFilterModelSelector,
+ gridSortModelSelector,
+ gridPaginationModelSelector,
+ (filterModel, sortModel, paginationModel) => {
+ return {
+ groupKeys: [],
+ // TODO: Implement with `rowGrouping`
+ groupFields: [],
+ paginationModel,
+ sortModel,
+ filterModel,
+ ...computeStartEnd(paginationModel),
+ };
+ },
+);
+
+export const gridDataSourceStateSelector = (state: GridStatePro) => state.dataSource;
+
+export const gridDataSourceLoadingSelector = createSelector(
+ gridDataSourceStateSelector,
+ (dataSource) => dataSource.loading,
+);
+
+export const gridDataSourceErrorsSelector = createSelector(
+ gridDataSourceStateSelector,
+ (dataSource) => dataSource.errors,
+);
diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts
new file mode 100644
index 000000000000..90bfc4ed39de
--- /dev/null
+++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/interfaces.ts
@@ -0,0 +1,53 @@
+import { GridRowId } from '@mui/x-data-grid';
+import { GridDataSourceCache } from '../../../models';
+
+export interface GridDataSourceState {
+ loading: Record;
+ errors: Record;
+}
+
+/**
+ * The base data source API interface that is available in the grid [[apiRef]].
+ */
+export interface GridDataSourceApiBase {
+ /**
+ * Set the loading state of a parent row.
+ * @param {string} parentId The id of the parent node.
+ * @param {boolean} loading The loading state to set.
+ */
+ setChildrenLoading: (parentId: GridRowId, loading: boolean) => void;
+ /**
+ * Set error occured while fetching the children of a row.
+ * @param {string} parentId The id of the parent node.
+ * @param {Error} error The error of type `Error` or `null`.
+ */
+ setChildrenFetchError: (parentId: GridRowId, error: Error | null) => void;
+ /**
+ * Fetches the rows from the server for a given `parentId`.
+ * If no `parentId` is provided, it fetches the root rows.
+ * @param {string} parentId The id of the group to be fetched.
+ */
+ fetchRows: (parentId?: GridRowId) => void;
+ /**
+ * The data source cache object.
+ */
+ cache: GridDataSourceCache;
+}
+
+export interface GridDataSourceApi {
+ /**
+ * The data source API.
+ */
+ unstable_dataSource: GridDataSourceApiBase;
+}
+export interface GridDataSourcePrivateApi {
+ /**
+ * Initiates the fetch of the children of a row.
+ * @param {string} id The id of the group to be fetched.
+ */
+ fetchRowChildren: (id: GridRowId) => void;
+ /**
+ * Resets the data source state.
+ */
+ resetDataSourceState: () => void;
+}
diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts
new file mode 100644
index 000000000000..b948c0a48745
--- /dev/null
+++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts
@@ -0,0 +1,299 @@
+import * as React from 'react';
+import useLazyRef from '@mui/utils/useLazyRef';
+import {
+ useGridApiEventHandler,
+ gridRowsLoadingSelector,
+ useGridApiMethod,
+ GridDataSourceGroupNode,
+ useGridSelector,
+ GridRowId,
+} from '@mui/x-data-grid';
+import { gridRowGroupsToFetchSelector, GridStateInitializer } from '@mui/x-data-grid/internals';
+import { GridPrivateApiPro } from '../../../models/gridApiPro';
+import { DataGridProProcessedProps } from '../../../models/dataGridProProps';
+import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector';
+import { GridDataSourceApi, GridDataSourceApiBase, GridDataSourcePrivateApi } from './interfaces';
+import { runIfServerMode, NestedDataManager, RequestStatus } from './utils';
+import { GridDataSourceCache } from '../../../models';
+import { GridDataSourceCacheDefault } from './cache';
+
+const INITIAL_STATE = {
+ loading: {},
+ errors: {},
+};
+
+const noopCache: GridDataSourceCache = {
+ clear: () => {},
+ get: () => undefined,
+ set: () => {},
+};
+
+function getCache(cacheProp?: GridDataSourceCache | null) {
+ if (cacheProp === null) {
+ return noopCache;
+ }
+ return cacheProp ?? new GridDataSourceCacheDefault({});
+}
+
+export const dataSourceStateInitializer: GridStateInitializer = (state) => {
+ return {
+ ...state,
+ dataSource: INITIAL_STATE,
+ };
+};
+
+export const useGridDataSource = (
+ apiRef: React.MutableRefObject,
+ props: Pick<
+ DataGridProProcessedProps,
+ | 'unstable_dataSource'
+ | 'unstable_dataSourceCache'
+ | 'unstable_onDataSourceError'
+ | 'sortingMode'
+ | 'filterMode'
+ | 'paginationMode'
+ | 'treeData'
+ >,
+) => {
+ const nestedDataManager = useLazyRef(
+ () => new NestedDataManager(apiRef),
+ ).current;
+ const groupsToAutoFetch = useGridSelector(apiRef, gridRowGroupsToFetchSelector);
+ const scheduledGroups = React.useRef(0);
+ const onError = props.unstable_onDataSourceError;
+
+ const [cache, setCache] = React.useState(() =>
+ getCache(props.unstable_dataSourceCache),
+ );
+
+ const fetchRows = React.useCallback(
+ async (parentId?: GridRowId) => {
+ const getRows = props.unstable_dataSource?.getRows;
+ if (!getRows) {
+ return;
+ }
+
+ if (parentId) {
+ nestedDataManager.queue([parentId]);
+ return;
+ }
+
+ nestedDataManager.clear();
+ scheduledGroups.current = 0;
+ const dataSourceState = apiRef.current.state.dataSource;
+ if (dataSourceState !== INITIAL_STATE) {
+ apiRef.current.resetDataSourceState();
+ }
+
+ const fetchParams = gridGetRowsParamsSelector(apiRef);
+
+ const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams);
+
+ if (cachedData !== undefined) {
+ const rows = cachedData.rows;
+ apiRef.current.setRows(rows);
+ if (cachedData.rowCount) {
+ apiRef.current.setRowCount(cachedData.rowCount);
+ }
+ return;
+ }
+
+ const isLoading = gridRowsLoadingSelector(apiRef);
+ if (!isLoading) {
+ apiRef.current.setLoading(true);
+ }
+
+ try {
+ const getRowsResponse = await getRows(fetchParams);
+ apiRef.current.unstable_dataSource.cache.set(fetchParams, getRowsResponse);
+ if (getRowsResponse.rowCount) {
+ apiRef.current.setRowCount(getRowsResponse.rowCount);
+ }
+ apiRef.current.setRows(getRowsResponse.rows);
+ apiRef.current.setLoading(false);
+ } catch (error) {
+ apiRef.current.setRows([]);
+ apiRef.current.setLoading(false);
+ onError?.(error as Error, fetchParams);
+ }
+ },
+ [nestedDataManager, apiRef, props.unstable_dataSource?.getRows, onError],
+ );
+
+ const fetchRowChildren = React.useCallback(
+ async (id) => {
+ if (!props.treeData) {
+ nestedDataManager.clearPendingRequest(id);
+ return;
+ }
+ const getRows = props.unstable_dataSource?.getRows;
+ if (!getRows) {
+ nestedDataManager.clearPendingRequest(id);
+ return;
+ }
+
+ const rowNode = apiRef.current.getRowNode(id);
+ if (!rowNode) {
+ nestedDataManager.clearPendingRequest(id);
+ return;
+ }
+
+ const fetchParams = { ...gridGetRowsParamsSelector(apiRef), groupKeys: rowNode.path };
+
+ const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams);
+
+ if (cachedData !== undefined) {
+ const rows = cachedData.rows;
+ nestedDataManager.setRequestSettled(id);
+ apiRef.current.updateServerRows(rows, rowNode.path);
+ if (cachedData.rowCount) {
+ apiRef.current.setRowCount(cachedData.rowCount);
+ }
+ apiRef.current.setRowChildrenExpansion(id, true);
+ apiRef.current.unstable_dataSource.setChildrenLoading(id, false);
+ return;
+ }
+
+ const existingError = gridDataSourceErrorsSelector(apiRef)[id] ?? null;
+ if (existingError) {
+ apiRef.current.unstable_dataSource.setChildrenFetchError(id, null);
+ }
+
+ try {
+ const getRowsResponse = await getRows(fetchParams);
+ if (!apiRef.current.getRowNode(id)) {
+ // The row has been removed from the grid
+ nestedDataManager.clearPendingRequest(id);
+ return;
+ }
+ if (nestedDataManager.getRequestStatus(id) === RequestStatus.UNKNOWN) {
+ apiRef.current.unstable_dataSource.setChildrenLoading(id, false);
+ return;
+ }
+ nestedDataManager.setRequestSettled(id);
+ apiRef.current.unstable_dataSource.cache.set(fetchParams, getRowsResponse);
+ if (getRowsResponse.rowCount) {
+ apiRef.current.setRowCount(getRowsResponse.rowCount);
+ }
+ apiRef.current.updateServerRows(getRowsResponse.rows, rowNode.path);
+ apiRef.current.setRowChildrenExpansion(id, true);
+ } catch (error) {
+ const e = error as Error;
+ apiRef.current.unstable_dataSource.setChildrenFetchError(id, e);
+ onError?.(e, fetchParams);
+ } finally {
+ apiRef.current.unstable_dataSource.setChildrenLoading(id, false);
+ nestedDataManager.setRequestSettled(id);
+ }
+ },
+ [nestedDataManager, onError, apiRef, props.treeData, props.unstable_dataSource?.getRows],
+ );
+
+ const setChildrenLoading = React.useCallback(
+ (parentId, isLoading) => {
+ apiRef.current.setState((state) => {
+ if (!state.dataSource.loading[parentId] && isLoading === false) {
+ return state;
+ }
+ const newLoadingState = { ...state.dataSource.loading };
+ if (isLoading === false) {
+ delete newLoadingState[parentId];
+ } else {
+ newLoadingState[parentId] = isLoading;
+ }
+ return {
+ ...state,
+ dataSource: {
+ ...state.dataSource,
+ loading: newLoadingState,
+ },
+ };
+ });
+ },
+ [apiRef],
+ );
+
+ const setChildrenFetchError = React.useCallback(
+ (parentId, error) => {
+ apiRef.current.setState((state) => {
+ const newErrorsState = { ...state.dataSource.errors };
+ if (error === null && newErrorsState[parentId] !== undefined) {
+ delete newErrorsState[parentId];
+ } else {
+ newErrorsState[parentId] = error;
+ }
+ return {
+ ...state,
+ dataSource: {
+ ...state.dataSource,
+ errors: newErrorsState,
+ },
+ };
+ });
+ },
+ [apiRef],
+ );
+
+ const resetDataSourceState = React.useCallback(() => {
+ apiRef.current.setState((state) => {
+ return {
+ ...state,
+ dataSource: INITIAL_STATE,
+ };
+ });
+ }, [apiRef]);
+
+ const dataSourceApi: GridDataSourceApi = {
+ unstable_dataSource: {
+ setChildrenLoading,
+ setChildrenFetchError,
+ fetchRows,
+ cache,
+ },
+ };
+
+ const dataSourcePrivateApi: GridDataSourcePrivateApi = {
+ fetchRowChildren,
+ resetDataSourceState,
+ };
+
+ useGridApiMethod(apiRef, dataSourceApi, 'public');
+ useGridApiMethod(apiRef, dataSourcePrivateApi, 'private');
+
+ useGridApiEventHandler(apiRef, 'sortModelChange', runIfServerMode(props.sortingMode, fetchRows));
+ useGridApiEventHandler(apiRef, 'filterModelChange', runIfServerMode(props.filterMode, fetchRows));
+ useGridApiEventHandler(
+ apiRef,
+ 'paginationModelChange',
+ runIfServerMode(props.paginationMode, fetchRows),
+ );
+
+ const isFirstRender = React.useRef(true);
+ React.useEffect(() => {
+ if (isFirstRender.current) {
+ isFirstRender.current = false;
+ return;
+ }
+ const newCache = getCache(props.unstable_dataSourceCache);
+ setCache((prevCache) => (prevCache !== newCache ? newCache : prevCache));
+ }, [props.unstable_dataSourceCache]);
+
+ React.useEffect(() => {
+ if (props.unstable_dataSource) {
+ apiRef.current.unstable_dataSource.cache.clear();
+ apiRef.current.unstable_dataSource.fetchRows();
+ }
+ }, [apiRef, props.unstable_dataSource]);
+
+ React.useEffect(() => {
+ if (
+ groupsToAutoFetch &&
+ groupsToAutoFetch.length &&
+ scheduledGroups.current < groupsToAutoFetch.length
+ ) {
+ const groupsToSchedule = groupsToAutoFetch.slice(scheduledGroups.current);
+ nestedDataManager.queue(groupsToSchedule);
+ scheduledGroups.current = groupsToAutoFetch.length;
+ }
+ }, [apiRef, nestedDataManager, groupsToAutoFetch]);
+};
diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts
new file mode 100644
index 000000000000..dafc6d9783f2
--- /dev/null
+++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/utils.ts
@@ -0,0 +1,114 @@
+import { GridRowId } from '@mui/x-data-grid';
+import { GridPrivateApiPro } from '../../../models/gridApiPro';
+
+const MAX_CONCURRENT_REQUESTS = Infinity;
+
+export const runIfServerMode = (modeProp: 'server' | 'client', fn: Function) => () => {
+ if (modeProp === 'server') {
+ fn();
+ }
+};
+
+export enum RequestStatus {
+ QUEUED,
+ PENDING,
+ SETTLED,
+ UNKNOWN,
+}
+
+/**
+ * Fetches row children from the server with option to limit the number of concurrent requests
+ * Determines the status of a request based on the enum `RequestStatus`
+ * Uses `GridRowId` to uniquely identify a request
+ */
+export class NestedDataManager {
+ private pendingRequests: Set = new Set();
+
+ private queuedRequests: Set = new Set();
+
+ private settledRequests: Set = new Set();
+
+ private api: GridPrivateApiPro;
+
+ private maxConcurrentRequests: number;
+
+ constructor(
+ privateApiRef: React.MutableRefObject,
+ maxConcurrentRequests = MAX_CONCURRENT_REQUESTS,
+ ) {
+ this.api = privateApiRef.current;
+ this.maxConcurrentRequests = maxConcurrentRequests;
+ }
+
+ private processQueue = async () => {
+ if (this.queuedRequests.size === 0 || this.pendingRequests.size >= this.maxConcurrentRequests) {
+ return;
+ }
+ const loopLength = Math.min(
+ this.maxConcurrentRequests - this.pendingRequests.size,
+ this.queuedRequests.size,
+ );
+ if (loopLength === 0) {
+ return;
+ }
+ const fetchQueue = Array.from(this.queuedRequests);
+
+ for (let i = 0; i < loopLength; i += 1) {
+ const id = fetchQueue[i];
+ this.queuedRequests.delete(id);
+ this.pendingRequests.add(id);
+ this.api.fetchRowChildren(id);
+ }
+ };
+
+ public queue = async (ids: GridRowId[]) => {
+ const loadingIds: Record = {};
+ ids.forEach((id) => {
+ this.queuedRequests.add(id);
+ loadingIds[id] = true;
+ });
+ this.api.setState((state) => ({
+ ...state,
+ dataSource: {
+ ...state.dataSource,
+ loading: {
+ ...state.dataSource.loading,
+ ...loadingIds,
+ },
+ },
+ }));
+ this.processQueue();
+ };
+
+ public setRequestSettled = (id: GridRowId) => {
+ this.pendingRequests.delete(id);
+ this.settledRequests.add(id);
+ this.processQueue();
+ };
+
+ public clear = () => {
+ this.queuedRequests.clear();
+ Array.from(this.pendingRequests).forEach((id) => this.clearPendingRequest(id));
+ };
+
+ public clearPendingRequest = (id: GridRowId) => {
+ this.api.unstable_dataSource.setChildrenLoading(id, false);
+ this.pendingRequests.delete(id);
+ this.processQueue();
+ };
+
+ public getRequestStatus = (id: GridRowId) => {
+ if (this.pendingRequests.has(id)) {
+ return RequestStatus.PENDING;
+ }
+ if (this.queuedRequests.has(id)) {
+ return RequestStatus.QUEUED;
+ }
+ if (this.settledRequests.has(id)) {
+ return RequestStatus.SETTLED;
+ }
+ return RequestStatus.UNKNOWN;
+ };
+
+ public getActiveRequestsCount = () => this.pendingRequests.size + this.queuedRequests.size;
+}
diff --git a/packages/x-data-grid-pro/src/hooks/features/index.ts b/packages/x-data-grid-pro/src/hooks/features/index.ts
index ea390a65dc61..dd9209be6a53 100644
--- a/packages/x-data-grid-pro/src/hooks/features/index.ts
+++ b/packages/x-data-grid-pro/src/hooks/features/index.ts
@@ -5,3 +5,5 @@ export * from './rowReorder';
export * from './treeData';
export * from './detailPanel';
export * from './rowPinning';
+export * from './dataSource/interfaces';
+export * from './dataSource/cache';
diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx
new file mode 100644
index 000000000000..6c182e6c04d1
--- /dev/null
+++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx
@@ -0,0 +1,256 @@
+import * as React from 'react';
+import {
+ gridRowTreeSelector,
+ useFirstRender,
+ GridColDef,
+ GridRenderCellParams,
+ GridDataSourceGroupNode,
+ GridRowId,
+ GRID_CHECKBOX_SELECTION_FIELD,
+} from '@mui/x-data-grid';
+import {
+ GridPipeProcessor,
+ GridRowsPartialUpdates,
+ GridStrategyProcessor,
+ useGridRegisterPipeProcessor,
+ useGridRegisterStrategyProcessor,
+} from '@mui/x-data-grid/internals';
+import {
+ GRID_TREE_DATA_GROUPING_COL_DEF,
+ GRID_TREE_DATA_GROUPING_COL_DEF_FORCED_PROPERTIES,
+} from '../treeData/gridTreeDataGroupColDef';
+import { DataGridProProcessedProps } from '../../../models/dataGridProProps';
+import { skipFiltering, skipSorting } from './utils';
+import { GridPrivateApiPro } from '../../../models/gridApiPro';
+import {
+ GridGroupingColDefOverride,
+ GridGroupingColDefOverrideParams,
+} from '../../../models/gridGroupingColDefOverride';
+import { GridDataSourceTreeDataGroupingCell } from '../../../components/GridDataSourceTreeDataGroupingCell';
+import { createRowTree } from '../../../utils/tree/createRowTree';
+import {
+ GridTreePathDuplicateHandler,
+ RowTreeBuilderGroupingCriterion,
+} from '../../../utils/tree/models';
+import { updateRowTree } from '../../../utils/tree/updateRowTree';
+import { getVisibleRowsLookup } from '../../../utils/tree/utils';
+
+const DATA_SOURCE_TREE_DATA_STRATEGY = 'dataSourceTreeData';
+
+export const useGridDataSourceTreeDataPreProcessors = (
+ privateApiRef: React.MutableRefObject,
+ props: Pick<
+ DataGridProProcessedProps,
+ | 'treeData'
+ | 'groupingColDef'
+ | 'disableChildrenSorting'
+ | 'disableChildrenFiltering'
+ | 'defaultGroupingExpansionDepth'
+ | 'isGroupExpandedByDefault'
+ | 'unstable_dataSource'
+ >,
+) => {
+ const setStrategyAvailability = React.useCallback(() => {
+ privateApiRef.current.setStrategyAvailability(
+ 'rowTree',
+ DATA_SOURCE_TREE_DATA_STRATEGY,
+ props.treeData && props.unstable_dataSource ? () => true : () => false,
+ );
+ }, [privateApiRef, props.treeData, props.unstable_dataSource]);
+
+ const getGroupingColDef = React.useCallback(() => {
+ const groupingColDefProp = props.groupingColDef;
+
+ let colDefOverride: GridGroupingColDefOverride | null | undefined;
+ if (typeof groupingColDefProp === 'function') {
+ const params: GridGroupingColDefOverrideParams = {
+ groupingName: DATA_SOURCE_TREE_DATA_STRATEGY,
+ fields: [],
+ };
+
+ colDefOverride = groupingColDefProp(params);
+ } else {
+ colDefOverride = groupingColDefProp;
+ }
+
+ const { hideDescendantCount, ...colDefOverrideProperties } = colDefOverride ?? {};
+
+ const commonProperties: Omit = {
+ ...GRID_TREE_DATA_GROUPING_COL_DEF,
+ renderCell: (params) => (
+ )}
+ hideDescendantCount={hideDescendantCount}
+ />
+ ),
+ headerName: privateApiRef.current.getLocaleText('treeDataGroupingHeaderName'),
+ };
+
+ return {
+ ...commonProperties,
+ ...colDefOverrideProperties,
+ ...GRID_TREE_DATA_GROUPING_COL_DEF_FORCED_PROPERTIES,
+ };
+ }, [privateApiRef, props.groupingColDef]);
+
+ const updateGroupingColumn = React.useCallback>(
+ (columnsState) => {
+ if (!props.unstable_dataSource) {
+ return columnsState;
+ }
+ const groupingColDefField = GRID_TREE_DATA_GROUPING_COL_DEF_FORCED_PROPERTIES.field;
+
+ const shouldHaveGroupingColumn = props.treeData;
+ const prevGroupingColumn = columnsState.lookup[groupingColDefField];
+
+ if (shouldHaveGroupingColumn) {
+ const newGroupingColumn = getGroupingColDef();
+ if (prevGroupingColumn) {
+ newGroupingColumn.width = prevGroupingColumn.width;
+ newGroupingColumn.flex = prevGroupingColumn.flex;
+ }
+ columnsState.lookup[groupingColDefField] = newGroupingColumn;
+ if (prevGroupingColumn == null) {
+ const index = columnsState.orderedFields[0] === GRID_CHECKBOX_SELECTION_FIELD ? 1 : 0;
+ columnsState.orderedFields = [
+ ...columnsState.orderedFields.slice(0, index),
+ groupingColDefField,
+ ...columnsState.orderedFields.slice(index),
+ ];
+ }
+ } else if (!shouldHaveGroupingColumn && prevGroupingColumn) {
+ delete columnsState.lookup[groupingColDefField];
+ columnsState.orderedFields = columnsState.orderedFields.filter(
+ (field) => field !== groupingColDefField,
+ );
+ }
+
+ return columnsState;
+ },
+ [props.treeData, props.unstable_dataSource, getGroupingColDef],
+ );
+
+ const createRowTreeForTreeData = React.useCallback>(
+ (params) => {
+ const getGroupKey = props.unstable_dataSource?.getGroupKey;
+ if (!getGroupKey) {
+ throw new Error('MUI X: No `getGroupKey` method provided with the dataSource.');
+ }
+
+ const getChildrenCount = props.unstable_dataSource?.getChildrenCount;
+ if (!getChildrenCount) {
+ throw new Error('MUI X: No `getChildrenCount` method provided with the dataSource.');
+ }
+
+ const parentPath = (params.updates as GridRowsPartialUpdates).groupKeys ?? [];
+
+ const getRowTreeBuilderNode = (rowId: GridRowId) => {
+ const count = getChildrenCount(params.dataRowIdToModelLookup[rowId]);
+ return {
+ id: rowId,
+ path: [...parentPath, getGroupKey(params.dataRowIdToModelLookup[rowId])].map(
+ (key): RowTreeBuilderGroupingCriterion => ({ key, field: null }),
+ ),
+ hasServerChildren: !!count && count !== 0,
+ };
+ };
+
+ const onDuplicatePath: GridTreePathDuplicateHandler = (firstId, secondId, path) => {
+ throw new Error(
+ [
+ 'MUI X: The values returned by `getGroupKey` for all the sibling rows should be unique.',
+ `The rows with id #${firstId} and #${secondId} have the same.`,
+ `Path: ${JSON.stringify(path.map((step) => step.key))}.`,
+ ].join('\n'),
+ );
+ };
+
+ if (params.updates.type === 'full') {
+ return createRowTree({
+ previousTree: params.previousTree,
+ nodes: params.updates.rows.map(getRowTreeBuilderNode),
+ defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth,
+ isGroupExpandedByDefault: props.isGroupExpandedByDefault,
+ groupingName: DATA_SOURCE_TREE_DATA_STRATEGY,
+ onDuplicatePath,
+ });
+ }
+
+ return updateRowTree({
+ nodes: {
+ inserted: params.updates.actions.insert.map(getRowTreeBuilderNode),
+ modified: params.updates.actions.modify.map(getRowTreeBuilderNode),
+ removed: params.updates.actions.remove,
+ },
+ previousTree: params.previousTree!,
+ previousGroupsToFetch: params.previousGroupsToFetch,
+ previousTreeDepth: params.previousTreeDepths!,
+ defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth,
+ isGroupExpandedByDefault: props.isGroupExpandedByDefault,
+ groupingName: DATA_SOURCE_TREE_DATA_STRATEGY,
+ });
+ },
+ [
+ props.unstable_dataSource,
+ props.defaultGroupingExpansionDepth,
+ props.isGroupExpandedByDefault,
+ ],
+ );
+
+ const filterRows = React.useCallback>(() => {
+ const rowTree = gridRowTreeSelector(privateApiRef);
+
+ return skipFiltering(rowTree);
+ }, [privateApiRef]);
+
+ const sortRows = React.useCallback>(() => {
+ const rowTree = gridRowTreeSelector(privateApiRef);
+
+ return skipSorting(rowTree);
+ }, [privateApiRef]);
+
+ useGridRegisterPipeProcessor(privateApiRef, 'hydrateColumns', updateGroupingColumn);
+ useGridRegisterStrategyProcessor(
+ privateApiRef,
+ DATA_SOURCE_TREE_DATA_STRATEGY,
+ 'rowTreeCreation',
+ createRowTreeForTreeData,
+ );
+ useGridRegisterStrategyProcessor(
+ privateApiRef,
+ DATA_SOURCE_TREE_DATA_STRATEGY,
+ 'filtering',
+ filterRows,
+ );
+ useGridRegisterStrategyProcessor(
+ privateApiRef,
+ DATA_SOURCE_TREE_DATA_STRATEGY,
+ 'sorting',
+ sortRows,
+ );
+ useGridRegisterStrategyProcessor(
+ privateApiRef,
+ DATA_SOURCE_TREE_DATA_STRATEGY,
+ 'visibleRowsLookupCreation',
+ getVisibleRowsLookup,
+ );
+
+ /**
+ * 1ST RENDER
+ */
+ useFirstRender(() => {
+ setStrategyAvailability();
+ });
+
+ /**
+ * EFFECTS
+ */
+ const isFirstRender = React.useRef(true);
+ React.useEffect(() => {
+ if (!isFirstRender.current) {
+ setStrategyAvailability();
+ } else {
+ isFirstRender.current = false;
+ }
+ }, [setStrategyAvailability]);
+};
diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts
new file mode 100644
index 000000000000..e1314f6aef1d
--- /dev/null
+++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts
@@ -0,0 +1,22 @@
+import { GridRowId, GridRowTreeConfig, GRID_ROOT_GROUP_ID } from '@mui/x-data-grid';
+import { getTreeNodeDescendants } from '@mui/x-data-grid/internals';
+
+export function skipFiltering(rowTree: GridRowTreeConfig) {
+ const filteredRowsLookup: Record = {};
+ const filteredDescendantCountLookup: Record = {};
+
+ const nodes = Object.values(rowTree);
+ for (let i = 0; i < nodes.length; i += 1) {
+ const node: any = nodes[i];
+ filteredRowsLookup[node.id] = true;
+ }
+
+ return {
+ filteredRowsLookup,
+ filteredDescendantCountLookup,
+ };
+}
+
+export function skipSorting(rowTree: GridRowTreeConfig) {
+ return getTreeNodeDescendants(rowTree, GRID_ROOT_GROUP_ID, false);
+}
diff --git a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeData.tsx b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeData.tsx
index f2410669db0f..8f09f695627d 100644
--- a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeData.tsx
+++ b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeData.tsx
@@ -1,9 +1,14 @@
import * as React from 'react';
import { useGridApiEventHandler, GridEventListener } from '@mui/x-data-grid';
import { GridApiPro } from '../../../models/gridApiPro';
+import { DataGridProProcessedProps } from '../../../models/dataGridProProps';
+
import { GRID_TREE_DATA_GROUPING_FIELD } from './gridTreeDataGroupColDef';
-export const useGridTreeData = (apiRef: React.MutableRefObject) => {
+export const useGridTreeData = (
+ apiRef: React.MutableRefObject,
+ props: Pick,
+) => {
/**
* EVENTS
*/
@@ -19,10 +24,15 @@ export const useGridTreeData = (apiRef: React.MutableRefObject) => {
return;
}
+ if (props.unstable_dataSource && !params.rowNode.childrenExpanded) {
+ apiRef.current.unstable_dataSource.fetchRows(params.id);
+ return;
+ }
+
apiRef.current.setRowChildrenExpansion(params.id, !params.rowNode.childrenExpanded);
}
},
- [apiRef],
+ [apiRef, props.unstable_dataSource],
);
useGridApiEventHandler(apiRef, 'cellKeyDown', handleCellKeyDown);
diff --git a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx
index be47c0bd6bb1..74c0fcfcda38 100644
--- a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx
+++ b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx
@@ -46,15 +46,16 @@ export const useGridTreeDataPreProcessors = (
| 'disableChildrenFiltering'
| 'defaultGroupingExpansionDepth'
| 'isGroupExpandedByDefault'
+ | 'unstable_dataSource'
>,
) => {
const setStrategyAvailability = React.useCallback(() => {
privateApiRef.current.setStrategyAvailability(
'rowTree',
TREE_DATA_STRATEGY,
- props.treeData ? () => true : () => false,
+ props.treeData && !props.unstable_dataSource ? () => true : () => false,
);
- }, [privateApiRef, props.treeData]);
+ }, [privateApiRef, props.treeData, props.unstable_dataSource]);
const getGroupingColDef = React.useCallback(() => {
const groupingColDefProp = props.groupingColDef;
@@ -93,6 +94,9 @@ export const useGridTreeDataPreProcessors = (
const updateGroupingColumn = React.useCallback>(
(columnsState) => {
+ if (props.unstable_dataSource) {
+ return columnsState;
+ }
const groupingColDefField = GRID_TREE_DATA_GROUPING_COL_DEF_FORCED_PROPERTIES.field;
const shouldHaveGroupingColumn = props.treeData;
@@ -122,7 +126,7 @@ export const useGridTreeDataPreProcessors = (
return columnsState;
},
- [props.treeData, getGroupingColDef],
+ [props.treeData, props.unstable_dataSource, getGroupingColDef],
);
const createRowTreeForTreeData = React.useCallback>(
diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts
index 83f507d992b1..28b184e2bd42 100644
--- a/packages/x-data-grid-pro/src/internals/index.ts
+++ b/packages/x-data-grid-pro/src/internals/index.ts
@@ -15,6 +15,7 @@ export {
useGridColumnReorder,
columnReorderStateInitializer,
} from '../hooks/features/columnReorder/useGridColumnReorder';
+export { useGridDataSourceTreeDataPreProcessors } from '../hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors';
export {
useGridDetailPanel,
detailPanelStateInitializer,
@@ -36,6 +37,10 @@ export {
} from '../hooks/features/rowPinning/useGridRowPinningPreProcessors';
export { useGridLazyLoader } from '../hooks/features/lazyLoader/useGridLazyLoader';
export { useGridLazyLoaderPreProcessors } from '../hooks/features/lazyLoader/useGridLazyLoaderPreProcessors';
+export {
+ useGridDataSource,
+ dataSourceStateInitializer,
+} from '../hooks/features/dataSource/useGridDataSource';
export type {
GridExperimentalProFeatures,
diff --git a/packages/x-data-grid-pro/src/internals/propValidation.ts b/packages/x-data-grid-pro/src/internals/propValidation.ts
index b93320993aab..13f138529d47 100644
--- a/packages/x-data-grid-pro/src/internals/propValidation.ts
+++ b/packages/x-data-grid-pro/src/internals/propValidation.ts
@@ -16,6 +16,7 @@ export const propValidatorsDataGridPro: PropValidator
(props) =>
(props.treeData &&
props.filterMode === 'server' &&
+ !props.unstable_dataSource &&
'MUI X: The `filterMode="server"` prop is not available when the `treeData` is enabled.') ||
undefined,
(props) =>
diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts
index 9ffc76113476..4d611b57ea06 100644
--- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts
+++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts
@@ -8,7 +8,7 @@ import {
GridGroupNode,
GridFeatureMode,
} from '@mui/x-data-grid';
-import {
+import type {
GridExperimentalFeatures,
DataGridPropsWithoutDefaultValue,
DataGridPropsWithDefaultValues,
@@ -17,6 +17,8 @@ import {
GridPinnedColumnFields,
DataGridProSharedPropsWithDefaultValue,
DataGridProSharedPropsWithoutDefaultValue,
+ GridDataSourceCache,
+ GridGetRowsParams,
} from '@mui/x-data-grid/internals';
import type { GridPinnedRowsProp } from '../hooks/features/rowPinning';
import { GridApiPro } from './gridApiPro';
@@ -137,11 +139,30 @@ export interface DataGridProPropsWithDefaultValue void;
+}
+
+interface DataGridProRegularProps {
+ /**
+ * Determines the path of a row in the tree data.
+ * For instance, a row with the path ["A", "B"] is the child of the row with the path ["A"].
+ * Note that all paths must contain at least one element.
+ * @template R
+ * @param {R} row The row from which we want the path.
+ * @returns {string[]} The path to the row.
+ */
+ getTreeDataPath?: (row: R) => string[];
+}
+
export interface DataGridProPropsWithoutDefaultValue
extends Omit<
DataGridPropsWithoutDefaultValue,
'initialState' | 'componentsProps' | 'slotProps'
>,
+ DataGridProRegularProps,
+ DataGridProDataSourceProps,
DataGridProSharedPropsWithoutDefaultValue {
/**
* The ref object that allows grid manipulation. Can be instantiated with `useGridApiRef()`.
@@ -158,15 +179,6 @@ export interface DataGridProPropsWithoutDefaultValue;
- /**
- * Determines the path of a row in the tree data.
- * For instance, a row with the path ["A", "B"] is the child of the row with the path ["A"].
- * Note that all paths must contain at least one element.
- * @template R
- * @param {R} row The row from which we want the path.
- * @returns {string[]} The path to the row.
- */
- getTreeDataPath?: (row: R) => string[];
/**
* Callback fired when scrolling to the bottom of the grid viewport.
* @param {GridRowScrollEndParams} params With all properties from [[GridRowScrollEndParams]].
diff --git a/packages/x-data-grid-pro/src/models/dataSource.ts b/packages/x-data-grid-pro/src/models/dataSource.ts
deleted file mode 100644
index 53bb071e6b4f..000000000000
--- a/packages/x-data-grid-pro/src/models/dataSource.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import {
- GridSortModel,
- GridFilterModel,
- GridColDef,
- GridRowModel,
- GridPaginationModel,
-} from '@mui/x-data-grid';
-
-interface GetRowsParams {
- sortModel: GridSortModel;
- filterModel: GridFilterModel;
- /**
- * Alternate to `start` and `end`, maps to `GridPaginationModel` interface.
- */
- paginationModel: GridPaginationModel;
- /**
- * First row index to fetch (number) or cursor information (number | string).
- */
- start: number | string;
- /**
- * Last row index to fetch.
- */
- end: number; // last row index to fetch
- /**
- * Array of keys returned by `getGroupKey` of all the parent rows until the row for which the data is requested
- * `getGroupKey` prop must be implemented to use this.
- * Useful for `treeData` and `rowGrouping` only.
- */
- groupKeys: string[];
- /**
- * List of grouped columns (only applicable with `rowGrouping`).
- */
- groupFields: GridColDef['field'][];
-}
-
-interface GetRowsResponse {
- rows: GridRowModel[];
- /**
- * To reflect updates in total `rowCount` (optional).
- * Useful when the `rowCount` is inaccurate (for example when filtering) or not available upfront.
- */
- rowCount?: number;
- /**
- * Additional `pageInfo` to help the grid determine if there are more rows to fetch (corner-cases).
- * `hasNextPage`: When row count is unknown/inaccurate, if `truncated` is set or rowCount is not known, data will keep loading until `hasNextPage` is `false`
- * `truncated`: To reflect `rowCount` is inaccurate (will trigger `x-y of many` in pagination after the count of rows fetched is greater than provided `rowCount`)
- * It could be useful with:
- * 1. Cursor based pagination:
- * When rowCount is not known, grid will check for `hasNextPage` to determine
- * if there are more rows to fetch.
- * 2. Inaccurate `rowCount`:
- * `truncated: true` will let the grid know that `rowCount` is estimated/truncated.
- * Thus `hasNextPage` will come into play to check more rows are available to fetch after the number becomes >= provided `rowCount`
- */
- pageInfo?: {
- hasNextPage?: boolean;
- truncated?: number;
- };
-}
-
-export interface DataSource {
- /**
- * Fetcher Functions:
- * - `getRows` is required
- * - `updateRow` is optional
- *
- * `getRows` will be used by the grid to fetch data for the current page or children for the current parent group.
- * It may return a `rowCount` to update the total count of rows in the grid along with the optional `pageInfo`.
- */
- getRows(params: GetRowsParams): Promise;
- updateRow?(rows: GridRowModel): Promise;
-}
diff --git a/packages/x-data-grid-pro/src/models/gridApiPro.ts b/packages/x-data-grid-pro/src/models/gridApiPro.ts
index 33e5f884a0d4..0f0f56261ef5 100644
--- a/packages/x-data-grid-pro/src/models/gridApiPro.ts
+++ b/packages/x-data-grid-pro/src/models/gridApiPro.ts
@@ -11,6 +11,8 @@ import type {
GridDetailPanelApi,
GridRowPinningApi,
GridDetailPanelPrivateApi,
+ GridDataSourceApi,
+ GridDataSourcePrivateApi,
} from '../hooks';
import type { DataGridProProcessedProps } from './dataGridProProps';
@@ -23,6 +25,7 @@ export interface GridApiPro
GridColumnPinningApi,
GridDetailPanelApi,
GridRowPinningApi,
+ GridDataSourceApi,
// APIs that are private in Community plan, but public in Pro and Premium plans
GridRowMultiSelectionApi,
GridColumnReorderApi {}
@@ -31,4 +34,5 @@ export interface GridPrivateApiPro
extends GridApiPro,
GridPrivateOnlyApiCommon,
GridDetailPanelPrivateApi,
- GridInfiniteLoaderPrivateApi {}
+ GridInfiniteLoaderPrivateApi,
+ GridDataSourcePrivateApi {}
diff --git a/packages/x-data-grid-pro/src/models/gridStatePro.ts b/packages/x-data-grid-pro/src/models/gridStatePro.ts
index 662a9bed10b0..e26694abd3e8 100644
--- a/packages/x-data-grid-pro/src/models/gridStatePro.ts
+++ b/packages/x-data-grid-pro/src/models/gridStatePro.ts
@@ -9,6 +9,7 @@ import type {
GridDetailPanelInitialState,
GridColumnReorderState,
} from '../hooks';
+import type { GridDataSourceState } from '../hooks/features/dataSource/interfaces';
/**
* The state of `DataGridPro`.
@@ -17,6 +18,7 @@ export interface GridStatePro extends GridStateCommunity {
columnReorder: GridColumnReorderState;
pinnedColumns: GridColumnPinningState;
detailPanel: GridDetailPanelState;
+ dataSource: GridDataSourceState;
}
/**
diff --git a/packages/x-data-grid-pro/src/models/index.ts b/packages/x-data-grid-pro/src/models/index.ts
index 36deff5c944b..8110b6c70a91 100644
--- a/packages/x-data-grid-pro/src/models/index.ts
+++ b/packages/x-data-grid-pro/src/models/index.ts
@@ -1,3 +1,9 @@
+export type {
+ GridGetRowsParams,
+ GridGetRowsResponse,
+ GridDataSource,
+ GridDataSourceCache,
+} from '@mui/x-data-grid/internals';
export * from './gridApiPro';
export * from './gridGroupingColDefOverride';
export * from './gridRowScrollEndParams';
diff --git a/packages/x-data-grid-pro/src/tests/columnPinning.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/columnPinning.DataGridPro.test.tsx
index 97615142218a..7508c219422f 100644
--- a/packages/x-data-grid-pro/src/tests/columnPinning.DataGridPro.test.tsx
+++ b/packages/x-data-grid-pro/src/tests/columnPinning.DataGridPro.test.tsx
@@ -229,7 +229,7 @@ describe(' - Column pinning', () => {
const borderLeftWidth = computedStyle.getPropertyValue('border-left-width');
expect(borderLeftWidth).to.equal('1px');
// should not be transparent
- expect(borderLeftColor).to.not.equal('rgba(0, 0, 0, 0)');
+ expect(borderLeftColor).not.to.equal('rgba(0, 0, 0, 0)');
});
// https://github.com/mui/mui-x/issues/12431
diff --git a/packages/x-data-grid-pro/src/tests/filterPanel.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/filterPanel.DataGridPro.test.tsx
index 4b4722fe3a89..1a924b004b82 100644
--- a/packages/x-data-grid-pro/src/tests/filterPanel.DataGridPro.test.tsx
+++ b/packages/x-data-grid-pro/src/tests/filterPanel.DataGridPro.test.tsx
@@ -37,7 +37,7 @@ describe(' - Filter panel', () => {
act(() => apiRef.current.showFilterPanel('brand'));
const model = gridFilterModelSelector(apiRef);
expect(model.items).to.have.length(1);
- expect(model.items[0].id).to.not.equal(null);
- expect(model.items[0].operator).to.not.equal(null);
+ expect(model.items[0].id).not.to.equal(null);
+ expect(model.items[0].operator).not.to.equal(null);
});
});
diff --git a/packages/x-data-grid-pro/src/tests/lazyLoader.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/lazyLoader.DataGridPro.test.tsx
index b7915e7c6878..1f2dbac26b93 100644
--- a/packages/x-data-grid-pro/src/tests/lazyLoader.DataGridPro.test.tsx
+++ b/packages/x-data-grid-pro/src/tests/lazyLoader.DataGridPro.test.tsx
@@ -166,8 +166,8 @@ describe(' - Lazy loader', () => {
const updatedAllRows = apiRef.current.getRowNode(GRID_ROOT_GROUP_ID)!.children;
expect(updatedAllRows.slice(4, 6)).to.deep.equal([4, 5]);
- expect(apiRef.current.getRowNode(4)).to.not.equal(null);
- expect(apiRef.current.getRowNode(5)).to.not.equal(null);
+ expect(apiRef.current.getRowNode(4)).not.to.equal(null);
+ expect(apiRef.current.getRowNode(5)).not.to.equal(null);
});
it('should update rows when `apiRef.current.updateRows` with data reversed', () => {
diff --git a/packages/x-data-grid-pro/src/tests/license.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/license.DataGridPro.test.tsx
index 0e4b3abd433b..e65d7ff55bf0 100644
--- a/packages/x-data-grid-pro/src/tests/license.DataGridPro.test.tsx
+++ b/packages/x-data-grid-pro/src/tests/license.DataGridPro.test.tsx
@@ -15,7 +15,7 @@ describe(' - License', () => {
]);
await waitFor(() => {
- expect(screen.getByText('MUI X Missing license key')).to.not.equal(null);
+ expect(screen.getByText('MUI X Missing license key')).not.to.equal(null);
});
});
});
diff --git a/packages/x-data-grid-pro/src/tests/pagination.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/pagination.DataGridPro.test.tsx
index 0f9a0952b616..5561eb1afc5e 100644
--- a/packages/x-data-grid-pro/src/tests/pagination.DataGridPro.test.tsx
+++ b/packages/x-data-grid-pro/src/tests/pagination.DataGridPro.test.tsx
@@ -105,7 +105,11 @@ describe(' - Pagination', () => {
it('should log an error if rowCount is used with client-side pagination', () => {
expect(() => {
- render( );
+ render(
+
+
+
,
+ );
}).toErrorDev([
'MUI X: Usage of the `rowCount` prop with client side pagination (`paginationMode="client"`) has no effect. `rowCount` is only meant to be used with `paginationMode="server"`.',
]);
diff --git a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
index f0704d4fb4fd..101bc83e0420 100644
--- a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
+++ b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx
@@ -27,8 +27,12 @@ import { useBasicDemoData, getBasicGridData } from '@mui/x-data-grid-generator';
const isJSDOM = /jsdom/.test(window.navigator.userAgent);
+interface BaselineProps extends DataGridProProps {
+ rows: GridValidRowModel[];
+}
+
describe(' - Rows', () => {
- let baselineProps: DataGridProProps & { rows: GridValidRowModel };
+ let baselineProps: BaselineProps;
const { clock, render } = createRenderer({ clock: 'fake' });
diff --git a/packages/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx
index 637907681341..2fb247eaabcb 100644
--- a/packages/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx
+++ b/packages/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx
@@ -249,7 +249,7 @@ describe(' - State persistence', () => {
expect(getColumnValues(0)).to.deep.equal(['3', '2']);
// Preference panel
- expect(screen.getByRole('button', { name: /Add Filter/i })).to.not.equal(null);
+ expect(screen.getByRole('button', { name: /Add Filter/i })).not.to.equal(null);
// Columns visibility
expect(getColumnHeadersTextContent()).to.deep.equal(['id', 'category']);
diff --git a/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts b/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts
index 09675aa21548..6f79cc1c727c 100644
--- a/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts
+++ b/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts
@@ -22,6 +22,7 @@ export const createRowTree = (params: CreateRowTreeParams): GridRowTreeCreationV
[GRID_ROOT_GROUP_ID]: buildRootGroup(),
};
const treeDepths: GridRowTreeCreationValue['treeDepths'] = {};
+ const groupsToFetch = new Set();
for (let i = 0; i < params.nodes.length; i += 1) {
const node = params.nodes[i];
@@ -32,10 +33,12 @@ export const createRowTree = (params: CreateRowTreeParams): GridRowTreeCreationV
previousTree: params.previousTree,
id: node.id,
path: node.path,
+ hasServerChildren: node.hasServerChildren,
onDuplicatePath: params.onDuplicatePath,
treeDepths,
isGroupExpandedByDefault: params.isGroupExpandedByDefault,
defaultGroupingExpansionDepth: params.defaultGroupingExpansionDepth,
+ groupsToFetch,
});
}
@@ -44,5 +47,6 @@ export const createRowTree = (params: CreateRowTreeParams): GridRowTreeCreationV
treeDepths,
groupingName: params.groupingName,
dataRowIds,
+ groupsToFetch: Array.from(groupsToFetch),
};
};
diff --git a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts
index a79fbdcc759e..86cd27e02026 100644
--- a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts
+++ b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts
@@ -4,10 +4,12 @@ import {
GridLeafNode,
GridRowId,
GridRowTreeConfig,
+ GridDataSourceGroupNode,
} from '@mui/x-data-grid';
import { GridTreeDepths, GridRowTreeUpdatedGroupsManager } from '@mui/x-data-grid/internals';
import {
updateGroupDefaultExpansion,
+ checkGroupChildrenExpansion,
getGroupRowIdFromPath,
insertNodeInTree,
updateGroupNodeIdAndAutoGenerated,
@@ -57,6 +59,8 @@ interface InsertDataRowInTreeParams {
onDuplicatePath?: GridTreePathDuplicateHandler;
isGroupExpandedByDefault?: DataGridProProps['isGroupExpandedByDefault'];
defaultGroupingExpansionDepth: number;
+ hasServerChildren?: boolean;
+ groupsToFetch?: Set;
}
/**
@@ -75,6 +79,8 @@ export const insertDataRowInTree = ({
onDuplicatePath,
isGroupExpandedByDefault,
defaultGroupingExpansionDepth,
+ hasServerChildren,
+ groupsToFetch,
}: InsertDataRowInTreeParams) => {
let parentNodeId = GRID_ROOT_GROUP_ID;
@@ -92,17 +98,43 @@ export const insertDataRowInTree = ({
// If no node matches the full path,
// We create a leaf node for the data row.
if (existingNodeIdWithPartialPath == null) {
- const leafNode: GridLeafNode = {
- type: 'leaf',
- id,
- depth,
- parent: parentNodeId,
- groupingKey: key,
- };
+ let node: GridLeafNode | GridDataSourceGroupNode;
+ if (hasServerChildren) {
+ node = {
+ type: 'group',
+ id,
+ parent: parentNodeId,
+ path: path.map((step) => step.key as string),
+ depth,
+ isAutoGenerated: false,
+ groupingKey: key,
+ groupingField: field,
+ children: [],
+ childrenFromPath: {},
+ childrenExpanded: false,
+ hasServerChildren: true,
+ };
+ const shouldFetchChildren = checkGroupChildrenExpansion(
+ node,
+ defaultGroupingExpansionDepth,
+ isGroupExpandedByDefault,
+ );
+ if (shouldFetchChildren) {
+ groupsToFetch?.add(id);
+ }
+ } else {
+ node = {
+ type: 'leaf',
+ id,
+ depth,
+ parent: parentNodeId,
+ groupingKey: key,
+ };
+ }
updatedGroupsManager?.addAction(parentNodeId, 'insertChildren');
- insertNodeInTree(leafNode, tree, treeDepths, previousTree);
+ insertNodeInTree(node, tree, treeDepths, previousTree);
} else {
const existingNodeWithPartialPath = tree[existingNodeIdWithPartialPath];
diff --git a/packages/x-data-grid-pro/src/utils/tree/models.ts b/packages/x-data-grid-pro/src/utils/tree/models.ts
index 5eef120e37ff..871d3fc86c97 100644
--- a/packages/x-data-grid-pro/src/utils/tree/models.ts
+++ b/packages/x-data-grid-pro/src/utils/tree/models.ts
@@ -8,6 +8,7 @@ export interface RowTreeBuilderGroupingCriterion {
export interface RowTreeBuilderNode {
id: GridRowId;
path: RowTreeBuilderGroupingCriterion[];
+ hasServerChildren?: boolean;
}
/**
diff --git a/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts b/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts
index ad87141ef647..0c7ddc514403 100644
--- a/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts
+++ b/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts
@@ -24,15 +24,19 @@ interface UpdateRowTreeParams {
isGroupExpandedByDefault?: (node: GridGroupNode) => boolean;
groupingName: string;
onDuplicatePath?: GridTreePathDuplicateHandler;
+ previousGroupsToFetch?: GridRowId[];
}
export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationValue => {
const tree = { ...params.previousTree };
const treeDepths = { ...params.previousTreeDepth };
const updatedGroupsManager = createUpdatedGroupsManager();
+ const groupsToFetch = params.previousGroupsToFetch
+ ? new Set([...params.previousGroupsToFetch])
+ : new Set([]);
for (let i = 0; i < params.nodes.inserted.length; i += 1) {
- const { id, path } = params.nodes.inserted[i];
+ const { id, path, hasServerChildren } = params.nodes.inserted[i];
insertDataRowInTree({
previousTree: params.previousTree,
@@ -41,9 +45,11 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV
updatedGroupsManager,
id,
path,
+ hasServerChildren,
onDuplicatePath: params.onDuplicatePath,
isGroupExpandedByDefault: params.isGroupExpandedByDefault,
defaultGroupingExpansionDepth: params.defaultGroupingExpansionDepth,
+ groupsToFetch,
});
}
@@ -59,7 +65,7 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV
}
for (let i = 0; i < params.nodes.modified.length; i += 1) {
- const { id, path } = params.nodes.modified[i];
+ const { id, path, hasServerChildren } = params.nodes.modified[i];
const pathInPreviousTree = getNodePathInTree({ tree, id });
const isInSameGroup = isDeepEqual(pathInPreviousTree, path);
@@ -78,9 +84,11 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV
updatedGroupsManager,
id,
path,
+ hasServerChildren,
onDuplicatePath: params.onDuplicatePath,
isGroupExpandedByDefault: params.isGroupExpandedByDefault,
defaultGroupingExpansionDepth: params.defaultGroupingExpansionDepth,
+ groupsToFetch,
});
} else {
updatedGroupsManager?.addAction(tree[id].parent!, 'modifyChildren');
@@ -96,5 +104,6 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV
groupingName: params.groupingName,
dataRowIds,
updatedGroupsManager,
+ groupsToFetch: Array.from(groupsToFetch),
};
};
diff --git a/packages/x-data-grid-pro/src/utils/tree/utils.ts b/packages/x-data-grid-pro/src/utils/tree/utils.ts
index 5de194769e5c..fba2f4c9bfa2 100644
--- a/packages/x-data-grid-pro/src/utils/tree/utils.ts
+++ b/packages/x-data-grid-pro/src/utils/tree/utils.ts
@@ -49,7 +49,7 @@ export const getNodePathInTree = ({
return path;
};
-export const updateGroupDefaultExpansion = (
+export const checkGroupChildrenExpansion = (
node: GridGroupNode,
defaultGroupingExpansionDepth: number,
isGroupExpandedByDefault?: DataGridProProps['isGroupExpandedByDefault'],
@@ -64,8 +64,20 @@ export const updateGroupDefaultExpansion = (
defaultGroupingExpansionDepth === -1 || defaultGroupingExpansionDepth > node.depth;
}
- node.childrenExpanded = childrenExpanded;
+ return childrenExpanded;
+};
+export const updateGroupDefaultExpansion = (
+ node: GridGroupNode,
+ defaultGroupingExpansionDepth: number,
+ isGroupExpandedByDefault?: DataGridProProps['isGroupExpandedByDefault'],
+) => {
+ const childrenExpanded = checkGroupChildrenExpansion(
+ node,
+ defaultGroupingExpansionDepth,
+ isGroupExpandedByDefault,
+ );
+ node.childrenExpanded = childrenExpanded;
return node;
};
diff --git a/packages/x-data-grid-pro/tsconfig.build.json b/packages/x-data-grid-pro/tsconfig.build.json
index 3649d7b11677..c5eab772f678 100644
--- a/packages/x-data-grid-pro/tsconfig.build.json
+++ b/packages/x-data-grid-pro/tsconfig.build.json
@@ -13,7 +13,8 @@
},
"references": [
{ "path": "../x-data-grid/tsconfig.build.json" },
- { "path": "../x-license/tsconfig.build.json" }
+ { "path": "../x-license/tsconfig.build.json" },
+ { "path": "../x-internals/tsconfig.build.json" }
],
"include": ["src/**/*.ts*"],
"exclude": ["src/**/*.spec.ts*", "src/**/*.test.ts*"]
diff --git a/packages/x-data-grid-pro/tsconfig.json b/packages/x-data-grid-pro/tsconfig.json
index aba8438e2441..a95beb1e8020 100644
--- a/packages/x-data-grid-pro/tsconfig.json
+++ b/packages/x-data-grid-pro/tsconfig.json
@@ -5,7 +5,8 @@
"@mui/internal-test-utils/initMatchers",
"@mui/material/themeCssVarsAugmentation",
"chai-dom",
- "mocha"
+ "mocha",
+ "node"
]
},
"include": ["src/**/*"]
diff --git a/packages/x-data-grid/package.json b/packages/x-data-grid/package.json
index 3749f544f50d..71626d62f370 100644
--- a/packages/x-data-grid/package.json
+++ b/packages/x-data-grid/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/x-data-grid",
- "version": "7.7.1",
+ "version": "7.9.0",
"description": "The Community plan edition of the Data Grid components (MUI X).",
"author": "MUI Team",
"main": "src/index.ts",
@@ -48,8 +48,9 @@
},
"dependencies": {
"@babel/runtime": "^7.24.7",
- "@mui/system": "^5.15.20",
- "@mui/utils": "^5.15.20",
+ "@mui/system": "^5.16.0",
+ "@mui/utils": "^5.16.0",
+ "@mui/x-internals": "workspace:*",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
"reselect": "^4.1.8"
@@ -60,11 +61,11 @@
"react-dom": "^17.0.0 || ^18.0.0"
},
"devDependencies": {
- "@mui/internal-test-utils": "^1.0.1",
- "@mui/joy": "5.0.0-beta.32",
+ "@mui/internal-test-utils": "^1.0.4",
+ "@mui/joy": "^5.0.0-beta.47",
"@mui/types": "^7.2.14",
"@types/prop-types": "^15.7.12",
- "rimraf": "^5.0.7"
+ "rimraf": "^5.0.8"
},
"engines": {
"node": ">=14.0.0"
diff --git a/packages/x-data-grid/src/DataGrid/useDataGridProps.ts b/packages/x-data-grid/src/DataGrid/useDataGridProps.ts
index d807385e16cb..1cd75b30f70b 100644
--- a/packages/x-data-grid/src/DataGrid/useDataGridProps.ts
+++ b/packages/x-data-grid/src/DataGrid/useDataGridProps.ts
@@ -105,14 +105,26 @@ export const useDataGridProps = (inProps: DataGridP
[themedProps.slots],
);
+ const injectDefaultProps = React.useMemo(() => {
+ return (
+ Object.keys(DATA_GRID_PROPS_DEFAULT_VALUES) as Array<
+ keyof DataGridPropsWithDefaultValues
+ >
+ ).reduce((acc, key) => {
+ // @ts-ignore
+ acc[key] = themedProps[key] ?? DATA_GRID_PROPS_DEFAULT_VALUES[key];
+ return acc;
+ }, {} as DataGridPropsWithDefaultValues);
+ }, [themedProps]);
+
return React.useMemo>(
() => ({
- ...DATA_GRID_PROPS_DEFAULT_VALUES,
...themedProps,
+ ...injectDefaultProps,
localeText,
slots,
...DATA_GRID_FORCED_PROPS,
}),
- [themedProps, localeText, slots],
+ [themedProps, localeText, slots, injectDefaultProps],
);
};
diff --git a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx
index 2bcb4eed7a89..dc68ffccc6d8 100644
--- a/packages/x-data-grid/src/components/GridLoadingOverlay.tsx
+++ b/packages/x-data-grid/src/components/GridLoadingOverlay.tsx
@@ -1,13 +1,63 @@
import * as React from 'react';
import PropTypes from 'prop-types';
+import LinearProgress from '@mui/material/LinearProgress';
import CircularProgress from '@mui/material/CircularProgress';
import { GridOverlay, GridOverlayProps } from './containers/GridOverlay';
+import { GridSkeletonLoadingOverlay } from './GridSkeletonLoadingOverlay';
+import { useGridApiContext } from '../hooks/utils/useGridApiContext';
+import { gridRowCountSelector, useGridSelector } from '../hooks';
-const GridLoadingOverlay = React.forwardRef(
+export type GridLoadingOverlayVariant = 'circular-progress' | 'linear-progress' | 'skeleton';
+
+export interface GridLoadingOverlayProps extends GridOverlayProps {
+ /**
+ * The variant of the overlay.
+ * @default 'circular-progress'
+ */
+ variant?: GridLoadingOverlayVariant;
+ /**
+ * The variant of the overlay when no rows are displayed.
+ * @default 'circular-progress'
+ */
+ noRowsVariant?: GridLoadingOverlayVariant;
+}
+
+const LOADING_VARIANTS: Record<
+ GridLoadingOverlayVariant,
+ {
+ component: React.ComponentType;
+ style: React.CSSProperties;
+ }
+> = {
+ 'circular-progress': {
+ component: CircularProgress,
+ style: {},
+ },
+ 'linear-progress': {
+ component: LinearProgress,
+ style: { display: 'block' },
+ },
+ skeleton: {
+ component: GridSkeletonLoadingOverlay,
+ style: { display: 'block' },
+ },
+};
+
+const GridLoadingOverlay = React.forwardRef(
function GridLoadingOverlay(props, ref) {
+ const {
+ variant = 'circular-progress',
+ noRowsVariant = 'circular-progress',
+ style,
+ ...other
+ } = props;
+ const apiRef = useGridApiContext();
+ const rowsCount = useGridSelector(apiRef, gridRowCountSelector);
+ const activeVariant = LOADING_VARIANTS[rowsCount === 0 ? noRowsVariant : variant];
+
return (
-
-
+
+
);
},
@@ -18,11 +68,21 @@ GridLoadingOverlay.propTypes = {
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "pnpm proptypes" |
// ----------------------------------------------------------------------
+ /**
+ * The variant of the overlay when no rows are displayed.
+ * @default 'circular-progress'
+ */
+ noRowsVariant: PropTypes.oneOf(['circular-progress', 'linear-progress', 'skeleton']),
sx: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
PropTypes.func,
PropTypes.object,
]),
+ /**
+ * The variant of the overlay.
+ * @default 'circular-progress'
+ */
+ variant: PropTypes.oneOf(['circular-progress', 'linear-progress', 'skeleton']),
} as any;
export { GridLoadingOverlay };
diff --git a/packages/x-data-grid/src/components/GridRow.tsx b/packages/x-data-grid/src/components/GridRow.tsx
index 134786356f65..917deae2ba62 100644
--- a/packages/x-data-grid/src/components/GridRow.tsx
+++ b/packages/x-data-grid/src/components/GridRow.tsx
@@ -376,10 +376,11 @@ const GridRow = React.forwardRef(function GridRow(
return (
);
}
diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx
new file mode 100644
index 000000000000..62643bb382f3
--- /dev/null
+++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx
@@ -0,0 +1,265 @@
+import * as React from 'react';
+import clsx from 'clsx';
+import { styled } from '@mui/system';
+import useForkRef from '@mui/utils/useForkRef';
+import composeClasses from '@mui/utils/composeClasses';
+import { useGridApiContext } from '../hooks/utils/useGridApiContext';
+import { useGridRootProps } from '../hooks/utils/useGridRootProps';
+import {
+ GridPinnedColumnPosition,
+ gridColumnPositionsSelector,
+ gridColumnsTotalWidthSelector,
+ gridDimensionsSelector,
+ gridVisibleColumnDefinitionsSelector,
+ gridVisiblePinnedColumnDefinitionsSelector,
+ useGridApiEventHandler,
+ useGridSelector,
+} from '../hooks';
+import { GridEventListener } from '../models';
+import { DataGridProcessedProps } from '../models/props/DataGridProps';
+import { getDataGridUtilityClass, gridClasses } from '../constants/gridClasses';
+import { getPinnedCellOffset } from '../internals/utils/getPinnedCellOffset';
+import { shouldCellShowLeftBorder, shouldCellShowRightBorder } from '../utils/cellBorderUtils';
+import { escapeOperandAttributeSelector } from '../utils/domUtils';
+import { GridScrollbarFillerCell } from './GridScrollbarFillerCell';
+
+const SkeletonOverlay = styled('div', {
+ name: 'MuiDataGrid',
+ slot: 'SkeletonLoadingOverlay',
+ overridesResolver: (props, styles) => styles.skeletonLoadingOverlay,
+})({
+ minWidth: '100%',
+ width: 'max-content', // prevents overflow: clip; cutting off the x axis
+ height: '100%',
+ overflow: 'clip', // y axis is hidden while the x axis is allowed to overflow
+});
+
+type OwnerState = { classes: DataGridProcessedProps['classes'] };
+
+const useUtilityClasses = (ownerState: OwnerState) => {
+ const { classes } = ownerState;
+
+ const slots = {
+ root: ['skeletonLoadingOverlay'],
+ };
+
+ return composeClasses(slots, getDataGridUtilityClass, classes);
+};
+
+const getColIndex = (el: HTMLElement) => parseInt(el.getAttribute('data-colindex')!, 10);
+
+const GridSkeletonLoadingOverlay = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(function GridSkeletonLoadingOverlay(props, forwardedRef) {
+ const rootProps = useGridRootProps();
+ const { slots } = rootProps;
+ const classes = useUtilityClasses({ classes: rootProps.classes });
+ const ref = React.useRef(null);
+ const handleRef = useForkRef(ref, forwardedRef);
+ const apiRef = useGridApiContext();
+ const dimensions = useGridSelector(apiRef, gridDimensionsSelector);
+ const viewportHeight = dimensions?.viewportInnerSize.height ?? 0;
+ const skeletonRowsCount = Math.ceil(viewportHeight / dimensions.rowHeight);
+ const totalWidth = useGridSelector(apiRef, gridColumnsTotalWidthSelector);
+ const positions = useGridSelector(apiRef, gridColumnPositionsSelector);
+ const inViewportCount = React.useMemo(
+ () => positions.filter((value) => value <= totalWidth).length,
+ [totalWidth, positions],
+ );
+ const allVisibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector);
+ const columns = React.useMemo(
+ () => allVisibleColumns.slice(0, inViewportCount),
+ [allVisibleColumns, inViewportCount],
+ );
+ const pinnedColumns = useGridSelector(apiRef, gridVisiblePinnedColumnDefinitionsSelector);
+
+ const getPinnedStyle = React.useCallback(
+ (computedWidth: number, index: number, position: GridPinnedColumnPosition) => {
+ const pinnedOffset = getPinnedCellOffset(
+ position,
+ computedWidth,
+ index,
+ positions,
+ dimensions,
+ );
+ return { [position]: pinnedOffset } as const;
+ },
+ [dimensions, positions],
+ );
+
+ const getPinnedPosition = React.useCallback(
+ (field: string) => {
+ if (pinnedColumns.left.findIndex((col) => col.field === field) !== -1) {
+ return GridPinnedColumnPosition.LEFT;
+ }
+ if (pinnedColumns.right.findIndex((col) => col.field === field) !== -1) {
+ return GridPinnedColumnPosition.RIGHT;
+ }
+ return undefined;
+ },
+ [pinnedColumns.left, pinnedColumns.right],
+ );
+
+ const children = React.useMemo(() => {
+ const array: React.ReactNode[] = [];
+
+ for (let i = 0; i < skeletonRowsCount; i += 1) {
+ const rowCells: React.ReactNode[] = [];
+
+ for (let colIndex = 0; colIndex < columns.length; colIndex += 1) {
+ const column = columns[colIndex];
+ const pinnedPosition = getPinnedPosition(column.field);
+ const isPinnedLeft = pinnedPosition === GridPinnedColumnPosition.LEFT;
+ const isPinnedRight = pinnedPosition === GridPinnedColumnPosition.RIGHT;
+ const sectionLength = pinnedPosition
+ ? pinnedColumns[pinnedPosition].length // pinned section
+ : columns.length - pinnedColumns.left.length - pinnedColumns.right.length; // middle section
+ const sectionIndex = pinnedPosition
+ ? pinnedColumns[pinnedPosition].findIndex((col) => col.field === column.field) // pinned section
+ : colIndex - pinnedColumns.left.length; // middle section
+ const pinnedStyle =
+ pinnedPosition && getPinnedStyle(column.computedWidth, colIndex, pinnedPosition);
+ const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width;
+ const showRightBorder = shouldCellShowRightBorder(
+ pinnedPosition,
+ sectionIndex,
+ sectionLength,
+ rootProps.showCellVerticalBorder,
+ gridHasFiller,
+ );
+ const showLeftBorder = shouldCellShowLeftBorder(pinnedPosition, sectionIndex);
+ const isLastColumn = colIndex === columns.length - 1;
+ const isFirstPinnedRight = isPinnedRight && sectionIndex === 0;
+ const hasFillerBefore = isFirstPinnedRight && gridHasFiller;
+ const hasFillerAfter = isLastColumn && !isFirstPinnedRight && gridHasFiller;
+ const expandedWidth = dimensions.viewportOuterSize.width - dimensions.columnsTotalWidth;
+ const emptyCellWidth = Math.max(0, expandedWidth);
+ const emptyCell = (
+
+ );
+ const scrollbarWidth = dimensions.hasScrollY ? dimensions.scrollbarSize : 0;
+ const hasScrollbarFiller = isLastColumn && scrollbarWidth !== 0;
+
+ if (hasFillerBefore) {
+ rowCells.push(emptyCell);
+ }
+
+ rowCells.push(
+ ,
+ );
+
+ if (hasFillerAfter) {
+ rowCells.push(emptyCell);
+ }
+
+ if (hasScrollbarFiller) {
+ rowCells.push( 0} />);
+ }
+ }
+
+ array.push(
+
+ {rowCells}
+
,
+ );
+ }
+ return array;
+ }, [
+ slots,
+ columns,
+ pinnedColumns,
+ skeletonRowsCount,
+ rootProps.showCellVerticalBorder,
+ dimensions.columnsTotalWidth,
+ dimensions.viewportOuterSize.width,
+ dimensions.rowHeight,
+ dimensions.hasScrollY,
+ dimensions.scrollbarSize,
+ getPinnedPosition,
+ getPinnedStyle,
+ ]);
+
+ // Sync the column resize of the overlay columns with the grid
+ const handleColumnResize: GridEventListener<'columnResize'> = (params) => {
+ const { colDef, width } = params;
+ const cells = ref.current?.querySelectorAll(
+ `[data-field="${escapeOperandAttributeSelector(colDef.field)}"]`,
+ );
+
+ if (!cells) {
+ throw new Error('MUI X: Expected skeleton cells to be defined with `data-field` attribute.');
+ }
+
+ const resizedColIndex = columns.findIndex((col) => col.field === colDef.field);
+ const pinnedPosition = getPinnedPosition(colDef.field);
+ const isPinnedLeft = pinnedPosition === GridPinnedColumnPosition.LEFT;
+ const isPinnedRight = pinnedPosition === GridPinnedColumnPosition.RIGHT;
+ const currentWidth = getComputedStyle(cells[0]).getPropertyValue('--width');
+ const delta = parseInt(currentWidth, 10) - width;
+
+ if (cells) {
+ cells.forEach((element) => {
+ element.style.setProperty('--width', `${width}px`);
+ });
+ }
+
+ if (isPinnedLeft) {
+ const pinnedCells = ref.current?.querySelectorAll(
+ `.${gridClasses['cell--pinnedLeft']}`,
+ );
+ pinnedCells?.forEach((element) => {
+ const colIndex = getColIndex(element);
+ if (colIndex > resizedColIndex) {
+ element.style.left = `${parseInt(getComputedStyle(element).left, 10) - delta}px`;
+ }
+ });
+ }
+
+ if (isPinnedRight) {
+ const pinnedCells = ref.current?.querySelectorAll(
+ `.${gridClasses['cell--pinnedRight']}`,
+ );
+ pinnedCells?.forEach((element) => {
+ const colIndex = getColIndex(element);
+ if (colIndex < resizedColIndex) {
+ element.style.right = `${parseInt(getComputedStyle(element).right, 10) + delta}px`;
+ }
+ });
+ }
+ };
+
+ useGridApiEventHandler(apiRef, 'columnResize', handleColumnResize);
+
+ return (
+
+ {children}
+
+ );
+});
+
+export { GridSkeletonLoadingOverlay };
diff --git a/packages/x-data-grid/src/components/base/GridOverlays.tsx b/packages/x-data-grid/src/components/base/GridOverlays.tsx
index 2e214bc219ff..169763a4633c 100644
--- a/packages/x-data-grid/src/components/base/GridOverlays.tsx
+++ b/packages/x-data-grid/src/components/base/GridOverlays.tsx
@@ -4,40 +4,47 @@ import { styled } from '@mui/system';
import { unstable_composeClasses as composeClasses } from '@mui/utils';
import clsx from 'clsx';
import { useGridSelector } from '../../hooks/utils/useGridSelector';
-import { gridExpandedRowCountSelector } from '../../hooks/features/filter/gridFilterSelector';
-import {
- gridRowCountSelector,
- gridRowsLoadingSelector,
-} from '../../hooks/features/rows/gridRowsSelector';
import { gridDimensionsSelector } from '../../hooks/features/dimensions';
+import { GridOverlayType } from '../../hooks/features/overlays/useGridOverlays';
import { useGridApiContext } from '../../hooks/utils/useGridApiContext';
import { useGridRootProps } from '../../hooks/utils/useGridRootProps';
import { useGridVisibleRows } from '../../hooks/utils/useGridVisibleRows';
import { getMinimalContentHeight } from '../../hooks/features/rows/gridRowsUtils';
import { DataGridProcessedProps } from '../../models/props/DataGridProps';
import { getDataGridUtilityClass } from '../../constants/gridClasses';
+import { GridLoadingOverlayVariant } from '../GridLoadingOverlay';
+
+interface GridOverlaysProps {
+ overlayType: GridOverlayType;
+ loadingOverlayVariant: GridLoadingOverlayVariant | null;
+}
const GridOverlayWrapperRoot = styled('div', {
name: 'MuiDataGrid',
slot: 'OverlayWrapper',
- shouldForwardProp: (prop) => prop !== 'overlayType',
+ shouldForwardProp: (prop) => prop !== 'overlayType' && prop !== 'loadingOverlayVariant',
overridesResolver: (props, styles) => styles.overlayWrapper,
-})<{ overlayType: 'loadingOverlay' | string }>(({ overlayType }) => ({
- position: 'sticky', // To stay in place while scrolling
- top: 'var(--DataGrid-headersTotalHeight)',
- left: 0,
- width: 0, // To stay above the content instead of shifting it down
- height: 0, // To stay above the content instead of shifting it down
- zIndex:
- overlayType === 'loadingOverlay'
- ? 5 // Should be above pinned columns, pinned rows, and detail panel
- : 4, // Should be above pinned columns and detail panel
-}));
+})(({ overlayType, loadingOverlayVariant }) =>
+ // Skeleton overlay should flow with the scroll container and not be sticky
+ loadingOverlayVariant !== 'skeleton'
+ ? {
+ position: 'sticky', // To stay in place while scrolling
+ top: 'var(--DataGrid-headersTotalHeight)',
+ left: 0,
+ width: 0, // To stay above the content instead of shifting it down
+ height: 0, // To stay above the content instead of shifting it down
+ zIndex:
+ overlayType === 'loadingOverlay'
+ ? 5 // Should be above pinned columns, pinned rows, and detail panel
+ : 4, // Should be above pinned columns and detail panel
+ }
+ : {},
+);
const GridOverlayWrapperInner = styled('div', {
name: 'MuiDataGrid',
slot: 'OverlayWrapperInner',
- shouldForwardProp: (prop) => prop !== 'overlayType',
+ shouldForwardProp: (prop) => prop !== 'overlayType' && prop !== 'loadingOverlayVariant',
overridesResolver: (props, styles) => styles.overlayWrapperInner,
})({});
@@ -54,7 +61,7 @@ const useUtilityClasses = (ownerState: OwnerState) => {
return composeClasses(slots, getDataGridUtilityClass, classes);
};
-function GridOverlayWrapper(props: React.PropsWithChildren<{ overlayType: string }>) {
+function GridOverlayWrapper(props: React.PropsWithChildren) {
const apiRef = useGridApiContext();
const rootProps = useGridRootProps();
const currentPage = useGridVisibleRows(apiRef, rootProps);
@@ -62,7 +69,8 @@ function GridOverlayWrapper(props: React.PropsWithChildren<{ overlayType: string
let height: React.CSSProperties['height'] =
dimensions.viewportOuterSize.height -
- dimensions.headersTotalHeight -
+ dimensions.topContainerHeight -
+ dimensions.bottomContainerHeight -
(dimensions.hasScrollX ? dimensions.scrollbarSize : 0);
if ((rootProps.autoHeight && currentPage.rows.length === 0) || height === 0) {
@@ -72,7 +80,7 @@ function GridOverlayWrapper(props: React.PropsWithChildren<{ overlayType: string
const classes = useUtilityClasses({ ...props, classes: rootProps.classes });
return (
-
+
0 && visibleRowCount === 0;
-
- let overlay: React.JSX.Element | null = null;
- let overlayType = '';
-
- if (showNoRowsOverlay) {
- overlay = ;
- overlayType = 'noRowsOverlay';
- }
-
- if (showNoResultsOverlay) {
- overlay = ;
- overlayType = 'noResultsOverlay';
- }
-
- if (loading) {
- overlay = ;
- overlayType = 'loadingOverlay';
- }
-
- if (overlay === null) {
+ if (!overlayType) {
return null;
}
- return {overlay} ;
+ const Overlay = rootProps.slots?.[overlayType];
+ const overlayProps = rootProps.slotProps?.[overlayType];
+
+ return (
+
+
+
+ );
}
diff --git a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx
index fef4188a67f0..28a25539b706 100644
--- a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx
+++ b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx
@@ -5,45 +5,101 @@ import {
unstable_composeClasses as composeClasses,
unstable_capitalize as capitalize,
} from '@mui/utils';
+import clsx from 'clsx';
import { fastMemo } from '../../utils/fastMemo';
-import { randomNumberBetween } from '../../utils/utils';
+import { createRandomNumberGenerator } from '../../utils/utils';
import { useGridRootProps } from '../../hooks/utils/useGridRootProps';
import { getDataGridUtilityClass } from '../../constants/gridClasses';
import { DataGridProcessedProps } from '../../models/props/DataGridProps';
+import { GridColType } from '../../models';
-const randomWidth = randomNumberBetween(10000, 20, 80);
+const CIRCULAR_CONTENT_SIZE = '1.3em';
-export interface GridSkeletonCellProps {
- width: number;
- height: number | 'auto';
- field: string;
- align: string;
+const CONTENT_HEIGHT = '1.2em';
+
+const DEFAULT_CONTENT_WIDTH_RANGE = [40, 80] as const;
+
+const CONTENT_WIDTH_RANGE_BY_TYPE: Partial> = {
+ number: [40, 60],
+ string: [40, 80],
+ date: [40, 60],
+ dateTime: [60, 80],
+ singleSelect: [40, 80],
+} as const;
+
+export interface GridSkeletonCellProps extends React.HTMLAttributes {
+ type?: GridColType;
+ width?: number | string;
+ height?: number | 'auto';
+ field?: string;
+ align?: string;
+ /**
+ * If `true`, the cell will not display the skeleton but still reserve the cell space.
+ * @default false
+ */
+ empty?: boolean;
}
-type OwnerState = Pick & {
+type OwnerState = Pick & {
classes?: DataGridProcessedProps['classes'];
};
const useUtilityClasses = (ownerState: OwnerState) => {
- const { align, classes } = ownerState;
+ const { align, classes, empty } = ownerState;
const slots = {
- root: ['cell', 'cellSkeleton', `cell--text${capitalize(align)}`, 'withBorderColor'],
+ root: [
+ 'cell',
+ 'cellSkeleton',
+ `cell--text${align ? capitalize(align) : 'Left'}`,
+ empty && 'cellEmpty',
+ ],
};
return composeClasses(slots, getDataGridUtilityClass, classes);
};
-function GridSkeletonCell(props: React.HTMLAttributes & GridSkeletonCellProps) {
- const { field, align, width, height, ...other } = props;
+const randomNumberGenerator = createRandomNumberGenerator(12345);
+
+function GridSkeletonCell(props: GridSkeletonCellProps) {
+ const { field, type, align, width, height, empty = false, style, className, ...other } = props;
const rootProps = useGridRootProps();
- const ownerState = { classes: rootProps.classes, align };
+ const ownerState = { classes: rootProps.classes, align, empty };
const classes = useUtilityClasses(ownerState);
- const contentWidth = Math.round(randomWidth());
+
+ // Memo prevents the non-circular skeleton widths changing to random widths on every render
+ const skeletonProps = React.useMemo(() => {
+ const isCircularContent = type === 'boolean' || type === 'actions';
+
+ if (isCircularContent) {
+ return {
+ variant: 'circular',
+ width: CIRCULAR_CONTENT_SIZE,
+ height: CIRCULAR_CONTENT_SIZE,
+ } as const;
+ }
+
+ // The width of the skeleton is a random number between the min and max values
+ // The min and max values are determined by the type of the column
+ const [min, max] = type
+ ? CONTENT_WIDTH_RANGE_BY_TYPE[type] ?? DEFAULT_CONTENT_WIDTH_RANGE
+ : DEFAULT_CONTENT_WIDTH_RANGE;
+
+ return {
+ variant: 'text',
+ width: `${Math.round(randomNumberGenerator(min, max))}%`,
+ height: CONTENT_HEIGHT,
+ } as const;
+ }, [type]);
return (
-
-
+
+ {!empty && }
);
}
@@ -53,10 +109,25 @@ GridSkeletonCell.propTypes = {
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "pnpm proptypes" |
// ----------------------------------------------------------------------
- align: PropTypes.string.isRequired,
- field: PropTypes.string.isRequired,
- height: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]).isRequired,
- width: PropTypes.number.isRequired,
+ align: PropTypes.string,
+ /**
+ * If `true`, the cell will not display the skeleton but still reserve the cell space.
+ * @default false
+ */
+ empty: PropTypes.bool,
+ field: PropTypes.string,
+ height: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]),
+ type: PropTypes.oneOf([
+ 'actions',
+ 'boolean',
+ 'custom',
+ 'date',
+ 'dateTime',
+ 'number',
+ 'singleSelect',
+ 'string',
+ ]),
+ width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
} as any;
const Memoized = fastMemo(GridSkeletonCell);
diff --git a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx
index 2822705bf090..920c197c6da7 100644
--- a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx
+++ b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx
@@ -6,7 +6,6 @@ import { fastMemo } from '../../utils/fastMemo';
import { GridStateColDef } from '../../models/colDef/gridColDef';
import { GridSortDirection } from '../../models/gridSortModel';
import { useGridPrivateApiContext } from '../../hooks/utils/useGridPrivateApiContext';
-import { GridColumnHeaderSortIcon } from './GridColumnHeaderSortIcon';
import { GridColumnHeaderSeparatorProps } from './GridColumnHeaderSeparator';
import { ColumnHeaderMenuIcon } from './ColumnHeaderMenuIcon';
import { GridColumnHeaderMenu } from '../menu/columnMenu/GridColumnHeaderMenu';
@@ -248,11 +247,13 @@ function GridColumnHeaderItem(props: GridColumnHeaderItemProps) {
)}
{showSortIcon && (
-
)}
diff --git a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderSortIcon.tsx b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderSortIcon.tsx
index d9e069df341a..c10c6225402f 100644
--- a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderSortIcon.tsx
+++ b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderSortIcon.tsx
@@ -11,6 +11,7 @@ import { DataGridProcessedProps } from '../../models/props/DataGridProps';
import { GridIconButtonContainer } from './GridIconButtonContainer';
export interface GridColumnHeaderSortIconProps {
+ field: string;
direction: GridSortDirection;
index: number | undefined;
sortingOrder: readonly GridSortDirection[];
@@ -51,7 +52,7 @@ function getIcon(
}
function GridColumnHeaderSortIconRaw(props: GridColumnHeaderSortIconProps) {
- const { direction, index, sortingOrder, disabled } = props;
+ const { direction, index, sortingOrder, disabled, ...other } = props;
const apiRef = useGridApiContext();
const rootProps = useGridRootProps();
const ownerState = { ...props, classes: rootProps.classes };
@@ -70,6 +71,7 @@ function GridColumnHeaderSortIconRaw(props: GridColumnHeaderSortIconProps) {
size="small"
disabled={disabled}
{...rootProps.slotProps?.baseIconButton}
+ {...other}
>
{iconElement}
@@ -78,7 +80,7 @@ function GridColumnHeaderSortIconRaw(props: GridColumnHeaderSortIconProps) {
return (
{index != null && (
-
+
{iconButton}
)}
@@ -97,6 +99,7 @@ GridColumnHeaderSortIconRaw.propTypes = {
// ----------------------------------------------------------------------
direction: PropTypes.oneOf(['asc', 'desc']),
disabled: PropTypes.bool,
+ field: PropTypes.string.isRequired,
index: PropTypes.number,
sortingOrder: PropTypes.arrayOf(PropTypes.oneOf(['asc', 'desc'])).isRequired,
} as any;
diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts
index 0d5e88de51f6..bdfd79712cd3 100644
--- a/packages/x-data-grid/src/components/containers/GridRootStyles.ts
+++ b/packages/x-data-grid/src/components/containers/GridRootStyles.ts
@@ -115,6 +115,9 @@ export const GridRootStyles = styled('div', {
{ [`& .${c.withBorderColor}`]: styles.withBorderColor },
{ [`& .${c.treeDataGroupingCell}`]: styles.treeDataGroupingCell },
{ [`& .${c.treeDataGroupingCellToggle}`]: styles.treeDataGroupingCellToggle },
+ {
+ [`& .${c.treeDataGroupingCellLoadingContainer}`]: styles.treeDataGroupingCellLoadingContainer,
+ },
{ [`& .${c.detailPanelToggleCell}`]: styles.detailPanelToggleCell },
{
[`& .${c['detailPanelToggleCell--expanded']}`]: styles['detailPanelToggleCell--expanded'],
@@ -145,7 +148,7 @@ export const GridRootStyles = styled('div', {
const selectedHoverBackground = t.vars
? `rgba(${t.vars.palette.primary.mainChannel} / calc(
- ${t.vars.palette.action.selectedOpacity} +
+ ${t.vars.palette.action.selectedOpacity} +
${t.vars.palette.action.hoverOpacity}
))`
: alpha(
@@ -433,6 +436,9 @@ export const GridRootStyles = styled('div', {
backgroundColor: 'transparent',
},
},
+ [`&.${c.rowSkeleton}:hover`]: {
+ backgroundColor: 'transparent',
+ },
'&.Mui-selected': selectedStyles,
},
[`& .${c['container--top']}, & .${c['container--bottom']}`]: {
@@ -620,6 +626,12 @@ export const GridRootStyles = styled('div', {
alignSelf: 'stretch',
marginRight: t.spacing(2),
},
+ [`& .${c.treeDataGroupingCellLoadingContainer}`]: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '100%',
+ },
[`& .${c.groupingCriteriaCell}`]: {
display: 'flex',
alignItems: 'center',
@@ -636,7 +648,7 @@ export const GridRootStyles = styled('div', {
minWidth: 'calc(var(--DataGrid-hasScrollY) * var(--DataGrid-scrollbarSize))',
alignSelf: 'stretch',
[`&.${c['scrollbarFiller--borderTop']}`]: {
- borderTop: '1px solid var(--DataGrid-rowBorderColor)',
+ borderTop: '1px solid var(--rowBorderColor)',
},
[`&.${c['scrollbarFiller--pinnedRight']}`]: {
backgroundColor: 'var(--DataGrid-pinnedBackground)',
@@ -651,6 +663,13 @@ export const GridRootStyles = styled('div', {
[`& .${c['filler--borderTop']}`]: {
borderTop: '1px solid var(--DataGrid-rowBorderColor)',
},
+
+ /* Hide grid rows and vertical scrollbar when skeleton overlay is visible */
+ [`& .${c['main--hasSkeletonLoadingOverlay']}`]: {
+ [`& .${c.virtualScrollerContent}, & .${c['scrollbar--vertical']}, & .${c.pinnedRows}`]: {
+ display: 'none',
+ },
+ },
};
return gridStyle;
diff --git a/packages/x-data-grid/src/components/panel/GridPanel.test.tsx b/packages/x-data-grid/src/components/panel/GridPanel.test.tsx
index 225f13469abf..4e82d0de7ddd 100644
--- a/packages/x-data-grid/src/components/panel/GridPanel.test.tsx
+++ b/packages/x-data-grid/src/components/panel/GridPanel.test.tsx
@@ -33,18 +33,13 @@ describe(' ', () => {
classes: classes as any,
inheritComponent: Popper,
muiName: 'MuiGridPanel',
- render: (node: React.ReactElement) => render({node} ),
- wrapMount:
- (baseMount: (node: React.ReactElement) => import('enzyme').ReactWrapper) =>
- (node: React.ReactNode) => {
- const wrapper = baseMount(
-
-
- {node}
- ,
- );
- return wrapper.find('span').childAt(0);
- },
+ render: (node: React.ReactElement) =>
+ render(
+
+
+ {node}
+ ,
+ ),
refInstanceof: window.HTMLDivElement,
only: ['mergeClassName', 'propsSpread', 'refForwarding', 'rootClass'],
}));
diff --git a/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx b/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx
index aa17ed93be6d..5afc96f930fc 100644
--- a/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx
+++ b/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx
@@ -9,7 +9,8 @@ import { getDataGridUtilityClass } from '../../constants/gridClasses';
import { DataGridProcessedProps } from '../../models/props/DataGridProps';
import { GridDimensions, gridDimensionsSelector } from '../../hooks/features/dimensions';
import { useGridVirtualScroller } from '../../hooks/features/virtualization/useGridVirtualScroller';
-import { GridOverlays } from '../base/GridOverlays';
+import { useGridOverlays } from '../../hooks/features/overlays/useGridOverlays';
+import { GridOverlays as Overlays } from '../base/GridOverlays';
import { GridHeaders } from '../GridHeaders';
import { GridMainContainer as Container } from './GridMainContainer';
import { GridTopContainer as TopContainer } from './GridTopContainer';
@@ -18,14 +19,23 @@ import { GridVirtualScrollerContent as Content } from './GridVirtualScrollerCont
import { GridVirtualScrollerFiller as SpaceFiller } from './GridVirtualScrollerFiller';
import { GridVirtualScrollerRenderZone as RenderZone } from './GridVirtualScrollerRenderZone';
import { GridVirtualScrollbar as Scrollbar } from './GridVirtualScrollbar';
+import { GridLoadingOverlayVariant } from '../GridLoadingOverlay';
type OwnerState = DataGridProcessedProps;
-const useUtilityClasses = (ownerState: OwnerState, dimensions: GridDimensions) => {
+const useUtilityClasses = (
+ ownerState: OwnerState,
+ dimensions: GridDimensions,
+ loadingOverlayVariant: GridLoadingOverlayVariant | null,
+) => {
const { classes } = ownerState;
const slots = {
- root: ['main', dimensions.rightPinnedWidth > 0 && 'main--hasPinnedRight'],
+ root: [
+ 'main',
+ dimensions.rightPinnedWidth > 0 && 'main--hasPinnedRight',
+ loadingOverlayVariant === 'skeleton' && 'main--hasSkeletonLoadingOverlay',
+ ],
scroller: ['virtualScroller'],
};
@@ -61,7 +71,8 @@ function GridVirtualScroller(props: GridVirtualScrollerProps) {
const apiRef = useGridApiContext();
const rootProps = useGridRootProps();
const dimensions = useGridSelector(apiRef, gridDimensionsSelector);
- const classes = useUtilityClasses(rootProps, dimensions);
+ const overlaysProps = useGridOverlays();
+ const classes = useUtilityClasses(rootProps, dimensions, overlaysProps.loadingOverlayVariant);
const virtualScroller = useGridVirtualScroller();
const {
@@ -86,7 +97,7 @@ function GridVirtualScroller(props: GridVirtualScrollerProps) {
-
+
@@ -95,7 +106,7 @@ function GridVirtualScroller(props: GridVirtualScrollerProps) {
- {rows.length > 0 && }
+
diff --git a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerFiller.tsx b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerFiller.tsx
index eeaafcdd67ad..718a449a6ee5 100644
--- a/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerFiller.tsx
+++ b/packages/x-data-grid/src/components/virtualization/GridVirtualScrollerFiller.tsx
@@ -17,24 +17,29 @@ const Pinned = styled('div')({
position: 'sticky',
height: '100%',
boxSizing: 'border-box',
- borderTop: '1px solid var(--DataGrid-rowBorderColor)',
+ borderTop: '1px solid var(--rowBorderColor)',
backgroundColor: 'var(--DataGrid-pinnedBackground)',
});
const PinnedLeft = styled(Pinned)({
left: 0,
- borderRight: '1px solid var(--DataGrid-rowBorderColor)',
+ borderRight: '1px solid var(--rowBorderColor)',
});
const PinnedRight = styled(Pinned)({
right: 0,
- borderLeft: '1px solid var(--DataGrid-rowBorderColor)',
+ borderLeft: '1px solid var(--rowBorderColor)',
});
const Main = styled('div')({
flexGrow: 1,
- borderTop: '1px solid var(--DataGrid-rowBorderColor)',
+ borderTop: '1px solid var(--rowBorderColor)',
});
-function GridVirtualScrollerFiller() {
+type Props = {
+ /** The number of rows */
+ rowsLength: number;
+};
+
+function GridVirtualScrollerFiller({ rowsLength }: Props) {
const apiRef = useGridApiContext();
const {
viewportOuterSize,
@@ -54,7 +59,16 @@ function GridVirtualScrollerFiller() {
}
return (
-
+
{leftPinnedWidth > 0 && (
('MuiDataGrid', [
'iconSeparator',
'main',
'main--hasPinnedRight',
+ 'main--hasSkeletonLoadingOverlay',
'menu',
'menuIcon',
'menuIconButton',
@@ -727,6 +742,7 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [
'rowReorderCellContainer',
'rowReorderCell',
'rowReorderCell--draggable',
+ 'rowSkeleton',
'scrollArea--left',
'scrollArea--right',
'scrollArea',
@@ -754,6 +770,7 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [
'columnHeader--withLeftBorder',
'treeDataGroupingCell',
'treeDataGroupingCellToggle',
+ 'treeDataGroupingCellLoadingContainer',
'groupingCriteriaCell',
'groupingCriteriaCellToggle',
'pinnedRows',
diff --git a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts
index 9727b8b19c03..6d423b29d931 100644
--- a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts
+++ b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts
@@ -86,6 +86,7 @@ export function useGridDimensions(
const logger = useGridLogger(apiRef, 'useResizeContainer');
const errorShown = React.useRef(false);
const rootDimensionsRef = React.useRef(EMPTY_SIZE);
+ const dimensionsState = useGridSelector(apiRef, gridDimensionsSelector);
const rowsMeta = useGridSelector(apiRef, gridRowsMetaSelector);
const pinnedColumns = useGridSelector(apiRef, gridVisiblePinnedColumnDefinitionsSelector);
const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector);
@@ -302,26 +303,25 @@ export function useGridDimensions(
}, [apiRef, savedSize, updateDimensions]);
const root = apiRef.current.rootElementRef.current;
- const dimensions = apiRef.current.state.dimensions;
useEnhancedEffect(() => {
if (!root) {
return;
}
const set = (k: string, v: string) => root.style.setProperty(k, v);
- set('--DataGrid-width', `${dimensions.viewportOuterSize.width}px`);
- set('--DataGrid-hasScrollX', `${Number(dimensions.hasScrollX)}`);
- set('--DataGrid-hasScrollY', `${Number(dimensions.hasScrollY)}`);
- set('--DataGrid-scrollbarSize', `${dimensions.scrollbarSize}px`);
- set('--DataGrid-rowWidth', `${dimensions.rowWidth}px`);
- set('--DataGrid-columnsTotalWidth', `${dimensions.columnsTotalWidth}px`);
- set('--DataGrid-leftPinnedWidth', `${dimensions.leftPinnedWidth}px`);
- set('--DataGrid-rightPinnedWidth', `${dimensions.rightPinnedWidth}px`);
- set('--DataGrid-headerHeight', `${dimensions.headerHeight}px`);
- set('--DataGrid-headersTotalHeight', `${dimensions.headersTotalHeight}px`);
- set('--DataGrid-topContainerHeight', `${dimensions.topContainerHeight}px`);
- set('--DataGrid-bottomContainerHeight', `${dimensions.bottomContainerHeight}px`);
- set('--height', `${dimensions.rowHeight}px`);
- }, [root, dimensions]);
+ set('--DataGrid-width', `${dimensionsState.viewportOuterSize.width}px`);
+ set('--DataGrid-hasScrollX', `${Number(dimensionsState.hasScrollX)}`);
+ set('--DataGrid-hasScrollY', `${Number(dimensionsState.hasScrollY)}`);
+ set('--DataGrid-scrollbarSize', `${dimensionsState.scrollbarSize}px`);
+ set('--DataGrid-rowWidth', `${dimensionsState.rowWidth}px`);
+ set('--DataGrid-columnsTotalWidth', `${dimensionsState.columnsTotalWidth}px`);
+ set('--DataGrid-leftPinnedWidth', `${dimensionsState.leftPinnedWidth}px`);
+ set('--DataGrid-rightPinnedWidth', `${dimensionsState.rightPinnedWidth}px`);
+ set('--DataGrid-headerHeight', `${dimensionsState.headerHeight}px`);
+ set('--DataGrid-headersTotalHeight', `${dimensionsState.headersTotalHeight}px`);
+ set('--DataGrid-topContainerHeight', `${dimensionsState.topContainerHeight}px`);
+ set('--DataGrid-bottomContainerHeight', `${dimensionsState.bottomContainerHeight}px`);
+ set('--height', `${dimensionsState.rowHeight}px`);
+ }, [root, dimensionsState]);
const isFirstSizing = React.useRef(true);
const handleResize = React.useCallback>(
diff --git a/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx b/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx
index 5ba08b587cdc..3421133554eb 100644
--- a/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx
+++ b/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx
@@ -177,15 +177,18 @@ export const useGridPrintExport = (
// The height above does not include grid border width, so we need to exclude it
gridClone.style.boxSizing = 'content-box';
- // the footer is always being placed at the bottom of the page as if all rows are exported
- // so if getRowsToExport is being used to only export a subset of rows then we need to
- // adjust the footer position to be correctly placed at the bottom of the grid
- const gridFooterElement: HTMLElement | null = gridClone.querySelector(
- `.${gridClasses.footerContainer}`,
- );
- gridFooterElement!.style.position = 'absolute';
- gridFooterElement!.style.width = '100%';
- gridFooterElement!.style.top = `${computedTotalHeight - gridFooterElementHeight}px`;
+ if (!normalizeOptions.hideFooter) {
+ // the footer is always being placed at the bottom of the page as if all rows are exported
+ // so if getRowsToExport is being used to only export a subset of rows then we need to
+ // adjust the footer position to be correctly placed at the bottom of the grid
+ const gridFooterElement: HTMLElement | null = gridClone.querySelector(
+ `.${gridClasses.footerContainer}`,
+ )!;
+
+ gridFooterElement.style.position = 'absolute';
+ gridFooterElement.style.width = '100%';
+ gridFooterElement.style.top = `${computedTotalHeight - gridFooterElementHeight}px`;
+ }
// printDoc.body.appendChild(gridClone); should be enough but a clone isolation bug in Safari
// prevents us to do it
diff --git a/packages/x-data-grid/src/hooks/features/filter/gridFilterSelector.ts b/packages/x-data-grid/src/hooks/features/filter/gridFilterSelector.ts
index 40fa6d9c6a3b..9d376a2321f2 100644
--- a/packages/x-data-grid/src/hooks/features/filter/gridFilterSelector.ts
+++ b/packages/x-data-grid/src/hooks/features/filter/gridFilterSelector.ts
@@ -131,6 +131,26 @@ export const gridFilteredTopLevelRowCountSelector = createSelector(
(visibleSortedTopLevelRows) => visibleSortedTopLevelRows.length,
);
+/**
+ * Get the amount of rows accessible after the filtering process.
+ * Includes top level and descendant rows.
+ * @category Filtering
+ */
+export const gridFilteredRowCountSelector = createSelector(
+ gridFilteredSortedRowEntriesSelector,
+ (filteredSortedRowEntries) => filteredSortedRowEntries.length,
+);
+
+/**
+ * Get the amount of descendant rows accessible after the filtering process.
+ * @category Filtering
+ */
+export const gridFilteredDescendantRowCountSelector = createSelector(
+ gridFilteredRowCountSelector,
+ gridFilteredTopLevelRowCountSelector,
+ (totalRowCount, topLevelRowCount) => totalRowCount - topLevelRowCount,
+);
+
/**
* @category Filtering
* @ignore - do not document.
diff --git a/packages/x-data-grid/src/hooks/features/overlays/useGridOverlays.ts b/packages/x-data-grid/src/hooks/features/overlays/useGridOverlays.ts
new file mode 100644
index 000000000000..f3f9f3998423
--- /dev/null
+++ b/packages/x-data-grid/src/hooks/features/overlays/useGridOverlays.ts
@@ -0,0 +1,47 @@
+import { useGridSelector } from '../../utils';
+import { useGridApiContext } from '../../utils/useGridApiContext';
+import { useGridRootProps } from '../../utils/useGridRootProps';
+import { gridExpandedRowCountSelector } from '../filter';
+import { gridRowCountSelector, gridRowsLoadingSelector } from '../rows';
+import { GridLoadingOverlayVariant } from '../../../components/GridLoadingOverlay';
+import { GridSlotsComponent } from '../../../models/gridSlotsComponent';
+
+export type GridOverlayType =
+ | keyof Pick
+ | null;
+
+/**
+ * Uses the grid state to determine which overlay to display.
+ * Returns the active overlay type and the active loading overlay variant.
+ */
+export const useGridOverlays = () => {
+ const apiRef = useGridApiContext();
+ const rootProps = useGridRootProps();
+
+ const totalRowCount = useGridSelector(apiRef, gridRowCountSelector);
+ const visibleRowCount = useGridSelector(apiRef, gridExpandedRowCountSelector);
+ const noRows = totalRowCount === 0;
+ const loading = useGridSelector(apiRef, gridRowsLoadingSelector);
+
+ const showNoRowsOverlay = !loading && noRows;
+ const showNoResultsOverlay = !loading && totalRowCount > 0 && visibleRowCount === 0;
+
+ let overlayType: GridOverlayType = null;
+ let loadingOverlayVariant: GridLoadingOverlayVariant | null = null;
+
+ if (showNoRowsOverlay) {
+ overlayType = 'noRowsOverlay';
+ }
+
+ if (showNoResultsOverlay) {
+ overlayType = 'noResultsOverlay';
+ }
+
+ if (loading) {
+ overlayType = 'loadingOverlay';
+ loadingOverlayVariant =
+ rootProps.slotProps?.loadingOverlay?.[noRows ? 'noRowsVariant' : 'variant'] || null;
+ }
+
+ return { overlayType, loadingOverlayVariant };
+};
diff --git a/packages/x-data-grid/src/hooks/features/pagination/index.ts b/packages/x-data-grid/src/hooks/features/pagination/index.ts
index f47e042c72ee..4437fa668eb6 100644
--- a/packages/x-data-grid/src/hooks/features/pagination/index.ts
+++ b/packages/x-data-grid/src/hooks/features/pagination/index.ts
@@ -1,5 +1,7 @@
export * from './gridPaginationSelector';
export type {
+ GridPaginationModelApi,
+ GridPaginationRowCountApi,
GridPaginationApi,
GridPaginationState,
GridPaginationInitialState,
diff --git a/packages/x-data-grid/src/hooks/features/rows/gridRowsInterfaces.ts b/packages/x-data-grid/src/hooks/features/rows/gridRowsInterfaces.ts
index 34562ff2231f..fc999812ee92 100644
--- a/packages/x-data-grid/src/hooks/features/rows/gridRowsInterfaces.ts
+++ b/packages/x-data-grid/src/hooks/features/rows/gridRowsInterfaces.ts
@@ -50,7 +50,7 @@ export interface GridRowsState {
treeDepths: GridTreeDepths;
dataRowIds: GridRowId[];
/**
- * Matches the value of the `loading` prop.
+ * The loading status of the rows.
*/
loading?: boolean;
/**
@@ -70,6 +70,12 @@ export interface GridRowsState {
additionalRowGroups?: {
pinnedRows?: GridPinnedRowsState;
};
+ /**
+ * Contains some values of type `GridRowId` that have been requested to be fetched
+ * either by `defaultGroupingExpansionDepth` or `isGroupExpandedByDefault` props.
+ * Applicable with server-side grouped data and `unstable_dataSource` only.
+ */
+ groupsToFetch?: GridRowId[];
}
export interface GridRowTreeCreationParams {
@@ -78,6 +84,7 @@ export interface GridRowTreeCreationParams {
updates: GridRowsPartialUpdates | GridRowsFullUpdate;
dataRowIdToIdLookup: GridRowIdToIdLookup;
dataRowIdToModelLookup: GridRowIdToModelLookup;
+ previousGroupsToFetch?: GridRowId[];
}
export type GridRowTreeUpdateGroupAction = 'removeChildren' | 'insertChildren' | 'modifyChildren';
@@ -93,7 +100,7 @@ export type GridRowTreeUpdatedGroupsManager = {
export type GridRowTreeCreationValue = Pick<
GridRowsState,
- 'groupingName' | 'tree' | 'treeDepths' | 'dataRowIds'
+ 'groupingName' | 'tree' | 'treeDepths' | 'dataRowIds' | 'groupsToFetch'
> & {
updatedGroupsManager?: GridRowTreeUpdatedGroupsManager;
};
@@ -128,6 +135,7 @@ export interface GridRowsPartialUpdates {
type: 'partial';
actions: { [action in GridRowsPartialUpdateAction]: GridRowId[] };
idToActionLookup: { [id: GridRowId]: GridRowsPartialUpdateAction | undefined };
+ groupKeys?: string[];
}
export interface GridPinnedRowsState {
diff --git a/packages/x-data-grid/src/hooks/features/rows/gridRowsSelector.ts b/packages/x-data-grid/src/hooks/features/rows/gridRowsSelector.ts
index 6dd24b5256bc..c0f3614d0ad3 100644
--- a/packages/x-data-grid/src/hooks/features/rows/gridRowsSelector.ts
+++ b/packages/x-data-grid/src/hooks/features/rows/gridRowsSelector.ts
@@ -31,6 +31,11 @@ export const gridRowsDataRowIdToIdLookupSelector = createSelector(
export const gridRowTreeSelector = createSelector(gridRowsStateSelector, (rows) => rows.tree);
+export const gridRowGroupsToFetchSelector = createSelector(
+ gridRowsStateSelector,
+ (rows) => rows.groupsToFetch,
+);
+
export const gridRowGroupingNameSelector = createSelector(
gridRowsStateSelector,
(rows) => rows.groupingName,
diff --git a/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts b/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts
index a2704b611c8e..83b643c84dd4 100644
--- a/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts
+++ b/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts
@@ -132,7 +132,11 @@ export const getRowsStateFromCache = ({
loadingProp,
previousTree,
previousTreeDepths,
-}: Pick & {
+ previousGroupsToFetch,
+}: Pick<
+ GridRowTreeCreationParams,
+ 'previousTree' | 'previousTreeDepths' | 'previousGroupsToFetch'
+> & {
apiRef: React.MutableRefObject;
rowCountProp: number | undefined;
loadingProp: boolean | undefined;
@@ -145,12 +149,14 @@ export const getRowsStateFromCache = ({
treeDepths: unProcessedTreeDepths,
dataRowIds: unProcessedDataRowIds,
groupingName,
+ groupsToFetch = [],
} = apiRef.current.applyStrategyProcessor('rowTreeCreation', {
previousTree,
previousTreeDepths,
updates: cache.updates,
dataRowIdToIdLookup: cache.dataRowIdToIdLookup,
dataRowIdToModelLookup: cache.dataRowIdToModelLookup,
+ previousGroupsToFetch,
});
// 2. Apply the "hydrateRows" pipe-processing.
@@ -182,6 +188,7 @@ export const getRowsStateFromCache = ({
}),
groupingName,
loading: loadingProp,
+ groupsToFetch,
};
};
@@ -230,10 +237,12 @@ export const updateCacheWithNewRows = ({
previousCache,
getRowId,
updates,
+ groupKeys,
}: {
previousCache: GridRowsInternalCache;
getRowId: DataGridProcessedProps['getRowId'];
updates: GridRowModelUpdate[];
+ groupKeys?: string[];
}): GridRowsInternalCache => {
if (previousCache.updates.type === 'full') {
throw new Error(
@@ -267,6 +276,7 @@ export const updateCacheWithNewRows = ({
remove: [...(previousCache.updates.actions.remove ?? [])],
},
idToActionLookup: { ...previousCache.updates.idToActionLookup },
+ groupKeys,
};
const dataRowIdToModelLookup = { ...previousCache.dataRowIdToModelLookup };
const dataRowIdToIdLookup = { ...previousCache.dataRowIdToIdLookup };
@@ -393,3 +403,35 @@ export function getMinimalContentHeight(apiRef: React.MutableRefObject,
+ updates: GridRowModelUpdate[],
+ getRowId: DataGridProcessedProps['getRowId'],
+) {
+ const nonPinnedRowsUpdates: GridRowModelUpdate[] = [];
+
+ updates.forEach((update) => {
+ const id = getRowIdFromRowModel(
+ update,
+ getRowId,
+ 'A row was provided without id when calling updateRows():',
+ );
+
+ const rowNode = apiRef.current.getRowNode(id);
+ if (rowNode?.type === 'pinnedRow') {
+ // @ts-ignore because otherwise `release:build` doesn't work
+ const pinnedRowsCache = apiRef.current.caches.pinnedRows;
+ const prevModel = pinnedRowsCache.idLookup[id];
+ if (prevModel) {
+ pinnedRowsCache.idLookup[id] = {
+ ...prevModel,
+ ...update,
+ };
+ }
+ } else {
+ nonPinnedRowsUpdates.push(update);
+ }
+ });
+ return nonPinnedRowsUpdates;
+}
diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts
index aba863dad31c..2737b07ce94c 100644
--- a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts
+++ b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts
@@ -2,13 +2,8 @@ import * as React from 'react';
import { GridEventListener } from '../../../models/events';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
-import { GridRowApi, GridRowProApi } from '../../../models/api/gridRowApi';
-import {
- GridRowId,
- GridGroupNode,
- GridLeafNode,
- GridRowModelUpdate,
-} from '../../../models/gridRows';
+import { GridRowApi, GridRowProApi, GridRowProPrivateApi } from '../../../models/api/gridRowApi';
+import { GridRowId, GridGroupNode, GridLeafNode } from '../../../models/gridRows';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { useGridLogger } from '../../utils/useGridLogger';
import {
@@ -20,6 +15,7 @@ import {
gridDataRowIdsSelector,
gridRowsDataRowIdToIdLookupSelector,
gridRowMaximumTreeDepthSelector,
+ gridRowGroupsToFetchSelector,
} from './gridRowsSelector';
import { useTimeout } from '../../utils/useTimeout';
import { GridSignature, useGridApiEventHandler } from '../../utils/useGridApiEventHandler';
@@ -38,14 +34,16 @@ import {
updateCacheWithNewRows,
getTopLevelRowCount,
getRowIdFromRowModel,
+ computeRowsUpdates,
} from './gridRowsUtils';
import { useGridRegisterPipeApplier } from '../../core/pipeProcessing';
export const rowsStateInitializer: GridStateInitializer<
- Pick
+ Pick
> = (state, props, apiRef) => {
+ const isDataSourceAvailable = !!props.unstable_dataSource;
apiRef.current.caches.rows = createRowsInternalCache({
- rows: props.rows,
+ rows: isDataSourceAvailable ? [] : props.rows,
getRowId: props.getRowId,
loading: props.loading,
rowCount: props.rowCount,
@@ -56,7 +54,7 @@ export const rowsStateInitializer: GridStateInitializer<
rows: getRowsStateFromCache({
apiRef,
rowCountProp: props.rowCount,
- loadingProp: props.loading,
+ loadingProp: isDataSourceAvailable ? true : props.loading,
previousTree: null,
previousTreeDepths: null,
}),
@@ -146,6 +144,7 @@ export const useGridRows = (
loadingProp: props.loading,
previousTree: gridRowTreeSelector(apiRef),
previousTreeDepths: gridRowTreeDepthsSelector(apiRef),
+ previousGroupsToFetch: gridRowGroupsToFetchSelector(apiRef),
}),
}));
apiRef.current.publishEvent('rowsSet');
@@ -203,30 +202,7 @@ export const useGridRows = (
);
}
- const nonPinnedRowsUpdates: GridRowModelUpdate[] = [];
-
- updates.forEach((update) => {
- const id = getRowIdFromRowModel(
- update,
- props.getRowId,
- 'A row was provided without id when calling updateRows():',
- );
-
- const rowNode = apiRef.current.getRowNode(id);
- if (rowNode?.type === 'pinnedRow') {
- // @ts-ignore because otherwise `release:build` doesn't work
- const pinnedRowsCache = apiRef.current.caches.pinnedRows;
- const prevModel = pinnedRowsCache.idLookup[id];
- if (prevModel) {
- pinnedRowsCache.idLookup[id] = {
- ...prevModel,
- ...update,
- };
- }
- } else {
- nonPinnedRowsUpdates.push(update);
- }
- });
+ const nonPinnedRowsUpdates = computeRowsUpdates(apiRef, updates, props.getRowId);
const cache = updateCacheWithNewRows({
updates: nonPinnedRowsUpdates,
@@ -239,6 +215,37 @@ export const useGridRows = (
[props.signature, props.getRowId, throttledRowsChange, apiRef],
);
+ const updateServerRows = React.useCallback(
+ (updates, groupKeys) => {
+ const nonPinnedRowsUpdates = computeRowsUpdates(apiRef, updates, props.getRowId);
+
+ const cache = updateCacheWithNewRows({
+ updates: nonPinnedRowsUpdates,
+ getRowId: props.getRowId,
+ previousCache: apiRef.current.caches.rows,
+ groupKeys: groupKeys ?? [],
+ });
+
+ throttledRowsChange({ cache, throttle: false });
+ },
+ [props.getRowId, throttledRowsChange, apiRef],
+ );
+
+ const setLoading = React.useCallback(
+ (loading) => {
+ if (loading === props.loading) {
+ return;
+ }
+ logger.debug(`Setting loading to ${loading}`);
+ apiRef.current.setState((state) => ({
+ ...state,
+ rows: { ...state.rows, loading },
+ }));
+ apiRef.current.caches.rows.loadingPropBeforePartialUpdates = loading;
+ },
+ [props.loading, apiRef, logger],
+ );
+
const getRowModels = React.useCallback(() => {
const dataRows = gridDataRowIdsSelector(apiRef);
const idRowsLookup = gridRowsLookupSelector(apiRef);
@@ -468,6 +475,7 @@ export const useGridRows = (
const rowApi: GridRowApi = {
getRow,
+ setLoading,
getRowId,
getRowModels,
getRowsCount,
@@ -485,6 +493,10 @@ export const useGridRows = (
getRowGroupChildren,
};
+ const rowProPrivateApi: GridRowProPrivateApi = {
+ updateServerRows,
+ };
+
/**
* EVENTS
*/
@@ -585,6 +597,7 @@ export const useGridRows = (
rowProApi,
props.signature === GridSignature.DataGrid ? 'private' : 'public',
);
+ useGridApiMethod(apiRef, rowProPrivateApi, 'private');
// The effect do not track any value defined synchronously during the 1st render by hooks called after `useGridRows`
// As a consequence, the state generated by the 1st run of this useEffect will always be equal to the initialization one
@@ -637,7 +650,7 @@ export const useGridRows = (
}
}
- logger.debug(`Updating all rows, new length ${props.rows.length}`);
+ logger.debug(`Updating all rows, new length ${props.rows?.length}`);
throttledRowsChange({
cache: createRowsInternalCache({
rows: props.rows,
diff --git a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
index a68ba9bae8e1..334d65759aac 100644
--- a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
+++ b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx
@@ -6,12 +6,12 @@ import {
} from '@mui/utils';
import useLazyRef from '@mui/utils/useLazyRef';
import useTimeout from '@mui/utils/useTimeout';
+import { useResizeObserver } from '@mui/x-internals/useResizeObserver';
import { useTheme, Theme } from '@mui/material/styles';
import type { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { useGridPrivateApiContext } from '../../utils/useGridPrivateApiContext';
import { useGridRootProps } from '../../utils/useGridRootProps';
import { useGridSelector } from '../../utils/useGridSelector';
-import { useResizeObserver } from '../../utils/useResizeObserver';
import { useRunOnce } from '../../utils/useRunOnce';
import {
gridVisibleColumnDefinitionsSelector,
@@ -517,22 +517,13 @@ export const useGridVirtualScroller = () => {
);
const contentSize = React.useMemo(() => {
- // In cases where the columns exceed the available width,
- // the horizontal scrollbar should be shown even when there're no rows.
- // Keeping 1px as minimum height ensures that the scrollbar will visible if necessary.
- const height = Math.max(contentHeight, 1);
-
const size: React.CSSProperties = {
width: needsHorizontalScrollbar ? columnsTotalWidth : 'auto',
- height,
+ height: contentHeight,
};
- if (rootProps.autoHeight) {
- if (currentPage.rows.length === 0) {
- size.height = getMinimalContentHeight(apiRef); // Give room to show the overlay when there no rows.
- } else {
- size.height = contentHeight;
- }
+ if (rootProps.autoHeight && currentPage.rows.length === 0) {
+ size.height = getMinimalContentHeight(apiRef); // Give room to show the overlay when there no rows.
}
return size;
diff --git a/packages/x-data-grid/src/hooks/utils/index.ts b/packages/x-data-grid/src/hooks/utils/index.ts
index e609ea5fb0fd..642c9db07366 100644
--- a/packages/x-data-grid/src/hooks/utils/index.ts
+++ b/packages/x-data-grid/src/hooks/utils/index.ts
@@ -5,5 +5,4 @@ export { useGridSelector } from './useGridSelector';
export * from './useGridNativeEventListener';
export * from './useFirstRender';
export * from './useOnMount';
-export * from './useResizeObserver';
export * from './useRunOnce';
diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts
index 88b4d71bf80f..1387f50016d3 100644
--- a/packages/x-data-grid/src/internals/index.ts
+++ b/packages/x-data-grid/src/internals/index.ts
@@ -130,10 +130,11 @@ export { useGridInitializeState } from '../hooks/utils/useGridInitializeState';
export type { GridStateInitializer } from '../hooks/utils/useGridInitializeState';
export type * from '../models/props/DataGridProps';
-
+export type * from '../models/gridDataSource';
export { getColumnsToExport, defaultGetRowsToExport } from '../hooks/features/export/utils';
export * from '../utils/createControllablePromise';
export { createSelector, createSelectorMemoized } from '../utils/createSelector';
+export { gridRowGroupsToFetchSelector } from '../hooks/features/rows/gridRowsSelector';
export {
findParentElementFromClassName,
getActiveElement,
diff --git a/packages/x-data-grid/src/internals/utils/propValidation.ts b/packages/x-data-grid/src/internals/utils/propValidation.ts
index 6fa67ab6cc15..825db3991f71 100644
--- a/packages/x-data-grid/src/internals/utils/propValidation.ts
+++ b/packages/x-data-grid/src/internals/utils/propValidation.ts
@@ -35,6 +35,7 @@ export const propValidatorsDataGrid: PropValidator[] = [
(props) =>
(props.paginationMode === 'server' &&
props.rowCount == null &&
+ !props.unstable_dataSource &&
[
"MUI X: The `rowCount` prop must be passed using `paginationMode='server'`",
'For more detail, see http://mui.com/components/data-grid/pagination/#index-based-pagination',
diff --git a/packages/x-data-grid/src/models/api/gridApiCommon.ts b/packages/x-data-grid/src/models/api/gridApiCommon.ts
index 1e59d123c8a5..f5ba4a40faf7 100644
--- a/packages/x-data-grid/src/models/api/gridApiCommon.ts
+++ b/packages/x-data-grid/src/models/api/gridApiCommon.ts
@@ -10,7 +10,7 @@ import type { GridLocaleTextApi } from './gridLocaleTextApi';
import type { GridParamsApi } from './gridParamsApi';
import { GridPreferencesPanelApi } from './gridPreferencesPanelApi';
import { GridPrintExportApi } from './gridPrintExportApi';
-import { GridRowApi } from './gridRowApi';
+import { GridRowApi, GridRowProPrivateApi } from './gridRowApi';
import { GridRowsMetaApi, GridRowsMetaPrivateApi } from './gridRowsMetaApi';
import { GridRowSelectionApi } from './gridRowSelectionApi';
import { GridSortApi } from './gridSortApi';
@@ -82,7 +82,8 @@ export interface GridPrivateOnlyApiCommon<
GridLoggerApi,
GridFocusPrivateApi,
GridHeaderFilteringPrivateApi,
- GridVirtualizationPrivateApi {}
+ GridVirtualizationPrivateApi,
+ GridRowProPrivateApi {}
export interface GridPrivateApiCommon
extends GridApiCommon,
diff --git a/packages/x-data-grid/src/models/api/gridRowApi.ts b/packages/x-data-grid/src/models/api/gridRowApi.ts
index 35382e3067dc..b54617e856a4 100644
--- a/packages/x-data-grid/src/models/api/gridRowApi.ts
+++ b/packages/x-data-grid/src/models/api/gridRowApi.ts
@@ -48,6 +48,11 @@ export interface GridRowApi {
* @returns {GridRowId[]} A list of ids.
*/
getAllRowIds: () => GridRowId[];
+ /**
+ * Sets the internal loading state.
+ * @param {boolean} loading If `true` the loading indicator will be shown over the Data Grid.
+ */
+ setLoading: (loading: boolean) => void;
/**
* Sets a new set of rows.
* @param {GridRowModel[]} rows The new rows.
@@ -112,3 +117,13 @@ export interface GridRowProApi {
*/
setRowChildrenExpansion: (id: GridRowId, isExpanded: boolean) => void;
}
+
+export interface GridRowProPrivateApi {
+ /**
+ * Allows to update, insert and delete rows at a specific nested level.
+ * @param {GridRowModelUpdate[]} updates An array of rows with an `action` specifying what to do.
+ * @param {string[]} groupKeys The group keys of the rows to update.
+ * @param {boolean} throttle Whether to throttle the updates or not. (default: `true`)
+ */
+ updateServerRows: (updates: GridRowModelUpdate[], groupKeys?: string[]) => void;
+}
diff --git a/packages/x-data-grid/src/models/api/gridRowSelectionApi.ts b/packages/x-data-grid/src/models/api/gridRowSelectionApi.ts
index ccbd3b1aebd2..ed8f6b228c9f 100644
--- a/packages/x-data-grid/src/models/api/gridRowSelectionApi.ts
+++ b/packages/x-data-grid/src/models/api/gridRowSelectionApi.ts
@@ -31,9 +31,9 @@ export interface GridRowSelectionApi {
/**
* Updates the selected rows to be those passed to the `rowIds` argument.
* Any row already selected will be unselected.
- * @param {GridRowId[]} rowIds The row ids to select.
+ * @param {readonly GridRowId[]} rowIds The row ids to select.
*/
- setRowSelectionModel: (rowIds: GridRowId[]) => void;
+ setRowSelectionModel: (rowIds: readonly GridRowId[]) => void;
}
export interface GridRowMultiSelectionApi {
diff --git a/packages/x-data-grid/src/models/colDef/gridColDef.ts b/packages/x-data-grid/src/models/colDef/gridColDef.ts
index 36d7619a5941..4030443ff315 100644
--- a/packages/x-data-grid/src/models/colDef/gridColDef.ts
+++ b/packages/x-data-grid/src/models/colDef/gridColDef.ts
@@ -134,7 +134,7 @@ export interface GridBaseColDef[];
+ filterOperators?: readonly GridFilterOperator[];
/**
* The callback that generates a filtering function for a given quick filter value.
* This function can return `null` to skip filtering for this value and column.
@@ -306,9 +306,9 @@ export interface GridActionsColDef[]} An array of [[GridActionsCell]] elements.
+ * @returns {readonly React.ReactElement[]} An array of [[GridActionsCell]] elements.
*/
- getActions: (params: GridRowParams) => React.ReactElement[];
+ getActions: (params: GridRowParams) => readonly React.ReactElement[];
}
/**
diff --git a/packages/x-data-grid/src/models/gridDataSource.ts b/packages/x-data-grid/src/models/gridDataSource.ts
new file mode 100644
index 000000000000..58038fe0e290
--- /dev/null
+++ b/packages/x-data-grid/src/models/gridDataSource.ts
@@ -0,0 +1,97 @@
+import type {
+ GridSortModel,
+ GridFilterModel,
+ GridColDef,
+ GridRowModel,
+ GridPaginationModel,
+} from '.';
+
+export interface GridGetRowsParams {
+ sortModel: GridSortModel;
+ filterModel: GridFilterModel;
+ /**
+ * Alternate to `start` and `end`, maps to `GridPaginationModel` interface.
+ */
+ paginationModel: GridPaginationModel;
+ /**
+ * First row index to fetch (number) or cursor information (number | string).
+ */
+ start: number | string;
+ /**
+ * Last row index to fetch.
+ */
+ end: number; // last row index to fetch
+ /**
+ * List of grouped columns (only applicable with `rowGrouping`).
+ */
+ groupFields?: GridColDef['field'][];
+ /**
+ * Array of keys returned by `getGroupKey` of all the parent rows until the row for which the data is requested
+ * `getGroupKey` prop must be implemented to use this.
+ * Useful for `treeData` and `rowGrouping` only.
+ */
+ groupKeys?: string[];
+}
+
+export interface GridGetRowsResponse {
+ rows: GridRowModel[];
+ /**
+ * To reflect updates in total `rowCount` (optional).
+ * Useful when the `rowCount` is inaccurate (for example when filtering) or not available upfront.
+ */
+ rowCount?: number;
+ /**
+ * Additional `pageInfo` for advanced use-cases.
+ * `hasNextPage`: When row count is unknown/estimated, `hasNextPage` will be used to check if more records are available on server
+ */
+ pageInfo?: {
+ hasNextPage?: boolean;
+ nextCursor?: string;
+ };
+}
+
+export interface GridDataSource {
+ /**
+ * This method will be called when the grid needs to fetch some rows
+ * @param {GridGetRowsParams} params The parameters required to fetch the rows
+ * @returns {Promise} A promise that resolves to the data of type [GridGetRowsResponse]
+ */
+ getRows(params: GridGetRowsParams): Promise;
+ /**
+ * This method will be called when the user updates a row [Not yet implemented]
+ * @param {GridRowModel} updatedRow The updated row
+ * @returns {Promise} If resolved (synced on the backend), the grid will update the row and mutate the cache
+ */
+ updateRow?(updatedRow: GridRowModel): Promise;
+ /**
+ * Used to group rows by their parent group. Replaces `getTreeDataPath` used in client side tree-data .
+ * @param {GridRowModel} row The row to get the group key of
+ * @returns {string} The group key for the row
+ */
+ getGroupKey?: (row: GridRowModel) => string;
+ /**
+ * Used to determine the number of children a row has on server.
+ * @param {GridRowModel} row The row to check the number of children
+ * @returns {number} The number of children the row has
+ */
+ getChildrenCount?: (row: GridRowModel) => number;
+}
+
+export interface GridDataSourceCache {
+ /**
+ * Set the cache entry for the given key
+ * @param {GridGetRowsParams} key The key of type `GridGetRowsParams`
+ * @param {GridGetRowsResponse} value The value to be stored in the cache
+ */
+ set: (key: GridGetRowsParams, value: GridGetRowsResponse) => void;
+ /**
+ * Get the cache entry for the given key
+ * @param {GridGetRowsParams} key The key of type `GridGetRowsParams`
+ * @returns {GridGetRowsResponse} The value stored in the cache
+ */
+ get: (key: GridGetRowsParams) => GridGetRowsResponse | undefined;
+ /**
+ * Clear the cache
+ */
+ clear: () => void;
+}
diff --git a/packages/x-data-grid/src/models/gridRowSelectionModel.ts b/packages/x-data-grid/src/models/gridRowSelectionModel.ts
index 19b3bee14dfb..599d21df8b8f 100644
--- a/packages/x-data-grid/src/models/gridRowSelectionModel.ts
+++ b/packages/x-data-grid/src/models/gridRowSelectionModel.ts
@@ -1,5 +1,5 @@
import { GridRowId } from './gridRows';
-export type GridInputRowSelectionModel = GridRowId[] | GridRowId;
+export type GridInputRowSelectionModel = readonly GridRowId[] | GridRowId;
-export type GridRowSelectionModel = GridRowId[];
+export type GridRowSelectionModel = readonly GridRowId[];
diff --git a/packages/x-data-grid/src/models/gridRows.ts b/packages/x-data-grid/src/models/gridRows.ts
index 1519fd1bddba..703433dc873c 100644
--- a/packages/x-data-grid/src/models/gridRows.ts
+++ b/packages/x-data-grid/src/models/gridRows.ts
@@ -114,6 +114,17 @@ export interface GridDataGroupNode extends GridBasicGroupNode {
isAutoGenerated: false;
}
+export interface GridDataSourceGroupNode extends GridDataGroupNode {
+ /**
+ * If true, this node has children on server.
+ */
+ hasServerChildren: boolean;
+ /**
+ * The cached path to be passed on as `groupKey` to the server.
+ */
+ path: string[];
+}
+
export type GridGroupNode = GridDataGroupNode | GridAutoGeneratedGroupNode;
export type GridChildrenFromPathLookup = {
diff --git a/packages/x-data-grid/src/models/gridSlotsComponent.ts b/packages/x-data-grid/src/models/gridSlotsComponent.ts
index f08dc9dde12f..4fe7ea508c74 100644
--- a/packages/x-data-grid/src/models/gridSlotsComponent.ts
+++ b/packages/x-data-grid/src/models/gridSlotsComponent.ts
@@ -93,6 +93,11 @@ export interface GridSlotsComponent extends GridBaseSlots, GridIconSlotsComponen
columnHeaderFilterIconButton: React.JSXElementConstructor<
GridSlotProps['columnHeaderFilterIconButton']
>;
+ /**
+ * Sort icon component rendered in each column header.
+ * @default GridColumnHeaderSortIcon
+ */
+ columnHeaderSortIcon: React.JSXElementConstructor;
/**
* Column menu component rendered by clicking on the 3 dots "kebab" icon in column headers.
* @default GridColumnMenu
diff --git a/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts b/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts
index 6623ae5d4483..6ea9c9f27613 100644
--- a/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts
+++ b/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts
@@ -27,7 +27,9 @@ import type { GridColumnHeadersProps } from '../components/GridColumnHeaders';
import type { GridDetailPanelsProps } from '../components/GridDetailPanels';
import type { GridPinnedRowsProps } from '../components/GridPinnedRows';
import type { GridColumnsManagementProps } from '../components/columnsManagement/GridColumnsManagement';
-import type { GridRowCountProps } from '../components';
+import type { GridLoadingOverlayProps } from '../components/GridLoadingOverlay';
+import type { GridRowCountProps } from '../components/GridRowCount';
+import type { GridColumnHeaderSortIconProps } from '../components/columnHeaders/GridColumnHeaderSortIcon';
// Overrides for module augmentation
export interface BaseCheckboxPropsOverrides {}
@@ -46,6 +48,7 @@ export interface BaseChipPropsOverrides {}
export interface CellPropsOverrides {}
export interface ToolbarPropsOverrides {}
export interface ColumnHeaderFilterIconButtonPropsOverrides {}
+export interface ColumnHeaderSortIconPropsOverrides {}
export interface ColumnMenuPropsOverrides {}
export interface ColumnsPanelPropsOverrides {}
export interface DetailPanelsPropsOverrides {}
@@ -84,6 +87,7 @@ export interface GridSlotProps {
columnHeaders: GridColumnHeadersProps;
columnHeaderFilterIconButton: ColumnHeaderFilterIconButtonProps &
ColumnHeaderFilterIconButtonPropsOverrides;
+ columnHeaderSortIcon: GridColumnHeaderSortIconProps & ColumnHeaderSortIconPropsOverrides;
columnMenu: GridColumnMenuProps & ColumnMenuPropsOverrides;
columnsPanel: GridColumnsPanelProps & ColumnsPanelPropsOverrides;
columnsManagement: GridColumnsManagementProps & ColumnsManagementPropsOverrides;
@@ -91,7 +95,7 @@ export interface GridSlotProps {
filterPanel: GridFilterPanelProps & FilterPanelPropsOverrides;
footer: GridFooterContainerProps & FooterPropsOverrides;
footerRowCount: GridRowCountProps & FooterRowCountOverrides;
- loadingOverlay: GridOverlayProps & LoadingOverlayPropsOverrides;
+ loadingOverlay: GridLoadingOverlayProps & LoadingOverlayPropsOverrides;
noResultsOverlay: GridOverlayProps & NoResultsOverlayPropsOverrides;
noRowsOverlay: GridOverlayProps & NoRowsOverlayPropsOverrides;
pagination: Partial & PaginationPropsOverrides;
diff --git a/packages/x-data-grid/src/models/props/DataGridProps.ts b/packages/x-data-grid/src/models/props/DataGridProps.ts
index 21e6a750d0a3..47a56c9a742a 100644
--- a/packages/x-data-grid/src/models/props/DataGridProps.ts
+++ b/packages/x-data-grid/src/models/props/DataGridProps.ts
@@ -32,6 +32,7 @@ import { GridCellModesModel, GridRowModesModel } from '../api/gridEditingApi';
import { GridColumnGroupingModel } from '../gridColumnGrouping';
import { GridPaginationMeta, GridPaginationModel } from '../gridPaginationProps';
import type { GridAutosizeOptions } from '../../hooks/features/columnResize';
+import type { GridDataSource } from '../gridDataSource';
export interface GridExperimentalFeatures {
/**
@@ -814,6 +815,7 @@ export interface DataGridProSharedPropsWithoutDefaultValue {
* Override the height of the header filters.
*/
headerFilterHeight?: number;
+ unstable_dataSource?: GridDataSource;
}
export interface DataGridPremiumSharedPropsWithDefaultValue {
diff --git a/packages/x-data-grid/src/tests/DataGrid.test.tsx b/packages/x-data-grid/src/tests/DataGrid.test.tsx
index 17011096eb2d..cef9fe12cf3a 100644
--- a/packages/x-data-grid/src/tests/DataGrid.test.tsx
+++ b/packages/x-data-grid/src/tests/DataGrid.test.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { createRenderer } from '@mui/internal-test-utils';
import { expect } from 'chai';
-import { DataGrid } from '@mui/x-data-grid';
+import { DataGrid, DATA_GRID_PROPS_DEFAULT_VALUES } from '@mui/x-data-grid';
const isJSDOM = /jsdom/.test(window.navigator.userAgent);
@@ -62,4 +62,36 @@ describe(' ', () => {
,
);
});
+
+ it('should not cause unexpected behavior when props are explictly set to undefined', () => {
+ const rows = [
+ { id: 'a', col1: 'Hello', col2: 'World' },
+ { id: 'constructor', col1: 'DataGridPro', col2: 'is Awesome' },
+ { id: 'hasOwnProperty', col1: 'MUI', col2: 'is Amazing' },
+ ];
+
+ const columns = [
+ { field: 'col1', headerName: 'Column 1', width: 150 },
+ { field: 'col2', headerName: 'Column 2', width: 150 },
+ ];
+ expect(() => {
+ render(
+
+
+ ).reduce((acc, key) => {
+ // @ts-ignore
+ acc[key] = undefined;
+ return acc;
+ }, {})}
+ rows={rows}
+ columns={columns}
+ />
+
,
+ );
+ }).not.toErrorDev();
+ });
});
diff --git a/packages/x-data-grid/src/tests/cells.DataGrid.test.tsx b/packages/x-data-grid/src/tests/cells.DataGrid.test.tsx
index 14c0f51e40e8..91eec8720b13 100644
--- a/packages/x-data-grid/src/tests/cells.DataGrid.test.tsx
+++ b/packages/x-data-grid/src/tests/cells.DataGrid.test.tsx
@@ -60,7 +60,7 @@ describe(' - Cells', () => {
expect(width).to.equal('1px');
// should not be transparent
- expect(color).to.not.equal('rgba(0, 0, 0, 0)');
+ expect(color).not.to.equal('rgba(0, 0, 0, 0)');
}
it('should add right border to cells', function test() {
diff --git a/packages/x-data-grid/src/tests/columnHeaders.DataGrid.test.tsx b/packages/x-data-grid/src/tests/columnHeaders.DataGrid.test.tsx
index 817d96a118a9..47e091416795 100644
--- a/packages/x-data-grid/src/tests/columnHeaders.DataGrid.test.tsx
+++ b/packages/x-data-grid/src/tests/columnHeaders.DataGrid.test.tsx
@@ -125,7 +125,7 @@ describe(' - Column headers', () => {
userEvent.mousePress(within(getColumnHeaderCell(0)).getByLabelText('Menu'));
clock.runToLast();
- expect(screen.queryByRole('menu')).to.not.equal(null);
+ expect(screen.queryByRole('menu')).not.to.equal(null);
userEvent.mousePress(within(getColumnHeaderCell(0)).getByLabelText('Menu'));
clock.runToLast();
diff --git a/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx b/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx
index 28a32fe53aa0..4f003a354866 100644
--- a/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx
+++ b/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx
@@ -1174,7 +1174,7 @@ describe(' - Layout & warnings', () => {
}
render(
-
+
,
);
@@ -1213,7 +1213,7 @@ describe('
- Layout & warnings', () => {
}
render(
-
+
- Pagination', () => {
expect(getColumnValues(0)).to.have.length(20);
setProps({ paginationModel: { pageSize: 10, page: 0 } });
expect(getColumnValues(0)).to.have.length(10);
- expect(getCell(0, 0)).to.not.equal(null);
+ expect(getCell(0, 0)).not.to.equal(null);
fireEvent.click(screen.getByRole('button', { name: /next page/i }));
expect(getColumnValues(0)).to.have.length(10);
});
@@ -597,30 +597,30 @@ describe('
- Pagination', () => {
it('should support server side pagination with unknown row count', () => {
const { setProps } = render(
);
expect(getColumnValues(0)).to.deep.equal(['0']);
- expect(screen.getByText('1β1 of more than 1')).to.not.equal(null);
+ expect(screen.getByText('1β1 of more than 1')).not.to.equal(null);
fireEvent.click(screen.getByRole('button', { name: /next page/i }));
expect(getColumnValues(0)).to.deep.equal(['1']);
- expect(screen.getByText('2β2 of more than 2')).to.not.equal(null);
+ expect(screen.getByText('2β2 of more than 2')).not.to.equal(null);
fireEvent.click(screen.getByRole('button', { name: /next page/i }));
setProps({ rowCount: 3 });
expect(getColumnValues(0)).to.deep.equal(['2']);
- expect(screen.getByText('3β3 of 3')).to.not.equal(null);
+ expect(screen.getByText('3β3 of 3')).not.to.equal(null);
});
it('should support server side pagination with estimated row count', () => {
const { setProps } = render(
);
expect(getColumnValues(0)).to.deep.equal(['0']);
- expect(screen.getByText('1β1 of more than 2')).to.not.equal(null);
+ expect(screen.getByText('1β1 of more than 2')).not.to.equal(null);
fireEvent.click(screen.getByRole('button', { name: /next page/i }));
expect(getColumnValues(0)).to.deep.equal(['1']);
- expect(screen.getByText('2β2 of more than 2')).to.not.equal(null);
+ expect(screen.getByText('2β2 of more than 2')).not.to.equal(null);
fireEvent.click(screen.getByRole('button', { name: /next page/i }));
expect(getColumnValues(0)).to.deep.equal(['2']);
- expect(screen.getByText('3β3 of more than 3')).to.not.equal(null);
+ expect(screen.getByText('3β3 of more than 3')).not.to.equal(null);
fireEvent.click(screen.getByRole('button', { name: /next page/i }));
setProps({ rowCount: 4 });
expect(getColumnValues(0)).to.deep.equal(['3']);
- expect(screen.getByText('4β4 of 4')).to.not.equal(null);
+ expect(screen.getByText('4β4 of 4')).not.to.equal(null);
});
});
@@ -730,12 +730,14 @@ describe('
- Pagination', () => {
];
expect(() => {
const { setProps } = render(
-
,
+
+
+
,
);
setProps({ rows: rows.slice(0, 2) });
}).not.to.throw();
diff --git a/packages/x-data-grid/src/utils/utils.ts b/packages/x-data-grid/src/utils/utils.ts
index cc682162e59f..097ed683d9b2 100644
--- a/packages/x-data-grid/src/utils/utils.ts
+++ b/packages/x-data-grid/src/utils/utils.ts
@@ -190,9 +190,16 @@ function mulberry32(a: number): () => number {
};
}
-export function randomNumberBetween(seed: number, min: number, max: number): () => number {
+/**
+ * Create a random number generator from a seed. The seed
+ * ensures that the random number generator produces the
+ * same sequence of 'random' numbers on every render. It
+ * returns a function that generates a random number between
+ * a specified min and max.
+ */
+export function createRandomNumberGenerator(seed: number): (min: number, max: number) => number {
const random = mulberry32(seed);
- return () => min + (max - min) * random();
+ return (min: number, max: number) => min + (max - min) * random();
}
export function deepClone(obj: Record
) {
diff --git a/packages/x-data-grid/tsconfig.build.json b/packages/x-data-grid/tsconfig.build.json
index a287257ec3c0..be862bc37b1b 100644
--- a/packages/x-data-grid/tsconfig.build.json
+++ b/packages/x-data-grid/tsconfig.build.json
@@ -11,6 +11,7 @@
"rootDir": "./src",
"types": ["node", "@mui/material/themeCssVarsAugmentation", "@emotion/styled", "@mui/types"]
},
+ "references": [{ "path": "../x-internals/tsconfig.build.json" }],
"include": ["src/**/*.ts*"],
"exclude": ["src/**/*.spec.ts*", "src/**/*.test.ts*"]
}
diff --git a/packages/x-data-grid/tsconfig.json b/packages/x-data-grid/tsconfig.json
index aba8438e2441..a95beb1e8020 100644
--- a/packages/x-data-grid/tsconfig.json
+++ b/packages/x-data-grid/tsconfig.json
@@ -5,7 +5,8 @@
"@mui/internal-test-utils/initMatchers",
"@mui/material/themeCssVarsAugmentation",
"chai-dom",
- "mocha"
+ "mocha",
+ "node"
]
},
"include": ["src/**/*"]
diff --git a/packages/x-date-pickers-pro/package.json b/packages/x-date-pickers-pro/package.json
index da78cb86f3b3..e852566d858b 100644
--- a/packages/x-date-pickers-pro/package.json
+++ b/packages/x-date-pickers-pro/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/x-date-pickers-pro",
- "version": "7.7.1",
+ "version": "7.9.0",
"description": "The Pro plan edition of the Date and Time Picker components (MUI X).",
"author": "MUI Team",
"main": "src/index.ts",
@@ -44,8 +44,8 @@
"dependencies": {
"@babel/runtime": "^7.24.7",
"@mui/base": "^5.0.0-beta.40",
- "@mui/system": "^5.15.20",
- "@mui/utils": "^5.15.20",
+ "@mui/system": "^5.16.0",
+ "@mui/utils": "^5.16.0",
"@mui/x-date-pickers": "workspace:*",
"@mui/x-license": "workspace:*",
"clsx": "^2.1.1",
@@ -96,7 +96,7 @@
}
},
"devDependencies": {
- "@mui/internal-test-utils": "^1.0.1",
+ "@mui/internal-test-utils": "^1.0.4",
"@types/luxon": "^3.4.2",
"@types/prop-types": "^15.7.12",
"date-fns": "^2.30.0",
@@ -104,7 +104,7 @@
"dayjs": "^1.11.11",
"luxon": "^3.4.4",
"moment": "^2.30.1",
- "rimraf": "^5.0.7"
+ "rimraf": "^5.0.8"
},
"engines": {
"node": ">=14.0.0"
diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx
index ffa81a05f51b..129d6c7ba2f2 100644
--- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx
+++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx
@@ -14,7 +14,6 @@ import {
MockedDataTransfer,
rangeCalendarDayTouches,
createPickerRenderer,
- wrapPickerMount,
} from 'test/utils/pickers';
import {
DateRangeCalendar,
@@ -45,9 +44,8 @@ describe(' ', () => {
inheritComponent: 'div',
render,
muiName: 'MuiDateRangeCalendar',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
- skip: ['componentProp', 'componentsProp', 'reactTestRenderer', 'themeVariants'],
+ skip: ['componentProp', 'componentsProp', 'themeVariants'],
}));
describe('Selection', () => {
diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerToolbar.tsx b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerToolbar.tsx
index b14f206755ee..da4b3debfa1d 100644
--- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerToolbar.tsx
+++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePickerToolbar.tsx
@@ -9,9 +9,9 @@ import {
PickersToolbarButton,
useUtils,
BaseToolbarProps,
- useLocaleText,
ExportedBaseToolbarProps,
} from '@mui/x-date-pickers/internals';
+import { usePickersTranslations } from '@mui/x-date-pickers/hooks';
import { PickerValidDate } from '@mui/x-date-pickers/models';
import { DateRange } from '../models';
import { UseRangePositionResponse } from '../internals/hooks/useRangePosition';
@@ -86,15 +86,15 @@ const DateRangePickerToolbar = React.forwardRef(function DateRangePickerToolbar<
...other
} = props;
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const startDateValue = start
? utils.formatByString(start, toolbarFormat || utils.formats.shortDate)
- : localeText.start;
+ : translations.start;
const endDateValue = end
? utils.formatByString(end, toolbarFormat || utils.formats.shortDate)
- : localeText.end;
+ : translations.end;
const ownerState = props;
const classes = useUtilityClasses(ownerState);
@@ -102,7 +102,7 @@ const DateRangePickerToolbar = React.forwardRef(function DateRangePickerToolbar<
return (
- Describes', () => {
@@ -10,7 +10,6 @@ describe(' - Describes', () => {
classes: {} as any,
render,
muiName: 'MuiDateRangePicker',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
skip: [
'componentProp',
@@ -21,7 +20,6 @@ describe(' - Describes', () => {
'mergeClassName',
'propsSpread',
'rootClass',
- 'reactTestRenderer',
],
}));
});
diff --git a/packages/x-date-pickers-pro/src/DateRangePickerDay/DateRangePickerDay.test.tsx b/packages/x-date-pickers-pro/src/DateRangePickerDay/DateRangePickerDay.test.tsx
index 03294c00ca36..6b1ef20fb85b 100644
--- a/packages/x-date-pickers-pro/src/DateRangePickerDay/DateRangePickerDay.test.tsx
+++ b/packages/x-date-pickers-pro/src/DateRangePickerDay/DateRangePickerDay.test.tsx
@@ -3,7 +3,7 @@ import {
DateRangePickerDay,
dateRangePickerDayClasses as classes,
} from '@mui/x-date-pickers-pro/DateRangePickerDay';
-import { wrapPickerMount, createPickerRenderer, adapterToUse } from 'test/utils/pickers';
+import { createPickerRenderer, adapterToUse } from 'test/utils/pickers';
import { describeConformance } from 'test/utils/describeConformance';
describe(' ', () => {
@@ -29,14 +29,13 @@ describe(' ', () => {
inheritComponent: 'button',
muiName: 'MuiDateRangePickerDay',
render,
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLButtonElement,
// cannot test reactTestRenderer because of required context
skip: [
'componentProp',
'rootClass', // forwards classes to DateRangePickerDayDay, but applies root class on DateRangePickerDayRoot
+ 'mergeClassName', // forwards other props (i.e. data-test-id) to the DateRangePickerDayDay, but `className` is applied on the root
'componentsProp',
- 'reactTestRenderer',
'propsSpread',
'refForwarding',
// TODO: Fix DateRangePickerDays is not spreading props on root
diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTabs.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTabs.tsx
index d71f3a04fce2..a5b56f8eaeac 100644
--- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTabs.tsx
+++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTabs.tsx
@@ -5,13 +5,14 @@ import { styled, useThemeProps } from '@mui/material/styles';
import composeClasses from '@mui/utils/composeClasses';
import useEventCallback from '@mui/utils/useEventCallback';
import { TimeIcon, DateRangeIcon, ArrowLeftIcon, ArrowRightIcon } from '@mui/x-date-pickers/icons';
+import { PickerValidDate } from '@mui/x-date-pickers/models';
import {
DateOrTimeViewWithMeridiem,
- useLocaleText,
BaseTabsProps,
ExportedBaseTabsProps,
isDatePickerView,
} from '@mui/x-date-pickers/internals';
+import { usePickersTranslations } from '@mui/x-date-pickers/hooks';
import IconButton from '@mui/material/IconButton';
import Button from '@mui/material/Button';
import {
@@ -106,7 +107,7 @@ const DateTimeRangePickerTabFiller = styled('div', {
const tabOptions: TabValue[] = ['start-date', 'start-time', 'end-date', 'end-time'];
-const DateTimeRangePickerTabs = function DateTimeRangePickerTabs(
+const DateTimeRangePickerTabs = function DateTimeRangePickerTabs(
inProps: DateTimeRangePickerTabsProps,
) {
const props = useThemeProps({ props: inProps, name: 'MuiDateTimeRangePickerTabs' });
@@ -122,7 +123,7 @@ const DateTimeRangePickerTabs = function DateTimeRangePickerTabs(
sx,
} = props;
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const classes = useUtilityClasses(props);
const value = React.useMemo(() => viewToTab(view, rangePosition), [view, rangePosition]);
const isPreviousHidden = value === 'start-date';
@@ -130,17 +131,23 @@ const DateTimeRangePickerTabs = function DateTimeRangePickerTabs(
const tabLabel = React.useMemo(() => {
switch (value) {
case 'start-date':
- return localeText.startDate;
+ return translations.startDate;
case 'start-time':
- return localeText.startTime;
+ return translations.startTime;
case 'end-date':
- return localeText.endDate;
+ return translations.endDate;
case 'end-time':
- return localeText.endTime;
+ return translations.endTime;
default:
return '';
}
- }, [localeText.endDate, localeText.endTime, localeText.startDate, localeText.startTime, value]);
+ }, [
+ translations.endDate,
+ translations.endTime,
+ translations.startDate,
+ translations.startTime,
+ value,
+ ]);
const handleRangePositionChange = useEventCallback((newTab: TabValue) => {
if (newTab.includes('start')) {
@@ -176,7 +183,7 @@ const DateTimeRangePickerTabs = function DateTimeRangePickerTabs(
@@ -195,7 +202,7 @@ const DateTimeRangePickerTabs = function DateTimeRangePickerTabs(
diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx
index f2ff988c224c..26816d573602 100644
--- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx
+++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx
@@ -5,12 +5,12 @@ import { styled, useThemeProps } from '@mui/material/styles';
import { unstable_composeClasses as composeClasses } from '@mui/utils';
import {
BaseToolbarProps,
- useLocaleText,
ExportedBaseToolbarProps,
useUtils,
DateOrTimeViewWithMeridiem,
WrapperVariant,
} from '@mui/x-date-pickers/internals';
+import { usePickersTranslations } from '@mui/x-date-pickers/hooks';
import { PickerValidDate } from '@mui/x-date-pickers/models';
import {
DateTimePickerToolbarProps,
@@ -151,7 +151,7 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker
toolbarPlaceholder,
};
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const ownerState = props;
const classes = useUtilityClasses(ownerState);
@@ -212,7 +212,7 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker
value={start}
onViewChange={handleStartRangeViewChange}
- toolbarTitle={localeText.start}
+ toolbarTitle={translations.start}
ownerState={ownerState}
toolbarVariant="desktop"
view={rangePosition === 'start' ? view : undefined}
@@ -224,7 +224,7 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker
value={end}
onViewChange={handleEndRangeViewChange}
- toolbarTitle={localeText.end}
+ toolbarTitle={translations.end}
ownerState={ownerState}
toolbarVariant="desktop"
view={rangePosition === 'end' ? view : undefined}
diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/tests/describes.DateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/tests/describes.DateTimeRangePicker.test.tsx
index 4778e65099b2..264143b0f016 100644
--- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/tests/describes.DateTimeRangePicker.test.tsx
+++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/tests/describes.DateTimeRangePicker.test.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { createPickerRenderer, wrapPickerMount } from 'test/utils/pickers';
+import { createPickerRenderer } from 'test/utils/pickers';
import { describeConformance } from 'test/utils/describeConformance';
import { DateTimeRangePicker } from '../DateTimeRangePicker';
@@ -10,7 +10,6 @@ describe(' - Describes', () => {
classes: {} as any,
render,
muiName: 'MuiDateTimeRangePicker',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
skip: [
'componentProp',
@@ -21,7 +20,6 @@ describe(' - Describes', () => {
'mergeClassName',
'propsSpread',
'rootClass',
- 'reactTestRenderer',
],
}));
});
diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/describes.DesktopDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/describes.DesktopDateRangePicker.test.tsx
index a4498dca0101..8272c9b05382 100644
--- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/describes.DesktopDateRangePicker.test.tsx
+++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/describes.DesktopDateRangePicker.test.tsx
@@ -3,7 +3,6 @@ import { screen, userEvent } from '@mui/internal-test-utils';
import {
adapterToUse,
createPickerRenderer,
- wrapPickerMount,
expectFieldValueV7,
describePicker,
describeValue,
@@ -34,7 +33,6 @@ describe(' - Describes', () => {
classes: {} as any,
render,
muiName: 'MuiDesktopDateRangePicker',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
skip: [
'componentProp',
@@ -45,7 +43,6 @@ describe(' - Describes', () => {
'mergeClassName',
'propsSpread',
'rootClass',
- 'reactTestRenderer',
],
}));
diff --git a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx
index 431bed8573b8..d90ff4da876f 100644
--- a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx
+++ b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx
@@ -7,6 +7,8 @@ import {
isInternalTimeView,
PickerViewRenderer,
PickerViewsRendererProps,
+ resolveDateTimeFormat,
+ useUtils,
} from '@mui/x-date-pickers/internals';
import { PickerValidDate } from '@mui/x-date-pickers/models';
import { resolveComponentProps } from '@mui/base/utils';
@@ -138,6 +140,7 @@ const DesktopDateTimeRangePicker = React.forwardRef(function DesktopDateTimeRang
inProps: DesktopDateTimeRangePickerProps,
ref: React.Ref,
) {
+ const utils = useUtils();
// Props with the default values common to all date time range pickers
const defaultizedProps = useDateTimeRangePickerDefaultizedProps<
TDate,
@@ -170,6 +173,7 @@ const DesktopDateTimeRangePicker = React.forwardRef(function DesktopDateTimeRang
...defaultizedProps,
views,
viewRenderers,
+ format: resolveDateTimeFormat(utils, defaultizedProps),
// force true to correctly handle `renderTimeViewClock` as a renderer
ampmInClock: true,
calendars: defaultizedProps.calendars ?? 1,
diff --git a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/describes.DesktopDateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/describes.DesktopDateTimeRangePicker.test.tsx
index 1045076de111..09e6d97de385 100644
--- a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/describes.DesktopDateTimeRangePicker.test.tsx
+++ b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/describes.DesktopDateTimeRangePicker.test.tsx
@@ -7,7 +7,6 @@ import {
describeValue,
describePicker,
describeRangeValidation,
- wrapPickerMount,
getFieldSectionsContainer,
} from 'test/utils/pickers';
import { DesktopDateTimeRangePicker } from '../DesktopDateTimeRangePicker';
@@ -35,7 +34,6 @@ describe(' - Describes', () => {
classes: {} as any,
render,
muiName: 'MuiDesktopDateTimeRangePicker',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
skip: [
'componentProp',
@@ -46,7 +44,6 @@ describe(' - Describes', () => {
'mergeClassName',
'propsSpread',
'rootClass',
- 'reactTestRenderer',
],
}));
diff --git a/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/describes.MobileDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/describes.MobileDateRangePicker.test.tsx
index 7b2bcf1dc888..22aace0cc29c 100644
--- a/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/describes.MobileDateRangePicker.test.tsx
+++ b/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/describes.MobileDateRangePicker.test.tsx
@@ -4,7 +4,6 @@ import { MobileDateRangePicker } from '@mui/x-date-pickers-pro/MobileDateRangePi
import {
adapterToUse,
createPickerRenderer,
- wrapPickerMount,
openPicker,
expectFieldValueV7,
describeRangeValidation,
@@ -34,7 +33,6 @@ describe(' - Describes', () => {
classes: {} as any,
render,
muiName: 'MuiMobileDateRangePicker',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
skip: [
'componentProp',
@@ -45,7 +43,6 @@ describe(' - Describes', () => {
'mergeClassName',
'propsSpread',
'rootClass',
- 'reactTestRenderer',
],
}));
diff --git a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/MobileDateTimeRangePicker.tsx b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/MobileDateTimeRangePicker.tsx
index 7ba1632219fb..8586aa9e32cf 100644
--- a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/MobileDateTimeRangePicker.tsx
+++ b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/MobileDateTimeRangePicker.tsx
@@ -11,6 +11,8 @@ import {
DefaultizedProps,
PickerViewsRendererProps,
TimeViewWithMeridiem,
+ resolveDateTimeFormat,
+ useUtils,
} from '@mui/x-date-pickers/internals';
import { PickerValidDate } from '@mui/x-date-pickers/models';
import { resolveComponentProps } from '@mui/base/utils';
@@ -147,6 +149,7 @@ const MobileDateTimeRangePicker = React.forwardRef(function MobileDateTimeRangeP
inProps: MobileDateTimeRangePickerProps,
ref: React.Ref,
) {
+ const utils = useUtils();
// Props with the default values common to all date time range pickers
const defaultizedProps = useDateTimeRangePickerDefaultizedProps<
TDate,
@@ -169,6 +172,7 @@ const MobileDateTimeRangePicker = React.forwardRef(function MobileDateTimeRangeP
const props = {
...defaultizedProps,
viewRenderers,
+ format: resolveDateTimeFormat(utils, defaultizedProps),
// Force one calendar on mobile to avoid layout issues
calendars: 1,
// force true to correctly handle `renderTimeViewClock` as a renderer
diff --git a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx
index 16358cdc5592..3f10b2f0b4ff 100644
--- a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx
+++ b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/tests/describes.MobileDateTimeRangePicker.test.tsx
@@ -7,7 +7,6 @@ import {
describeValue,
describePicker,
describeRangeValidation,
- wrapPickerMount,
getFieldSectionsContainer,
openPicker,
} from 'test/utils/pickers';
@@ -36,7 +35,6 @@ describe(' - Describes', () => {
classes: {} as any,
render,
muiName: 'MuiMobileDateTimeRangePicker',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
skip: [
'componentProp',
@@ -47,7 +45,6 @@ describe(' - Describes', () => {
'mergeClassName',
'propsSpread',
'rootClass',
- 'reactTestRenderer',
],
}));
diff --git a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/tests/MultiInputDateRangeField.conformance.test.tsx b/packages/x-date-pickers-pro/src/MultiInputDateRangeField/tests/MultiInputDateRangeField.conformance.test.tsx
index d0a04e59dd14..6ba21a354f09 100644
--- a/packages/x-date-pickers-pro/src/MultiInputDateRangeField/tests/MultiInputDateRangeField.conformance.test.tsx
+++ b/packages/x-date-pickers-pro/src/MultiInputDateRangeField/tests/MultiInputDateRangeField.conformance.test.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { MultiInputDateRangeField } from '@mui/x-date-pickers-pro/MultiInputDateRangeField';
-import { createPickerRenderer, wrapPickerMount } from 'test/utils/pickers';
+import { createPickerRenderer } from 'test/utils/pickers';
import { describeConformance } from 'test/utils/describeConformance';
describe(' ', () => {
@@ -11,9 +11,7 @@ describe(' ', () => {
inheritComponent: 'div',
render,
muiName: 'MuiMultiInputDateRangeField',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
- // cannot test reactTestRenderer because of required context
- skip: ['reactTestRenderer', 'themeVariants', 'componentProp', 'componentsProp'],
+ skip: ['themeVariants', 'componentProp', 'componentsProp'],
}));
});
diff --git a/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/tests/MultiInputDateTimeRangeField.conformance.test.tsx b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/tests/MultiInputDateTimeRangeField.conformance.test.tsx
index a0ebd8203fc1..7bb1d6cc437b 100644
--- a/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/tests/MultiInputDateTimeRangeField.conformance.test.tsx
+++ b/packages/x-date-pickers-pro/src/MultiInputDateTimeRangeField/tests/MultiInputDateTimeRangeField.conformance.test.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { MultiInputDateTimeRangeField } from '@mui/x-date-pickers-pro/MultiInputDateTimeRangeField';
-import { createPickerRenderer, wrapPickerMount } from 'test/utils/pickers';
+import { createPickerRenderer } from 'test/utils/pickers';
import { describeConformance } from 'test/utils/describeConformance';
describe(' ', () => {
@@ -11,9 +11,7 @@ describe(' ', () => {
inheritComponent: 'div',
render,
muiName: 'MuiMultiInputDateTimeRangeField',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
- // cannot test reactTestRenderer because of required context
- skip: ['componentProp', 'componentsProp', 'reactTestRenderer', 'themeVariants'],
+ skip: ['componentProp', 'componentsProp', 'themeVariants'],
}));
});
diff --git a/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/tests/MultiInputTimeRangeField.conformance.test.tsx b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/tests/MultiInputTimeRangeField.conformance.test.tsx
index e5efca7026ec..47047c3007d4 100644
--- a/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/tests/MultiInputTimeRangeField.conformance.test.tsx
+++ b/packages/x-date-pickers-pro/src/MultiInputTimeRangeField/tests/MultiInputTimeRangeField.conformance.test.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { MultiInputTimeRangeField } from '@mui/x-date-pickers-pro/MultiInputTimeRangeField';
-import { createPickerRenderer, wrapPickerMount } from 'test/utils/pickers';
+import { createPickerRenderer } from 'test/utils/pickers';
import { describeConformance } from 'test/utils/describeConformance';
describe(' ', () => {
@@ -11,9 +11,7 @@ describe(' ', () => {
inheritComponent: 'div',
render,
muiName: 'MuiMultiInputTimeRangeField',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
- // cannot test reactTestRenderer because of required context
- skip: ['reactTestRenderer', 'themeVariants', 'componentProp', 'componentsProp'],
+ skip: ['themeVariants', 'componentProp', 'componentsProp'],
}));
});
diff --git a/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx b/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx
index 6657941e84d6..467081a8dfc3 100644
--- a/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx
+++ b/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx
@@ -5,11 +5,11 @@ import { PickersCalendarHeader } from '@mui/x-date-pickers/PickersCalendarHeader
import { PickerValidDate } from '@mui/x-date-pickers/models';
import {
PickersArrowSwitcher,
- useLocaleText,
useNextMonthDisabled,
usePreviousMonthDisabled,
useUtils,
} from '@mui/x-date-pickers/internals';
+import { usePickersTranslations } from '@mui/x-date-pickers/hooks';
import { PickersRangeCalendarHeaderProps } from './PickersRangeCalendarHeader.types';
type PickersRangeCalendarHeaderComponent = ((
@@ -27,7 +27,7 @@ const PickersRangeCalendarHeader = React.forwardRef(function PickersRangeCalenda
TDate extends PickerValidDate,
>(props: PickersRangeCalendarHeaderProps, ref: React.Ref) {
const utils = useUtils();
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const { calendars, month, monthIndex, labelId, ...other } = props;
const {
@@ -70,10 +70,10 @@ const PickersRangeCalendarHeader = React.forwardRef(function PickersRangeCalenda
onGoToNext={selectNextMonth}
isPreviousHidden={monthIndex !== 0}
isPreviousDisabled={isPreviousMonthDisabled}
- previousLabel={localeText.previousMonth}
+ previousLabel={translations.previousMonth}
isNextHidden={monthIndex !== calendars - 1}
isNextDisabled={isNextMonthDisabled}
- nextLabel={localeText.nextMonth}
+ nextLabel={translations.nextMonth}
slots={slots}
slotProps={slotProps}
labelId={labelId}
diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/tests/describes.SingleInputDateRangeField.test.tsx b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/tests/describes.SingleInputDateRangeField.test.tsx
index 57704368d4a3..673212f9f185 100644
--- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/tests/describes.SingleInputDateRangeField.test.tsx
+++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/tests/describes.SingleInputDateRangeField.test.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import { PickersTextField } from '@mui/x-date-pickers/PickersTextField';
import { SingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDateRangeField';
-import { createPickerRenderer, wrapPickerMount, describeRangeValidation } from 'test/utils/pickers';
+import { createPickerRenderer, describeRangeValidation } from 'test/utils/pickers';
import { describeConformance } from 'test/utils/describeConformance';
describe(' - Describes', () => {
@@ -12,11 +12,8 @@ describe(' - Describes', () => {
inheritComponent: PickersTextField,
render,
muiName: 'MuiSingleInputDateRangeField',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
- // cannot test reactTestRenderer because of required context
skip: [
- 'reactTestRenderer',
'componentProp',
'componentsProp',
'themeDefaultProps',
diff --git a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/tests/describes.SingleInputDateTimeRangeField.test.tsx b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/tests/describes.SingleInputDateTimeRangeField.test.tsx
index 219e51370d93..28385f501b68 100644
--- a/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/tests/describes.SingleInputDateTimeRangeField.test.tsx
+++ b/packages/x-date-pickers-pro/src/SingleInputDateTimeRangeField/tests/describes.SingleInputDateTimeRangeField.test.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { SingleInputDateTimeRangeField } from '@mui/x-date-pickers-pro/SingleInputDateTimeRangeField';
-import { createPickerRenderer, wrapPickerMount, describeRangeValidation } from 'test/utils/pickers';
+import { createPickerRenderer, describeRangeValidation } from 'test/utils/pickers';
import { describeConformance } from 'test/utils/describeConformance';
describe(' - Describes', () => {
@@ -11,11 +11,8 @@ describe(' - Describes', () => {
inheritComponent: 'div',
render,
muiName: 'MuiSingleInputDateTimeRangeField',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
- // cannot test reactTestRenderer because of required context
skip: [
- 'reactTestRenderer',
'componentProp',
'componentsProp',
'themeDefaultProps',
diff --git a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/tests/describes.SingleInputTimeRangeField.test.tsx b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/tests/describes.SingleInputTimeRangeField.test.tsx
index 684786c4d54d..afab8beb758b 100644
--- a/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/tests/describes.SingleInputTimeRangeField.test.tsx
+++ b/packages/x-date-pickers-pro/src/SingleInputTimeRangeField/tests/describes.SingleInputTimeRangeField.test.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import { SingleInputTimeRangeField } from '@mui/x-date-pickers-pro/SingleInputTimeRangeField';
-import { createPickerRenderer, wrapPickerMount, describeRangeValidation } from 'test/utils/pickers';
+import { createPickerRenderer, describeRangeValidation } from 'test/utils/pickers';
import { describeConformance } from 'test/utils/describeConformance';
describe(' - Describes', () => {
@@ -11,11 +11,8 @@ describe(' - Describes', () => {
inheritComponent: 'div',
render,
muiName: 'MuiSingleInputTimeRangeField',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
- // cannot test reactTestRenderer because of required context
skip: [
- 'reactTestRenderer',
'componentProp',
'componentsProp',
'themeDefaultProps',
diff --git a/packages/x-date-pickers-pro/src/StaticDateRangePicker/StaticDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/StaticDateRangePicker/StaticDateRangePicker.test.tsx
index b47653c3e28d..2804871d2440 100644
--- a/packages/x-date-pickers-pro/src/StaticDateRangePicker/StaticDateRangePicker.test.tsx
+++ b/packages/x-date-pickers-pro/src/StaticDateRangePicker/StaticDateRangePicker.test.tsx
@@ -3,12 +3,7 @@ import { expect } from 'chai';
import { isWeekend } from 'date-fns';
import { StaticDateRangePicker } from '@mui/x-date-pickers-pro/StaticDateRangePicker';
import { screen } from '@mui/internal-test-utils';
-import {
- wrapPickerMount,
- createPickerRenderer,
- adapterToUse,
- describeRangeValidation,
-} from 'test/utils/pickers';
+import { createPickerRenderer, adapterToUse, describeRangeValidation } from 'test/utils/pickers';
import { describeConformance } from 'test/utils/describeConformance';
describe(' ', () => {
@@ -21,7 +16,6 @@ describe(' ', () => {
classes: {} as any,
render,
muiName: 'MuiStaticDateRangePicker',
- wrapMount: wrapPickerMount,
refInstanceof: undefined,
skip: [
'componentProp',
@@ -33,7 +27,6 @@ describe(' ', () => {
'propsSpread',
'refForwarding',
'rootClass',
- 'reactTestRenderer',
],
}));
diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts
index a0b3a6b24f39..dab1f11a3451 100644
--- a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts
+++ b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts
@@ -11,12 +11,15 @@ import {
FieldRef,
PickerValidDate,
} from '@mui/x-date-pickers/models';
-import { UseClearableFieldSlots, UseClearableFieldSlotProps } from '@mui/x-date-pickers/hooks';
+import {
+ UseClearableFieldSlots,
+ UseClearableFieldSlotProps,
+ usePickersTranslations,
+} from '@mui/x-date-pickers/hooks';
import { PickersInputLocaleText } from '@mui/x-date-pickers/locales';
import {
BaseFieldProps,
onSpaceOrEnter,
- useLocaleText,
UsePickerResponse,
WrapperVariant,
UsePickerProps,
@@ -167,7 +170,7 @@ const useMultiInputFieldSlotProps = <
TError
>;
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const handleStartFieldRef = useForkRef(fieldProps.unstableStartFieldRef, startFieldRef);
const handleEndFieldRef = useForkRef(fieldProps.unstableEndFieldRef, endFieldRef);
@@ -245,7 +248,7 @@ const useMultiInputFieldSlotProps = <
let InputProps: MultiInputFieldSlotTextFieldProps['InputProps'];
if (ownerState.position === 'start') {
textFieldProps = {
- label: inLocaleText?.start ?? localeText.start,
+ label: inLocaleText?.start ?? translations.start,
onKeyDown: onSpaceOrEnter(openRangeStartSelection),
onFocus: handleFocusStart,
focused: open ? rangePosition === 'start' : undefined,
@@ -262,7 +265,7 @@ const useMultiInputFieldSlotProps = <
}
} else {
textFieldProps = {
- label: inLocaleText?.end ?? localeText.end,
+ label: inLocaleText?.end ?? translations.end,
onKeyDown: onSpaceOrEnter(openRangeEndSelection),
onFocus: handleFocusEnd,
focused: open ? rangePosition === 'end' : undefined,
diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx b/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx
index e0016ea48423..ecb9797f7500 100644
--- a/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx
+++ b/packages/x-date-pickers-pro/src/internals/hooks/useMobileRangePicker/useMobileRangePicker.tsx
@@ -8,10 +8,10 @@ import {
PickersModalDialog,
InferError,
ExportedBaseToolbarProps,
- useLocaleText,
DateOrTimeViewWithMeridiem,
ExportedBaseTabsProps,
} from '@mui/x-date-pickers/internals';
+import { usePickersTranslations } from '@mui/x-date-pickers/hooks';
import { PickerValidDate, FieldRef, BaseSingleInputFieldProps } from '@mui/x-date-pickers/models';
import useId from '@mui/utils/useId';
import {
@@ -78,7 +78,7 @@ export const useMobileRangePicker = <
fieldType === 'single-input' ? startFieldRef : undefined,
);
const labelId = useId();
- const contextLocaleText = useLocaleText();
+ const contextTranslations = usePickersTranslations();
const {
open,
@@ -193,7 +193,7 @@ export const useMobileRangePicker = <
const Layout = slots?.layout ?? PickersLayout;
const finalLocaleText = {
- ...contextLocaleText,
+ ...contextTranslations,
...localeText,
};
let labelledById =
diff --git a/packages/x-date-pickers-pro/tsconfig.json b/packages/x-date-pickers-pro/tsconfig.json
index b6443628f807..82f2e7898632 100644
--- a/packages/x-date-pickers-pro/tsconfig.json
+++ b/packages/x-date-pickers-pro/tsconfig.json
@@ -7,7 +7,8 @@
"chai-dom",
"dayjs/plugin/timezone.d.ts",
"dayjs/plugin/utc.d.ts",
- "mocha"
+ "mocha",
+ "node"
],
"noImplicitAny": false
},
diff --git a/packages/x-date-pickers/package.json b/packages/x-date-pickers/package.json
index 72705696804c..c1992bda0d5f 100644
--- a/packages/x-date-pickers/package.json
+++ b/packages/x-date-pickers/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/x-date-pickers",
- "version": "7.7.1",
+ "version": "7.9.0",
"description": "The community edition of the Date and Time Picker components (MUI X).",
"author": "MUI Team",
"main": "src/index.ts",
@@ -47,8 +47,8 @@
"dependencies": {
"@babel/runtime": "^7.24.7",
"@mui/base": "^5.0.0-beta.40",
- "@mui/system": "^5.15.20",
- "@mui/utils": "^5.15.20",
+ "@mui/system": "^5.16.0",
+ "@mui/utils": "^5.16.0",
"@types/react-transition-group": "^4.4.10",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
@@ -98,7 +98,7 @@
}
},
"devDependencies": {
- "@mui/internal-test-utils": "^1.0.1",
+ "@mui/internal-test-utils": "^1.0.4",
"@types/luxon": "^3.4.2",
"@types/moment-hijri": "^2.1.4",
"@types/moment-jalaali": "^0.7.9",
@@ -109,9 +109,9 @@
"luxon": "^3.4.4",
"moment": "^2.30.1",
"moment-hijri": "^2.1.2",
- "moment-jalaali": "^0.10.0",
+ "moment-jalaali": "^0.10.1",
"moment-timezone": "^0.5.45",
- "rimraf": "^5.0.7"
+ "rimraf": "^5.0.8"
},
"engines": {
"node": ">=14.0.0"
diff --git a/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx b/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx
index 00382e513bbd..5c06161d17c1 100644
--- a/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx
+++ b/packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx
@@ -10,7 +10,8 @@ import {
} from '@mui/utils';
import clsx from 'clsx';
import { PickersDay, PickersDayProps, ExportedPickersDayProps } from '../PickersDay/PickersDay';
-import { useUtils, useNow, useLocaleText } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils, useNow } from '../internals/hooks/useUtils';
import { PickerOnChangeFn } from '../internals/hooks/useViews';
import { DAY_SIZE, DAY_MARGIN } from '../internals/constants/dimensions';
import {
@@ -385,7 +386,7 @@ export function DayCalendar(inProps: DayCalendarP
timezone,
});
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const [internalHasFocus, setInternalHasFocus] = useControlled({
name: 'DayCalendar',
@@ -562,10 +563,10 @@ export function DayCalendar(inProps: DayCalendarP
- {localeText.calendarWeekNumberHeaderText}
+ {translations.calendarWeekNumberHeaderText}
)}
{getWeekdays(utils, now).map((weekday, i) => (
@@ -614,11 +615,11 @@ export function DayCalendar(inProps: DayCalendarP
- {localeText.calendarWeekNumberText(utils.getWeekNumber(week[0]))}
+ {translations.calendarWeekNumberText(utils.getWeekNumber(week[0]))}
)}
{week.map((day, dayIndex) => (
diff --git a/packages/x-date-pickers/src/DateCalendar/tests/describes.DateCalendar.test.tsx b/packages/x-date-pickers/src/DateCalendar/tests/describes.DateCalendar.test.tsx
index 4d03f1df80d0..7a807f651b17 100644
--- a/packages/x-date-pickers/src/DateCalendar/tests/describes.DateCalendar.test.tsx
+++ b/packages/x-date-pickers/src/DateCalendar/tests/describes.DateCalendar.test.tsx
@@ -5,7 +5,6 @@ import { DateCalendar, dateCalendarClasses as classes } from '@mui/x-date-picker
import { pickersDayClasses } from '@mui/x-date-pickers/PickersDay';
import {
adapterToUse,
- wrapPickerMount,
createPickerRenderer,
describeValidation,
describeValue,
@@ -27,10 +26,8 @@ describe(' - Describes', () => {
inheritComponent: 'div',
render,
muiName: 'MuiDateCalendar',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
- // cannot test reactTestRenderer because of required context
- skip: ['componentProp', 'componentsProp', 'reactTestRenderer', 'themeVariants'],
+ skip: ['componentProp', 'componentsProp', 'themeVariants'],
}));
describeValue(DateCalendar, () => ({
diff --git a/packages/x-date-pickers/src/DateField/tests/describes.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/describes.DateField.test.tsx
index 1c26eeda56fb..a88c0d4e46c1 100644
--- a/packages/x-date-pickers/src/DateField/tests/describes.DateField.test.tsx
+++ b/packages/x-date-pickers/src/DateField/tests/describes.DateField.test.tsx
@@ -3,7 +3,6 @@ import { PickersTextField } from '@mui/x-date-pickers/PickersTextField';
import { DateField } from '@mui/x-date-pickers/DateField';
import {
createPickerRenderer,
- wrapPickerMount,
expectFieldValueV7,
adapterToUse,
describeValidation,
@@ -27,11 +26,8 @@ describe(' - Describes', () => {
inheritComponent: PickersTextField,
render,
muiName: 'MuiDateField',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
- // cannot test reactTestRenderer because of required context
skip: [
- 'reactTestRenderer',
'componentProp',
'componentsProp',
'themeDefaultProps',
diff --git a/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx
index 3d419cdc9240..d68fab6c666c 100644
--- a/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx
+++ b/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx
@@ -2030,13 +2030,13 @@ describe(' - Editing', () => {
fireEvent.change(input, { target: { value: initialValueStr.replace('23', '1') } });
});
- expectFieldValueV6(input, '11/21/2022');
+ expectFieldValueV6(input, '11/01/2022');
});
it('should support letter editing', () => {
// Test with v6 input
const v6Response = renderWithProps({
- defaultValue: adapter.date('2022-05-16'),
+ defaultValue: adapter.date('2022-01-16'),
format: `${adapter.formats.month} ${adapter.formats.year}`,
enableAccessibleFieldDOMStructure: false,
});
@@ -2057,10 +2057,10 @@ describe(' - Editing', () => {
fireEvent.change(input, { target: { value: ' 2022' } });
// Set the key pressed in the selected section
- fireEvent.change(input, { target: { value: 'u 2022' } });
+ fireEvent.change(input, { target: { value: 'a 2022' } });
});
- expectFieldValueV6(input, 'June 2022');
+ expectFieldValueV6(input, 'April 2022');
});
},
);
diff --git a/packages/x-date-pickers/src/DatePicker/DatePickerToolbar.tsx b/packages/x-date-pickers/src/DatePicker/DatePickerToolbar.tsx
index 366996ed1a65..46c31a964c5d 100644
--- a/packages/x-date-pickers/src/DatePicker/DatePickerToolbar.tsx
+++ b/packages/x-date-pickers/src/DatePicker/DatePickerToolbar.tsx
@@ -5,7 +5,8 @@ import Typography from '@mui/material/Typography';
import { styled, useThemeProps } from '@mui/material/styles';
import { unstable_composeClasses as composeClasses } from '@mui/utils';
import { PickersToolbar } from '../internals/components/PickersToolbar';
-import { useLocaleText, useUtils } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils } from '../internals/hooks/useUtils';
import { BaseToolbarProps, ExportedBaseToolbarProps } from '../internals/models/props/toolbar';
import { DateView, PickerValidDate } from '../models';
import {
@@ -87,7 +88,7 @@ export const DatePickerToolbar = React.forwardRef(function DatePickerToolbar<
...other
} = props;
const utils = useUtils();
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const classes = useUtilityClasses(props);
const dateText = React.useMemo(() => {
@@ -105,7 +106,7 @@ export const DatePickerToolbar = React.forwardRef(function DatePickerToolbar<
return (
- Describes', () => {
inheritComponent: PickersTextField,
render,
muiName: 'MuiDateTimeField',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
- // cannot test reactTestRenderer because of required context
skip: [
- 'reactTestRenderer',
'componentProp',
'componentsProp',
'themeDefaultProps',
diff --git a/packages/x-date-pickers/src/DateTimePicker/DateTimePickerTabs.tsx b/packages/x-date-pickers/src/DateTimePicker/DateTimePickerTabs.tsx
index 72b58d1c4f94..4e04145fcf47 100644
--- a/packages/x-date-pickers/src/DateTimePicker/DateTimePickerTabs.tsx
+++ b/packages/x-date-pickers/src/DateTimePicker/DateTimePickerTabs.tsx
@@ -7,13 +7,14 @@ import { styled, useThemeProps } from '@mui/material/styles';
import composeClasses from '@mui/utils/composeClasses';
import { TimeIcon, DateRangeIcon } from '../icons';
import { DateOrTimeViewWithMeridiem } from '../internals/models';
-import { useLocaleText } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
import {
DateTimePickerTabsClasses,
getDateTimePickerTabsUtilityClass,
} from './dateTimePickerTabsClasses';
import { BaseTabsProps, ExportedBaseTabsProps } from '../internals/models/props/tabs';
import { isDatePickerView } from '../internals/utils/date-utils';
+import { PickerValidDate } from '../models';
type TabValue = 'date' | 'time';
@@ -93,7 +94,9 @@ const DateTimePickerTabsRoot = styled(Tabs, {
*
* - [DateTimePickerTabs API](https://mui.com/x/api/date-pickers/date-time-picker-tabs/)
*/
-const DateTimePickerTabs = function DateTimePickerTabs(inProps: DateTimePickerTabsProps) {
+const DateTimePickerTabs = function DateTimePickerTabs(
+ inProps: DateTimePickerTabsProps,
+) {
const props = useThemeProps({ props: inProps, name: 'MuiDateTimePickerTabs' });
const {
dateIcon = ,
@@ -105,7 +108,7 @@ const DateTimePickerTabs = function DateTimePickerTabs(inProps: DateTimePickerTa
sx,
} = props;
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const classes = useUtilityClasses(props);
const handleChange = (event: React.SyntheticEvent, value: TabValue) => {
@@ -127,12 +130,12 @@ const DateTimePickerTabs = function DateTimePickerTabs(inProps: DateTimePickerTa
>
{dateIcon}}
/>
{timeIcon}}
/>
diff --git a/packages/x-date-pickers/src/DateTimePicker/DateTimePickerToolbar.tsx b/packages/x-date-pickers/src/DateTimePicker/DateTimePickerToolbar.tsx
index c8df143a0131..fc46e8bf0083 100644
--- a/packages/x-date-pickers/src/DateTimePicker/DateTimePickerToolbar.tsx
+++ b/packages/x-date-pickers/src/DateTimePicker/DateTimePickerToolbar.tsx
@@ -7,7 +7,8 @@ import clsx from 'clsx';
import { PickersToolbarText } from '../internals/components/PickersToolbarText';
import { PickersToolbar } from '../internals/components/PickersToolbar';
import { PickersToolbarButton } from '../internals/components/PickersToolbarButton';
-import { useLocaleText, useUtils } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils } from '../internals/hooks/useUtils';
import { BaseToolbarProps, ExportedBaseToolbarProps } from '../internals/models/props/toolbar';
import {
dateTimePickerToolbarClasses,
@@ -20,7 +21,7 @@ import { MULTI_SECTION_CLOCK_SECTION_WIDTH } from '../internals/constants/dimens
import { formatMeridiem } from '../internals/utils/date-utils';
import { MakeOptional } from '../internals/models/helpers';
import { pickersToolbarTextClasses } from '../internals/components/pickersToolbarTextClasses';
-import { pickersToolbarClasses } from '../internals';
+import { pickersToolbarClasses } from '../internals/components/pickersToolbarClasses';
import { PickerValidDate } from '../models';
export interface ExportedDateTimePickerToolbarProps extends ExportedBaseToolbarProps {
@@ -260,9 +261,9 @@ function DateTimePickerToolbar(
const showAmPmControl = Boolean(ampm && !ampmInClock);
const isDesktop = toolbarVariant === 'desktop';
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const classes = useUtilityClasses(ownerState);
- const toolbarTitle = inToolbarTitle ?? localeText.dateTimePickerToolbarTitle;
+ const toolbarTitle = inToolbarTitle ?? translations.dateTimePickerToolbarTitle;
const formatHours = (time: TDate) =>
ampm ? utils.format(time, 'hours12h') : utils.format(time, 'hours24h');
diff --git a/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx b/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx
index 5a1043e6d2d6..ef7bc618e178 100644
--- a/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx
+++ b/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx
@@ -5,7 +5,8 @@ import { refType } from '@mui/utils';
import { singleItemValueManager } from '../internals/utils/valueManagers';
import { DesktopDatePickerProps } from './DesktopDatePicker.types';
import { DatePickerViewRenderers, useDatePickerDefaultizedProps } from '../DatePicker/shared';
-import { useLocaleText, useUtils } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils } from '../internals/hooks/useUtils';
import { validateDate } from '../internals/utils/validation/validateDate';
import { DateView, PickerValidDate } from '../models';
import { useDesktopPicker } from '../internals/hooks/useDesktopPicker';
@@ -40,7 +41,7 @@ const DesktopDatePicker = React.forwardRef(function DesktopDatePicker<
inProps: DesktopDatePickerProps,
ref: React.Ref,
) {
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const utils = useUtils();
// Props with the default values common to all date pickers
@@ -91,7 +92,7 @@ const DesktopDatePicker = React.forwardRef(function DesktopDatePicker<
valueManager: singleItemValueManager,
valueType: 'date',
getOpenDialogAriaText:
- props.localeText?.openDatePickerDialogue ?? localeText.openDatePickerDialogue,
+ props.localeText?.openDatePickerDialogue ?? translations.openDatePickerDialogue,
validator: validateDate,
});
diff --git a/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx b/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx
index 3d615adc120f..b3ed68ab6851 100644
--- a/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx
+++ b/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx
@@ -85,7 +85,7 @@ describe(' ', () => {
openPicker({ type: 'date', variant: 'desktop' });
- expect(screen.getByRole('radio', { checked: true, name: '2018' })).to.not.equal(null);
+ expect(screen.getByRole('radio', { checked: true, name: '2018' })).not.to.equal(null);
// Dismiss the picker
userEvent.keyPress(document.activeElement!, { key: 'Escape' });
@@ -95,7 +95,7 @@ describe(' ', () => {
clock.runToLast();
// should have changed the open view
- expect(screen.getByRole('radio', { checked: true, name: 'January' })).to.not.equal(null);
+ expect(screen.getByRole('radio', { checked: true, name: 'January' })).not.to.equal(null);
});
it('should move the focus to the newly opened views', function test() {
@@ -122,7 +122,7 @@ describe(' ', () => {
openPicker({ type: 'date', variant: 'desktop' });
- expect(screen.getByRole('radio', { checked: true, name: 'January' })).to.not.equal(null);
+ expect(screen.getByRole('radio', { checked: true, name: 'January' })).not.to.equal(null);
// Dismiss the picker
userEvent.keyPress(document.activeElement!, { key: 'Escape' });
@@ -132,7 +132,7 @@ describe(' ', () => {
clock.runToLast();
// should have changed the open view
- expect(screen.getByRole('radio', { checked: true, name: '2018' })).to.not.equal(null);
+ expect(screen.getByRole('radio', { checked: true, name: '2018' })).not.to.equal(null);
});
});
@@ -322,7 +322,7 @@ describe(' ', () => {
/>,
);
- expect(document.querySelector(`.${inputBaseClasses.error}`)).to.not.equal(null);
+ expect(document.querySelector(`.${inputBaseClasses.error}`)).not.to.equal(null);
});
it('should enable the input error state when the current date has an invalid month', () => {
@@ -333,7 +333,7 @@ describe(' ', () => {
/>,
);
- expect(document.querySelector(`.${inputBaseClasses.error}`)).to.not.equal(null);
+ expect(document.querySelector(`.${inputBaseClasses.error}`)).not.to.equal(null);
});
it('should enable the input error state when the current date has an invalid year', () => {
@@ -344,7 +344,7 @@ describe(' ', () => {
/>,
);
- expect(document.querySelector(`.${inputBaseClasses.error}`)).to.not.equal(null);
+ expect(document.querySelector(`.${inputBaseClasses.error}`)).not.to.equal(null);
});
});
diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx
index be5eb451549d..42695c7bb8e0 100644
--- a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx
+++ b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx
@@ -11,7 +11,8 @@ import {
DateTimePickerViewRenderers,
} from '../DateTimePicker/shared';
import { renderDateViewCalendar } from '../dateViewRenderers/dateViewRenderers';
-import { useLocaleText, useUtils } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils } from '../internals/hooks/useUtils';
import { validateDateTime } from '../internals/utils/validation/validateDateTime';
import { DateOrTimeViewWithMeridiem } from '../internals/models';
import { CalendarIcon } from '../icons';
@@ -28,19 +29,18 @@ import {
renderDigitalClockTimeView,
renderMultiSectionDigitalClockTimeView,
} from '../timeViewRenderers';
-import {
- DefaultizedProps,
- UsePickerViewsProps,
- VIEW_HEIGHT,
- isDatePickerView,
- isInternalTimeView,
-} from '../internals';
+
import {
multiSectionDigitalClockClasses,
multiSectionDigitalClockSectionClasses,
} from '../MultiSectionDigitalClock';
import { digitalClockClasses } from '../DigitalClock';
import { DesktopDateTimePickerLayout } from './DesktopDateTimePickerLayout';
+import { VIEW_HEIGHT } from '../internals/constants/dimensions';
+import { DefaultizedProps } from '../internals/models/helpers';
+import { UsePickerViewsProps } from '../internals/hooks/usePicker/usePickerViews';
+import { isInternalTimeView } from '../internals/utils/time-utils';
+import { isDatePickerView } from '../internals/utils/date-utils';
const rendererInterceptor = function rendererInterceptor<
TDate extends PickerValidDate,
@@ -134,7 +134,7 @@ const DesktopDateTimePicker = React.forwardRef(function DesktopDateTimePicker<
inProps: DesktopDateTimePickerProps,
ref: React.Ref,
) {
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const utils = useUtils();
// Props with the default values common to all date time pickers
@@ -227,7 +227,7 @@ const DesktopDateTimePicker = React.forwardRef(function DesktopDateTimePicker<
valueManager: singleItemValueManager,
valueType: 'date-time',
getOpenDialogAriaText:
- props.localeText?.openDatePickerDialogue ?? localeText.openDatePickerDialogue,
+ props.localeText?.openDatePickerDialogue ?? translations.openDatePickerDialogue,
validator: validateDateTime,
rendererInterceptor,
});
diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePickerLayout.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePickerLayout.tsx
index fca2a0f44898..85b531fef636 100644
--- a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePickerLayout.tsx
+++ b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePickerLayout.tsx
@@ -11,7 +11,7 @@ import {
usePickerLayout,
} from '../PickersLayout';
import { PickerValidDate } from '../models';
-import { DateOrTimeViewWithMeridiem } from '../internals';
+import { DateOrTimeViewWithMeridiem } from '../internals/models/common';
/**
* @ignore - internal component.
diff --git a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx
index d0a2d8b38c20..63a21269ac3b 100644
--- a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx
+++ b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx
@@ -6,7 +6,8 @@ import { singleItemValueManager } from '../internals/utils/valueManagers';
import { TimeField } from '../TimeField';
import { DesktopTimePickerProps } from './DesktopTimePicker.types';
import { TimePickerViewRenderers, useTimePickerDefaultizedProps } from '../TimePicker/shared';
-import { useLocaleText, useUtils } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils } from '../internals/hooks/useUtils';
import { validateTime } from '../internals/utils/validation/validateTime';
import { ClockIcon } from '../icons';
import { useDesktopPicker } from '../internals/hooks/useDesktopPicker';
@@ -46,7 +47,7 @@ const DesktopTimePicker = React.forwardRef(function DesktopTimePicker<
inProps: DesktopTimePickerProps,
ref: React.Ref,
) {
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const utils = useUtils();
// Props with the default values common to all time pickers
@@ -129,7 +130,7 @@ const DesktopTimePicker = React.forwardRef(function DesktopTimePicker<
valueManager: singleItemValueManager,
valueType: 'time',
getOpenDialogAriaText:
- props.localeText?.openTimePickerDialogue ?? localeText.openTimePickerDialogue,
+ props.localeText?.openTimePickerDialogue ?? translations.openTimePickerDialogue,
validator: validateTime,
});
diff --git a/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx
index 38219d321165..36a9cc7288aa 100644
--- a/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx
+++ b/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx
@@ -2,7 +2,6 @@ import * as React from 'react';
import { screen, userEvent } from '@mui/internal-test-utils';
import {
createPickerRenderer,
- wrapPickerMount,
adapterToUse,
expectFieldValueV7,
describeValidation,
@@ -35,7 +34,6 @@ describe(' - Describes', () => {
classes: {} as any,
render,
muiName: 'MuiDesktopTimePicker',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
skip: [
'componentProp',
@@ -46,7 +44,6 @@ describe(' - Describes', () => {
'mergeClassName',
'propsSpread',
'rootClass',
- 'reactTestRenderer',
],
}));
diff --git a/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx b/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx
index bfd5ae2efc29..81d214a13049 100644
--- a/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx
+++ b/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx
@@ -8,7 +8,8 @@ import composeClasses from '@mui/utils/composeClasses';
import MenuItem from '@mui/material/MenuItem';
import MenuList from '@mui/material/MenuList';
import useForkRef from '@mui/utils/useForkRef';
-import { useUtils, useNow, useLocaleText } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils, useNow } from '../internals/hooks/useUtils';
import { createIsAfterIgnoreDatePart } from '../internals/utils/time-utils';
import { PickerViewRoot } from '../internals/components/PickerViewRoot';
import { getDigitalClockUtilityClass } from './digitalClockClasses';
@@ -163,7 +164,7 @@ export const DigitalClock = React.forwardRef(function DigitalClock();
+ const translations = usePickersTranslations();
const now = useNow(timezone);
const ownerState = React.useMemo(
@@ -301,7 +302,7 @@ export const DigitalClock = React.forwardRef(function DigitalClock
{timeOptions.map((option, index) => {
diff --git a/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx b/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx
index c4eb1f60a69c..380c056fa557 100644
--- a/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx
+++ b/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx
@@ -3,7 +3,6 @@ import { expect } from 'chai';
import { screen } from '@mui/internal-test-utils';
import {
createPickerRenderer,
- wrapPickerMount,
adapterToUse,
digitalClockHandler,
describeValidation,
@@ -28,7 +27,6 @@ describe(' - Describes', () => {
classes: {} as any,
render,
muiName: 'MuiDigitalClock',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
skip: [
'componentProp',
@@ -39,7 +37,6 @@ describe(' - Describes', () => {
'mergeClassName',
'propsSpread',
'rootClass',
- 'reactTestRenderer',
],
}));
diff --git a/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx b/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx
index 23fe6ab1943e..870bee0911cf 100644
--- a/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx
+++ b/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx
@@ -5,7 +5,8 @@ import { refType } from '@mui/utils';
import { useMobilePicker } from '../internals/hooks/useMobilePicker';
import { MobileDatePickerProps } from './MobileDatePicker.types';
import { DatePickerViewRenderers, useDatePickerDefaultizedProps } from '../DatePicker/shared';
-import { useUtils, useLocaleText } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils } from '../internals/hooks/useUtils';
import { validateDate } from '../internals/utils/validation/validateDate';
import { DateView, PickerValidDate } from '../models';
import { DateField } from '../DateField';
@@ -39,7 +40,7 @@ const MobileDatePicker = React.forwardRef(function MobileDatePicker<
inProps: MobileDatePickerProps,
ref: React.Ref,
) {
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const utils = useUtils();
// Props with the default values common to all date pickers
@@ -88,7 +89,7 @@ const MobileDatePicker = React.forwardRef(function MobileDatePicker<
valueManager: singleItemValueManager,
valueType: 'date',
getOpenDialogAriaText:
- props.localeText?.openDatePickerDialogue ?? localeText.openDatePickerDialogue,
+ props.localeText?.openDatePickerDialogue ?? translations.openDatePickerDialogue,
validator: validateDate,
});
diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx b/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx
index 9964c55a3948..8c4aa9ac36e2 100644
--- a/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx
+++ b/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx
@@ -9,7 +9,8 @@ import {
DateTimePickerViewRenderers,
useDateTimePickerDefaultizedProps,
} from '../DateTimePicker/shared';
-import { useLocaleText, useUtils } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils } from '../internals/hooks/useUtils';
import { validateDateTime } from '../internals/utils/validation/validateDateTime';
import { DateOrTimeView, PickerValidDate } from '../models';
import { useMobilePicker } from '../internals/hooks/useMobilePicker';
@@ -43,7 +44,7 @@ const MobileDateTimePicker = React.forwardRef(function MobileDateTimePicker<
inProps: MobileDateTimePickerProps,
ref: React.Ref,
) {
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const utils = useUtils();
// Props with the default values common to all date time pickers
@@ -103,7 +104,7 @@ const MobileDateTimePicker = React.forwardRef(function MobileDateTimePicker<
valueManager: singleItemValueManager,
valueType: 'date-time',
getOpenDialogAriaText:
- props.localeText?.openDatePickerDialogue ?? localeText.openDatePickerDialogue,
+ props.localeText?.openDatePickerDialogue ?? translations.openDatePickerDialogue,
validator: validateDateTime,
});
diff --git a/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx b/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx
index 49e09712163b..159260767130 100644
--- a/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx
+++ b/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx
@@ -6,7 +6,8 @@ import { singleItemValueManager } from '../internals/utils/valueManagers';
import { TimeField } from '../TimeField';
import { MobileTimePickerProps } from './MobileTimePicker.types';
import { TimePickerViewRenderers, useTimePickerDefaultizedProps } from '../TimePicker/shared';
-import { useLocaleText, useUtils } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils } from '../internals/hooks/useUtils';
import { validateTime } from '../internals/utils/validation/validateTime';
import { PickerValidDate, TimeView } from '../models';
import { useMobilePicker } from '../internals/hooks/useMobilePicker';
@@ -39,7 +40,7 @@ const MobileTimePicker = React.forwardRef(function MobileTimePicker<
inProps: MobileTimePickerProps,
ref: React.Ref,
) {
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const utils = useUtils();
// Props with the default values common to all time pickers
@@ -92,7 +93,7 @@ const MobileTimePicker = React.forwardRef(function MobileTimePicker<
valueManager: singleItemValueManager,
valueType: 'time',
getOpenDialogAriaText:
- props.localeText?.openTimePickerDialogue ?? localeText.openTimePickerDialogue,
+ props.localeText?.openTimePickerDialogue ?? translations.openTimePickerDialogue,
validator: validateTime,
});
diff --git a/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx b/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx
index 39db06fea3e0..ec8a060c935f 100644
--- a/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx
+++ b/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx
@@ -2,7 +2,6 @@ import * as React from 'react';
import { screen, fireEvent, userEvent, fireTouchChangedEvent } from '@mui/internal-test-utils';
import {
createPickerRenderer,
- wrapPickerMount,
adapterToUse,
expectFieldValueV7,
openPicker,
@@ -36,7 +35,6 @@ describe(' - Describes', () => {
classes: {} as any,
render,
muiName: 'MuiMobileTimePicker',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
skip: [
'componentProp',
@@ -47,7 +45,6 @@ describe(' - Describes', () => {
'mergeClassName',
'propsSpread',
'rootClass',
- 'reactTestRenderer',
],
}));
diff --git a/packages/x-date-pickers/src/MonthCalendar/tests/MonthCalendar.test.tsx b/packages/x-date-pickers/src/MonthCalendar/tests/MonthCalendar.test.tsx
index 98cb6e98686e..72037d514c1c 100644
--- a/packages/x-date-pickers/src/MonthCalendar/tests/MonthCalendar.test.tsx
+++ b/packages/x-date-pickers/src/MonthCalendar/tests/MonthCalendar.test.tsx
@@ -165,7 +165,7 @@ describe(' ', () => {
it('should not mark the `referenceDate` month as selected', () => {
render( );
- expect(screen.getByRole('radio', { name: 'February', checked: false })).to.not.equal(null);
+ expect(screen.getByRole('radio', { name: 'February', checked: false })).not.to.equal(null);
});
});
});
diff --git a/packages/x-date-pickers/src/MonthCalendar/tests/describes.MonthCalendar.test.tsx b/packages/x-date-pickers/src/MonthCalendar/tests/describes.MonthCalendar.test.tsx
index e34093f7fb33..f439b851f2c3 100644
--- a/packages/x-date-pickers/src/MonthCalendar/tests/describes.MonthCalendar.test.tsx
+++ b/packages/x-date-pickers/src/MonthCalendar/tests/describes.MonthCalendar.test.tsx
@@ -2,7 +2,6 @@ import * as React from 'react';
import { expect } from 'chai';
import { userEvent, screen } from '@mui/internal-test-utils';
import {
- wrapPickerMount,
createPickerRenderer,
adapterToUse,
describeValidation,
@@ -25,11 +24,9 @@ describe(' - Describes', () => {
classes,
inheritComponent: 'div',
render,
- wrapMount: wrapPickerMount,
muiName: 'MuiMonthCalendar',
refInstanceof: window.HTMLDivElement,
- // cannot test reactTestRenderer because of required context
- skip: ['componentProp', 'componentsProp', 'reactTestRenderer', 'themeVariants'],
+ skip: ['componentProp', 'componentsProp', 'themeVariants'],
}));
describeValue(MonthCalendar, () => ({
diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx
index c4fcc16f1a83..bf30b978c580 100644
--- a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx
+++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx
@@ -5,7 +5,8 @@ import { useRtl } from '@mui/system/RtlProvider';
import { styled, useThemeProps } from '@mui/material/styles';
import useEventCallback from '@mui/utils/useEventCallback';
import composeClasses from '@mui/utils/composeClasses';
-import { useUtils, useNow, useLocaleText } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils, useNow } from '../internals/hooks/useUtils';
import { convertValueToMeridiem, createIsAfterIgnoreDatePart } from '../internals/utils/time-utils';
import { useViews } from '../internals/hooks/useViews';
import type { PickerSelectionState } from '../internals/hooks/usePicker';
@@ -114,7 +115,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi
valueManager: singleItemValueManager,
});
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const now = useNow(timezone);
const timeSteps = React.useMemo>(
@@ -296,7 +297,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi
utils,
isDisabled: (hours) => isTimeDisabled(hours, 'hours'),
timeStep: timeSteps.hours,
- resolveAriaLabel: localeText.hoursClockNumberText,
+ resolveAriaLabel: translations.hoursClockNumberText,
valueOrReferenceDate,
}),
};
@@ -318,7 +319,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi
resolveLabel: (minutes) => utils.format(utils.setMinutes(now, minutes), 'minutes'),
timeStep: timeSteps.minutes,
hasValue: !!value,
- resolveAriaLabel: localeText.minutesClockNumberText,
+ resolveAriaLabel: translations.minutesClockNumberText,
}),
};
}
@@ -339,7 +340,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi
resolveLabel: (seconds) => utils.format(utils.setSeconds(now, seconds), 'seconds'),
timeStep: timeSteps.seconds,
hasValue: !!value,
- resolveAriaLabel: localeText.secondsClockNumberText,
+ resolveAriaLabel: translations.secondsClockNumberText,
}),
};
}
@@ -380,9 +381,9 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi
timeSteps.hours,
timeSteps.minutes,
timeSteps.seconds,
- localeText.hoursClockNumberText,
- localeText.minutesClockNumberText,
- localeText.secondsClockNumberText,
+ translations.hoursClockNumberText,
+ translations.minutesClockNumberText,
+ translations.secondsClockNumberText,
meridiemMode,
setValueAndGoToNextView,
valueOrReferenceDate,
@@ -396,7 +397,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi
return views;
}
const digitViews = views.filter((v) => v !== 'meridiem');
- const result = digitViews.toReversed();
+ const result: TimeViewWithMeridiem[] = digitViews.toReversed();
if (views.includes('meridiem')) {
result.push('meridiem');
}
@@ -435,7 +436,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi
slots={slots}
slotProps={slotProps}
skipDisabled={skipDisabled}
- aria-label={localeText.selectViewText(timeView)}
+ aria-label={translations.selectViewText(timeView)}
/>
))}
diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx
index ed61d90f3988..87b19d1f7fc5 100644
--- a/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx
+++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/describes.MultiSectionDigitalClock.test.tsx
@@ -3,7 +3,6 @@ import { expect } from 'chai';
import { screen } from '@mui/internal-test-utils';
import {
createPickerRenderer,
- wrapPickerMount,
adapterToUse,
multiSectionDigitalClockHandler,
describeValidation,
@@ -28,7 +27,6 @@ describe(' - Describes', () => {
classes: {} as any,
render,
muiName: 'MuiMultiSectionDigitalClock',
- wrapMount: wrapPickerMount,
refInstanceof: window.HTMLDivElement,
skip: [
'componentProp',
@@ -39,7 +37,6 @@ describe(' - Describes', () => {
'mergeClassName',
'propsSpread',
'rootClass',
- 'reactTestRenderer',
],
}));
diff --git a/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.tsx b/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.tsx
index f2f5f05ffe38..3b12b4353d02 100644
--- a/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.tsx
+++ b/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import Button from '@mui/material/Button';
import DialogActions, { DialogActionsProps } from '@mui/material/DialogActions';
-import { useLocaleText } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
export type PickersActionBarAction = 'clear' | 'cancel' | 'accept' | 'today';
@@ -32,7 +32,7 @@ export interface PickersActionBarProps extends DialogActionsProps {
function PickersActionBar(props: PickersActionBarProps) {
const { onAccept, onClear, onCancel, onSetToday, actions, ...other } = props;
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
if (actions == null || actions.length === 0) {
return null;
@@ -43,28 +43,28 @@ function PickersActionBar(props: PickersActionBarProps) {
case 'clear':
return (
- {localeText.clearButtonLabel}
+ {translations.clearButtonLabel}
);
case 'cancel':
return (
- {localeText.cancelButtonLabel}
+ {translations.cancelButtonLabel}
);
case 'accept':
return (
- {localeText.okButtonLabel}
+ {translations.okButtonLabel}
);
case 'today':
return (
- {localeText.todayButtonLabel}
+ {translations.todayButtonLabel}
);
diff --git a/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.tsx b/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.tsx
index d49252d9c441..8412b01d8848 100644
--- a/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.tsx
+++ b/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.tsx
@@ -6,7 +6,8 @@ import { styled, useThemeProps } from '@mui/material/styles';
import { useSlotProps } from '@mui/base/utils';
import { unstable_composeClasses as composeClasses } from '@mui/utils';
import IconButton from '@mui/material/IconButton';
-import { useLocaleText, useUtils } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils } from '../internals/hooks/useUtils';
import { PickersFadeTransitionGroup } from '../DateCalendar/PickersFadeTransitionGroup';
import { ArrowDropDownIcon } from '../icons';
import { PickersArrowSwitcher } from '../internals/components/PickersArrowSwitcher';
@@ -129,7 +130,7 @@ type PickersCalendarHeaderComponent = ((
const PickersCalendarHeader = React.forwardRef(function PickersCalendarHeader<
TDate extends PickerValidDate,
>(inProps: PickersCalendarHeaderProps, ref: React.Ref) {
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const utils = useUtils();
const props = useThemeProps({ props: inProps, name: 'MuiPickersCalendarHeader' });
@@ -165,7 +166,7 @@ const PickersCalendarHeader = React.forwardRef(function PickersCalendarHeader<
externalSlotProps: slotProps?.switchViewButton,
additionalProps: {
size: 'small',
- 'aria-label': localeText.calendarViewSwitchingButtonAriaLabel(view),
+ 'aria-label': translations.calendarViewSwitchingButtonAriaLabel(view),
},
ownerState,
className: classes.switchViewButton,
@@ -252,10 +253,10 @@ const PickersCalendarHeader = React.forwardRef(function PickersCalendarHeader<
slotProps={slotProps}
onGoToPrevious={selectPreviousMonth}
isPreviousDisabled={isPreviousMonthDisabled}
- previousLabel={localeText.previousMonth}
+ previousLabel={translations.previousMonth}
onGoToNext={selectNextMonth}
isNextDisabled={isNextMonthDisabled}
- nextLabel={localeText.nextMonth}
+ nextLabel={translations.nextMonth}
/>
diff --git a/packages/x-date-pickers/src/PickersDay/PickersDay.test.tsx b/packages/x-date-pickers/src/PickersDay/PickersDay.test.tsx
index 361f7d7ae798..5afe172c0b1f 100644
--- a/packages/x-date-pickers/src/PickersDay/PickersDay.test.tsx
+++ b/packages/x-date-pickers/src/PickersDay/PickersDay.test.tsx
@@ -4,7 +4,7 @@ import { spy } from 'sinon';
import { fireEvent, screen } from '@mui/internal-test-utils';
import ButtonBase from '@mui/material/ButtonBase';
import { PickersDay, pickersDayClasses as classes } from '@mui/x-date-pickers/PickersDay';
-import { adapterToUse, wrapPickerMount, createPickerRenderer } from 'test/utils/pickers';
+import { adapterToUse, createPickerRenderer } from 'test/utils/pickers';
import { describeConformance } from 'test/utils/describeConformance';
describe(' ', () => {
@@ -23,12 +23,10 @@ describe(' ', () => {
classes,
inheritComponent: ButtonBase,
render,
- wrapMount: wrapPickerMount,
muiName: 'MuiPickersDay',
refInstanceof: window.HTMLButtonElement,
testVariantProps: { variant: 'disableMargin' },
- // cannot test reactTestRenderer because of required context
- skip: ['componentProp', 'componentsProp', 'reactTestRenderer'],
+ skip: ['componentProp', 'componentsProp'],
}),
);
diff --git a/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx b/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx
index 7cb2ef91eaae..3eafe702915b 100644
--- a/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx
+++ b/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx
@@ -2,12 +2,7 @@ import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
import { fireTouchChangedEvent, screen, getAllByRole, fireEvent } from '@mui/internal-test-utils';
-import {
- adapterToUse,
- wrapPickerMount,
- createPickerRenderer,
- describeValidation,
-} from 'test/utils/pickers';
+import { adapterToUse, createPickerRenderer, describeValidation } from 'test/utils/pickers';
import { StaticTimePicker } from '@mui/x-date-pickers/StaticTimePicker';
import { describeConformance } from 'test/utils/describeConformance';
@@ -28,7 +23,6 @@ describe(' ', () => {
classes: {} as any,
render,
muiName: 'MuiStaticTimePicker',
- wrapMount: wrapPickerMount,
refInstanceof: undefined,
skip: [
'componentProp',
@@ -41,7 +35,6 @@ describe(' ', () => {
// TODO: `ref` is typed but has no effect
'refForwarding',
'rootClass',
- 'reactTestRenderer',
],
}));
diff --git a/packages/x-date-pickers/src/TimeClock/Clock.tsx b/packages/x-date-pickers/src/TimeClock/Clock.tsx
index 23a583f9dd71..26d69784720d 100644
--- a/packages/x-date-pickers/src/TimeClock/Clock.tsx
+++ b/packages/x-date-pickers/src/TimeClock/Clock.tsx
@@ -8,7 +8,8 @@ import {
unstable_composeClasses as composeClasses,
} from '@mui/utils';
import { ClockPointer } from './ClockPointer';
-import { useLocaleText, useUtils } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils } from '../internals/hooks/useUtils';
import type { PickerSelectionState } from '../internals/hooks/usePicker';
import { useMeridiemMode } from '../internals/hooks/date-helpers-hooks';
import { CLOCK_HOUR_WIDTH, getHours, getMinutes } from './shared';
@@ -233,7 +234,7 @@ export function Clock(inProps: ClockProps)
const ownerState = props;
const utils = useUtils();
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const isMoving = React.useRef(false);
const classes = useUtilityClasses(ownerState);
@@ -372,7 +373,7 @@ export function Clock(inProps: ClockProps)
)}
();
+ const translations = usePickersTranslations();
const now = useNow(timezone);
const { view, setView, previousView, nextView, setValueAndGoToNextView } = useViews({
@@ -269,7 +270,7 @@ export const TimeClock = React.forwardRef(function TimeClock disabled || isTimeDisabled(hourValue, 'hours'),
selectedId,
}),
@@ -293,7 +294,7 @@ export const TimeClock = React.forwardRef(function TimeClock disabled || isTimeDisabled(minuteValue, 'minutes'),
selectedId,
}),
@@ -317,7 +318,7 @@ export const TimeClock = React.forwardRef(function TimeClock disabled || isTimeDisabled(secondValue, 'seconds'),
selectedId,
}),
@@ -332,9 +333,9 @@ export const TimeClock = React.forwardRef(function TimeClock setView(previousView!)}
isPreviousDisabled={!previousView}
- previousLabel={localeText.openPreviousView}
+ previousLabel={translations.openPreviousView}
onGoToNext={() => setView(nextView!)}
isNextDisabled={!nextView}
- nextLabel={localeText.openNextView}
+ nextLabel={translations.openNextView}
ownerState={ownerState}
/>
)}
diff --git a/packages/x-date-pickers/src/TimeClock/tests/describes.TimeClock.test.tsx b/packages/x-date-pickers/src/TimeClock/tests/describes.TimeClock.test.tsx
index f2b06f645f4c..dc295643b47c 100644
--- a/packages/x-date-pickers/src/TimeClock/tests/describes.TimeClock.test.tsx
+++ b/packages/x-date-pickers/src/TimeClock/tests/describes.TimeClock.test.tsx
@@ -7,7 +7,6 @@ import {
timeClockClasses as classes,
} from '@mui/x-date-pickers/TimeClock';
import {
- wrapPickerMount,
createPickerRenderer,
adapterToUse,
timeClockHandler,
@@ -21,11 +20,10 @@ describe(' - Describes', () => {
describeConformance( , () => ({
classes,
inheritComponent: 'div',
- wrapMount: wrapPickerMount,
render,
refInstanceof: window.HTMLDivElement,
muiName: 'MuiTimeClock',
- skip: ['componentProp', 'componentsProp', 'reactTestRenderer', 'themeVariants'],
+ skip: ['componentProp', 'componentsProp', 'themeVariants'],
}));
describeValue(TimeClock, () => ({
diff --git a/packages/x-date-pickers/src/TimePicker/TimePickerToolbar.tsx b/packages/x-date-pickers/src/TimePicker/TimePickerToolbar.tsx
index a24f68cf5711..ef2d545cfad0 100644
--- a/packages/x-date-pickers/src/TimePicker/TimePickerToolbar.tsx
+++ b/packages/x-date-pickers/src/TimePicker/TimePickerToolbar.tsx
@@ -8,7 +8,8 @@ import { PickersToolbarText } from '../internals/components/PickersToolbarText';
import { PickersToolbarButton } from '../internals/components/PickersToolbarButton';
import { PickersToolbar } from '../internals/components/PickersToolbar';
import { arrayIncludes } from '../internals/utils/utils';
-import { useLocaleText, useUtils } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from '../hooks/usePickersTranslations';
+import { useUtils } from '../internals/hooks/useUtils';
import { useMeridiemMode } from '../internals/hooks/date-helpers-hooks';
import { BaseToolbarProps, ExportedBaseToolbarProps } from '../internals/models/props/toolbar';
import {
@@ -165,7 +166,7 @@ function TimePickerToolbar(inProps: TimePickerToo
...other
} = props;
const utils = useUtils();
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const isRtl = useRtl();
const showAmPmControl = Boolean(ampm && !ampmInClock && views.includes('hours'));
@@ -190,7 +191,7 @@ function TimePickerToolbar(inProps: TimePickerToo
return (
', () => {
it('should not mark the `referenceDate` year as selected', () => {
render( );
- expect(screen.getByRole('radio', { name: '2018', checked: false })).to.not.equal(null);
+ expect(screen.getByRole('radio', { name: '2018', checked: false })).not.to.equal(null);
});
});
diff --git a/packages/x-date-pickers/src/YearCalendar/tests/describes.YearCalendar.test.tsx b/packages/x-date-pickers/src/YearCalendar/tests/describes.YearCalendar.test.tsx
index c93548b4ec59..e2b1c722d71a 100644
--- a/packages/x-date-pickers/src/YearCalendar/tests/describes.YearCalendar.test.tsx
+++ b/packages/x-date-pickers/src/YearCalendar/tests/describes.YearCalendar.test.tsx
@@ -3,7 +3,6 @@ import { expect } from 'chai';
import { userEvent, screen } from '@mui/internal-test-utils';
import { YearCalendar, yearCalendarClasses as classes } from '@mui/x-date-pickers/YearCalendar';
import {
- wrapPickerMount,
createPickerRenderer,
adapterToUse,
describeValidation,
@@ -26,12 +25,10 @@ describe(' - Describes', () => {
describeConformance( , () => ({
classes,
inheritComponent: 'div',
- wrapMount: wrapPickerMount,
render,
muiName: 'MuiYearCalendar',
refInstanceof: window.HTMLDivElement,
- // cannot test reactTestRenderer because of required context
- skip: ['componentProp', 'componentsProp', 'reactTestRenderer', 'themeVariants'],
+ skip: ['componentProp', 'componentsProp', 'themeVariants'],
}));
describeValue(YearCalendar, () => ({
diff --git a/packages/x-date-pickers/src/hooks/index.tsx b/packages/x-date-pickers/src/hooks/index.tsx
index 40f52b8c93d7..99cde931f0f5 100644
--- a/packages/x-date-pickers/src/hooks/index.tsx
+++ b/packages/x-date-pickers/src/hooks/index.tsx
@@ -5,3 +5,5 @@ export type {
UseClearableFieldSlotProps,
UseClearableFieldResponse,
} from './useClearableField';
+
+export { usePickersTranslations } from './usePickersTranslations';
diff --git a/packages/x-date-pickers/src/hooks/useClearableField.tsx b/packages/x-date-pickers/src/hooks/useClearableField.tsx
index 27815914b83c..fb06acdfbd90 100644
--- a/packages/x-date-pickers/src/hooks/useClearableField.tsx
+++ b/packages/x-date-pickers/src/hooks/useClearableField.tsx
@@ -4,7 +4,7 @@ import MuiIconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
import { SxProps } from '@mui/system';
import { ClearIcon } from '../icons';
-import { useLocaleText } from '../internals/hooks/useUtils';
+import { usePickersTranslations } from './usePickersTranslations';
export interface ExportedUseClearableFieldProps {
/**
@@ -51,7 +51,7 @@ export type UseClearableFieldResponse(
props: TFieldProps,
): UseClearableFieldResponse => {
- const localeText = useLocaleText();
+ const translations = usePickersTranslations();
const { clearable, onClear, InputProps, sx, slots, slotProps, ...other } = props;
@@ -63,7 +63,7 @@ export const useClearableField = (
ownerState: {},
className: 'clearButton',
additionalProps: {
- title: localeText.fieldClearLabel,
+ title: translations.fieldClearLabel,
},
});
const EndClearIcon = slots?.clearIcon ?? ClearIcon;
diff --git a/packages/x-date-pickers/src/hooks/usePickersTranslations.ts b/packages/x-date-pickers/src/hooks/usePickersTranslations.ts
new file mode 100644
index 000000000000..6ad1a7d4bcc2
--- /dev/null
+++ b/packages/x-date-pickers/src/hooks/usePickersTranslations.ts
@@ -0,0 +1,5 @@
+import { PickerValidDate } from '../models';
+import { useLocalizationContext } from '../internals/hooks/useUtils';
+
+export const usePickersTranslations = () =>
+ useLocalizationContext().localeText;
diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts b/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts
index c6fadb4b23d2..23e6bbc66900 100644
--- a/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts
+++ b/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts
@@ -1,7 +1,8 @@
import * as React from 'react';
import useControlled from '@mui/utils/useControlled';
import { useRtl } from '@mui/system/RtlProvider';
-import { useUtils, useLocaleText, useLocalizationContext } from '../useUtils';
+import { usePickersTranslations } from '../../../hooks/usePickersTranslations';
+import { useUtils, useLocalizationContext } from '../useUtils';
import {
UseFieldInternalProps,
UseFieldParams,
@@ -86,7 +87,7 @@ export const useFieldState = <
>,
): UseFieldStateResponse => {
const utils = useUtils();
- const localeText = useLocaleText