diff --git a/.circleci/config.yml b/.circleci/config.yml index 53f9f97eed6cd..72bff75bfe512 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -99,6 +99,14 @@ commands: # log a patch for maintainers who want to check out this change git --no-pager diff HEAD + - when: + condition: + equal: [material-ui-v6, << pipeline.parameters.workflow >>] + steps: + - run: + name: Install @mui/material@next + command: pnpm use-material-ui-v6 + - when: condition: << parameters.browsers >> steps: @@ -248,7 +256,7 @@ jobs: command: pnpm docs:typescript:formatted --disable-cache - run: name: '`pnpm docs:typescript:formatted` changes committed?' - command: git add -A && git diff --exit-code --staged + command: git add -A && git diff --exit-code --staged docs/src docs/data - run: name: Tests TypeScript definitions command: pnpm typescript:ci @@ -383,3 +391,23 @@ workflows: <<: *default-context react-version: next name: test_e2e-react@next + + material-ui-v6: + when: + equal: [material-ui-v6, << pipeline.parameters.workflow >>] + jobs: + - test_unit: + <<: *default-context + name: test_unit-material@next + - test_browser: + <<: *default-context + name: test_browser-material@next + - test_regressions: + <<: *default-context + name: test_regressions-material@next + - test_e2e: + <<: *default-context + name: test_e2e-material@next + - test_types: + <<: *default-context + name: test_types-material@next diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index f709cfa8489c1..f46a89cf56985 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -11,8 +11,10 @@ "packages/x-date-pickers", "packages/x-date-pickers-pro", "packages/x-charts", + "packages/x-charts-pro", "packages/x-charts-vendor", "packages/x-tree-view", + "packages/x-tree-view-pro", "packages/x-internals" ], "publishDirectory": { diff --git a/.eslintrc.js b/.eslintrc.js index 1227ab308c00a..d7b85f4b16dec 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -43,6 +43,17 @@ const addReactCompilerRule = (packagesNames, isEnabled) => }, })); +const RESTRICTED_TOP_LEVEL_IMPORTS = [ + '@mui/material', + '@mui/x-charts', + '@mui/x-charts-pro', + '@mui/x-codemod', + '@mui/x-date-pickers', + '@mui/x-date-pickers-pro', + '@mui/x-tree-view', + '@mui/x-tree-view-pro', +]; + // TODO move this helper to @mui/monorepo/.eslintrc // It needs to know about the parent "no-restricted-imports" to not override them. const buildPackageRestrictedImports = (packageName, root, allowRootImports = true) => [ @@ -85,43 +96,16 @@ const buildPackageRestrictedImports = (packageName, root, allowRootImports = tru files: [ `packages/${root}/src/**/*.test{.ts,.tsx,.js}`, `packages/${root}/src/**/*.spec{.ts,.tsx,.js}`, - 'docs/data/**/*{.ts,.tsx,.js}', ], excludedFiles: ['*.d.ts'], rules: { 'no-restricted-imports': [ 'error', { - paths: [ - { - name: '@mui/x-charts', - message: 'Use deeper import instead', - }, - { - name: '@mui/x-charts-pro', - message: 'Use deeper import instead', - }, - { - name: '@mui/x-codemod', - message: 'Use deeper import instead', - }, - { - name: '@mui/x-date-pickers', - message: 'Use deeper import instead', - }, - { - name: '@mui/x-date-pickers-pro', - message: 'Use deeper import instead', - }, - { - name: '@mui/x-tree-view', - message: 'Use deeper import instead', - }, - { - name: '@mui/x-tree-view-pro', - message: 'Use deeper import instead', - }, - ], + paths: RESTRICTED_TOP_LEVEL_IMPORTS.map((name) => ({ + name, + message: 'Use deeper import instead', + })), }, ], }, @@ -197,6 +181,19 @@ module.exports = { }, overrides: [ ...baseline.overrides, + { + files: [ + // matching the pattern of the test runner + '*.test.js', + '*.test.ts', + '*.test.tsx', + ], + excludedFiles: ['test/e2e/**/*', 'test/regressions/**/*'], + extends: ['plugin:testing-library/react'], + rules: { + 'testing-library/no-container': 'off', + }, + }, { files: [ // matching the pattern of the test runner @@ -269,6 +266,37 @@ module.exports = { ], }, }, + { + files: ['docs/**/*{.ts,.tsx,.js}'], + excludedFiles: ['*.d.ts'], + rules: { + 'no-restricted-imports': [ + 'error', + { + paths: RESTRICTED_TOP_LEVEL_IMPORTS.map((name) => ({ + name, + message: 'Use deeper import instead', + })), + patterns: [ + { + group: [ + '@mui/*/*/*', + // Allow any import depth with any internal packages + '!@mui/internal-*/**', + + // Exceptions (QUESTION: Keep or remove?) + '!@mui/x-date-pickers/internals/demo', + '!@mui/x-tree-view/hooks/useTreeViewApiRef', + // TODO: export this from /ButtonBase in core. This will break after we move to package exports + '!@mui/material/ButtonBase/TouchRipple', + ], + message: 'Use less deep import instead', + }, + ], + }, + ], + }, + }, ...buildPackageRestrictedImports('@mui/x-charts', 'x-charts', false), ...buildPackageRestrictedImports('@mui/x-charts-pro', 'x-charts-pro', false), ...buildPackageRestrictedImports('@mui/x-codemod', 'x-codemod', false), diff --git a/.github/ISSUE_TEMPLATE/3.pro-support.yml b/.github/ISSUE_TEMPLATE/3.pro-support.yml index 5c240d8e03c53..d2971f1ea2423 100644 --- a/.github/ISSUE_TEMPLATE/3.pro-support.yml +++ b/.github/ISSUE_TEMPLATE/3.pro-support.yml @@ -13,7 +13,7 @@ body: id: contact attributes: label: Order ID or Support key šŸ’³ - description: The order ID of the purchased Pro plan. Community users can [learn more about support](https://mui.com/getting-started/support/) in the documentation. + description: The order ID of the purchased Pro plan. Community users can [learn more about support](https://mui.com/x/introduction/support/) in the documentation. placeholder: 'e.g. 11111' validations: required: true diff --git a/.github/ISSUE_TEMPLATE/4.premium-support.yml b/.github/ISSUE_TEMPLATE/4.premium-support.yml index 3cecec3472964..9e410f1ef5ba6 100644 --- a/.github/ISSUE_TEMPLATE/4.premium-support.yml +++ b/.github/ISSUE_TEMPLATE/4.premium-support.yml @@ -13,7 +13,7 @@ body: id: contact attributes: label: Order ID or Support key šŸ’³ - description: The order ID of the purchased Premium plan. Community users can [learn more about support](https://mui.com/getting-started/support/) in the documentation. + description: The order ID of the purchased Premium plan. Community users can [learn more about support](https://mui.com/x/introduction/support/) in the documentation. placeholder: 'e.g. 11111' validations: required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 51792745ac79e..5494cf0593c41 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ contact_links: - name: Support ā” - url: https://mui.com/getting-started/support/ - about: I need support with MUI X. + url: https://mui.com/x/introduction/support/ + about: I need support with MUIĀ X. diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 38ce7892b3508..e112f7be51a74 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@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 + uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 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@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 + uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml index 3f56d206a17d9..e05f761ab3024 100644 --- a/.github/workflows/codspeed.yml +++ b/.github/workflows/codspeed.yml @@ -17,6 +17,8 @@ on: - 'master' - 'next' +permissions: {} + jobs: benchmarks: name: Benchmarks Charts @@ -26,10 +28,10 @@ jobs: # L3: Run the benchmarks for pull requests with the label 'component: charts' # Yaml syntax looks a little weird, but it is correct. if: >- - ${{ - (github.event_name == 'push') || - (github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'component: charts') || - (github.event_name == 'pull_request' && github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'component: charts')) + ${{ + (github.event_name == 'push') || + (github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'component: charts') || + (github.event_name == 'pull_request' && github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'component: charts')) }} steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 diff --git a/.github/workflows/issue-cleanup.yml b/.github/workflows/issue-cleanup.yml deleted file mode 100644 index a4d8839b60aa6..0000000000000 --- a/.github/workflows/issue-cleanup.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Issue cleanup - -on: - issues: - types: - - opened - -permissions: {} - -jobs: - issue_cleanup: - runs-on: ubuntu-latest - name: Clean issue body and add support label - permissions: - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - name: Clean issue body - id: cleanup - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 - with: - script: | - const script = require('./scripts/githubActions/issueBodyCleanup.js') - await script({core, github, context}) - - name: Add support label - if: steps.cleanup.outputs.ORDER_ID != '' - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 - with: - script: | - const script = require('./scripts/githubActions/orderIdValidation.js') - await script({core, github, context}) - env: - ORDER_API_TOKEN: ${{ secrets.SHOP_AUTH_TOKEN }} - ORDER_ID: ${{ steps.cleanup.outputs.ORDER_ID }} diff --git a/.github/workflows/new-issue-triage.yml b/.github/workflows/new-issue-triage.yml new file mode 100644 index 0000000000000..42f94ff7987b2 --- /dev/null +++ b/.github/workflows/new-issue-triage.yml @@ -0,0 +1,26 @@ +name: New issue triage +on: + issues: + types: + - opened + +permissions: {} + +jobs: + issue_cleanup: + name: Clean issue body + uses: mui/mui-public/.github/workflows/issues_body-cleanup.yml@master + permissions: + contents: read + issues: write + order_id_validation: + name: Validate order ID + needs: issue_cleanup + if: needs.issue_cleanup.outputs.orderId != '' + uses: mui/mui-public/.github/workflows/issues_order-id-validation.yml@master + secrets: inherit + with: + orderId: ${{ needs.issue_cleanup.outputs.orderId }} + permissions: + contents: read + issues: write diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index e04f536c60adb..4dd91ef555e58 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@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15 + uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: sarif_file: results.sarif diff --git a/.github/workflows/support-stackoverflow.yml b/.github/workflows/support-stackoverflow.yml index c9d8069a636ff..d6c113db37c6b 100644 --- a/.github/workflows/support-stackoverflow.yml +++ b/.github/workflows/support-stackoverflow.yml @@ -22,12 +22,12 @@ jobs: # Comment to post on issues marked as support requests. Add a link # to a support page, or set to `false` to disable issue-comment: | - šŸ‘‹ Thanks for using MUI X! + šŸ‘‹ Thanks for using this project! We use GitHub issues as a bug and feature requests tracker, however, this issue appears to be a support request. - For support, please check out https://mui.com/x/introduction/support/. Thanks! + For support with MUIĀ X please check out https://mui.com/x/introduction/support/. Thanks! If you have a question on StackĀ Overflow, you are welcome to link to it here, it might help others. If your issue is subsequently confirmed as a bug, and the report follows the issue template, it can be reopened. diff --git a/CHANGELOG.md b/CHANGELOG.md index e86dfe05c0d05..72a65f2523895 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,306 @@ -# Change Log +# Changelog All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 7.15.0 + +_Aug 29, 2024_ + +We'd like to offer a big thanks to the 8 contributors who made this release possible. Here are some highlights āœØ: + +- šŸ’« Support MaterialĀ UI v6 (`@mui/material@6`) peer dependency (#14142) @cherniavskii + +You can now use MUI X components with either v5 or v6 of `@mui/material` package šŸŽ‰ + +- šŸž Bugfixes + +### Data Grid + +#### `@mui/x-data-grid-pro@7.15.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +- [DataGridPro] Export `GridRowReorderCell` component (#14079) @genepaul + +#### `@mui/x-data-grid-premium@7.15.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.15.0`. + +### Date and Time Pickers + +#### `@mui/x-date-pickers@7.15.0` + +- [pickers] Add `onTouchStart` handler for `TimeClock` (#14305) @arthurbalduini + +#### `@mui/x-date-pickers-pro@7.15.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.15.0`, plus: + +- [DateTimeRangePicker] Fix date format resolving from views on 24hr locales (#14341) @arthurbalduini + +### Charts + +#### `@mui/x-charts@7.15.0` + +- [charts] Add missing `themeAugmentation` in pro plan (#14313) @lhilgert9 +- [charts] Fix `LineChart` transition stopping before completion (#14366) @JCQuintas +- [charts] Fix tooltip with horizontal layout (#14337) @alexfauquette +- [charts] Keep axis root classe usage explicit (#14378) @alexfauquette + +#### `@mui/x-charts-pro@7.0.0-alpha.3` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-charts@7.15.0`, plus: + +- [charts pro] Avoid relative reference to `@mui/x-charts` package (#14335) @LukasTy + +### Docs + +- [docs] Fix sentence case `h2` @oliviertassinari +- [docs] Clarify contribution guide references @oliviertassinari +- [docs] Fix Stack Overflow issue canned response @oliviertassinari +- [docs] Fix outdated link to support page @oliviertassinari +- [docs] Fix use of MaterialĀ UI @oliviertassinari +- [docs] Update deprecated props in docs (#14295) @JCQuintas + +### Core + +- [core] Allow only v5.x for `MUI Core` renovate group (#14382) @LukasTy +- [core] Avoid visual regression when using `@mui/material@6` (#14357) @cherniavskii +- [core] Remove renovate rule targeting only `next` releases of `@mui/docs` (#14364) @LukasTy +- [core] Support `@mui/material@6` peer dependency (#14142) @cherniavskii +- [core] Use `useRtl` instead of `useTheme` to access direction (#14359) @LukasTy +- [code-infra] Typecheck nested folders in playground (#14352) @JCQuintas +- [infra] Fix Issue cleanup action @oliviertassinari +- [license] Prepare renaming of argument names @oliviertassinari + +## 7.14.0 + +_Aug 23, 2024_ + +We'd like to offer a big thanks to the 14 contributors who made this release possible. Here are some highlights āœØ: + +- šŸ’« Allow [filtering the axis on zoom](https://mui.com/x/react-charts/zoom-and-pan/#zoom-filtering), making the axis adapt by removing values outside the view. + + filtering the axis on zoom + +- šŸ“Š Improve bar chart performances +- šŸŒ Improve Czech (cs-CZ) and Hebrew (he-IL) locales on the Data Grid +- šŸŒ Improve Chinese (zh-HK), Hebrew (he-IL), and Vietnamese (vi-VN) locales on the Date and Time Pickers +- šŸž Bugfixes + + + +### Data Grid + +#### `@mui/x-data-grid@7.14.0` + +- [DataGrid] Use readonly array result for `getTreeDataPath` (#11743) @pcorpet +- [DataGrid] Use `event.key` for `Tab` and `Escape` keys (#14170) @k-rajat19 +- [DataGrid] Introduce selectors with arguments (#14236) @MBilalShafi +- [DataGrid] include `api` in `gridCellParams` interface (#14201) @k-rajat19 +- [l10n] Improve Czech (cs-CZ) locale (#14135) @chirimiri22 +- [l10n] Improve Hebrew (he-IL) locale (#14287) @rotembarsela + +#### `@mui/x-data-grid-pro@7.14.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.14.0`. + +#### `@mui/x-data-grid-premium@7.14.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.14.0`, plus: + +- [DataGridPremium] Fix clipboard paste not working for a single cell on non-first page (#14261) @arminmeh +- [DataGridPremium] Fix `onCellSelectionModelChange` not triggered when additional cell range is selected (#14199) @arminmeh + +### Date and Time Pickers + +#### `@mui/x-date-pickers@7.14.0` + +- [l10n] Improve Chinese (zh-HK) locale (#13289) @yeeharn +- [l10n] Improve Hebrew (he-IL) locale (#14287) @rotembarsela +- [l10n] Improve Vietnamese (vi-VN) locale (#14238) @locnbk2002 +- [TimePicker] Handle `Space` and `Enter` on the `TimeClock` component @arthurbalduini + +#### `@mui/x-date-pickers-pro@7.14.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.14.0`. + +### Charts + +#### `@mui/x-charts@7.14.0` + +- [charts] Fix grid overflow with zooming (#14280) @alexfauquette +- [charts] Improve bar chart performances (#14278) @alexfauquette +- [charts] Test pointer events (#14042) @alexfauquette +- [charts] Use `isPointInside` function for both graphs and axis (#14222) @JCQuintas + +#### `@mui/x-charts-pro@7.0.0-alpha.2` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-charts@7.14.0`, plus: + +- [charts-pro] Zoom axis filtering (#14121) @JCQuintas + +### Docs + +- [docs] Consistent use of UTC and timezones (#14250) @oliviertassinari +- [docs] Fix missing leading slashes in URLs (#14249) @oliviertassinari +- [docs] Dash usage revision on pickers pages (#14260) @arthurbalduini + +### Core + +- [core] Follow JSDocs convention @oliviertassinari +- [core] Prepare for material v6 (#14143) @LukasTy +- [code-infra] Set up `eslint-plugin-testing-library` (#14232) @LukasTy +- [infra] Updated mui-x roadmap links with new project URL (#14271) @michelengelen + +## 7.13.0 + +_Aug 16, 2024_ + +We'd like to offer a big thanks to the 12 contributors who made this release possible. Here are some highlights āœØ: + +- šŸ’« Allow to [edit the label](https://mui.com/x/react-tree-view/rich-tree-view/editing/) of Tree View's items. + + Item label editing + +- šŸ”§ Improve rows accessibility on the Data Grid features "Tree Data" and "Row Grouping". Certain "Row Grouping" accessibility updates will only be applied if experimental feature flag is enabled. See the [documentation](https://mui.com/x/react-data-grid/row-grouping/#accessibility-changes-in-v8) for more information. +- šŸŒ Improve Vietnamese (vi-VN) locale on the Data Grid +- šŸž Bugfixes + + + +### Data Grid + +#### `@mui/x-data-grid@7.13.0` + +- [DataGrid] Fix CSV export for `null` and `undefined` values (#14166) @k-rajat19 +- [DataGrid] Fix error logged during skeleton loading with nested data grid (#14186) @KenanYusuf +- [DataGrid] Remove needless check in `useGridStateInitialization` (#14181) @k-rajat19 +- [DataGrid] Add recipe for persisting filters in local storage (#14208) @cherniavskii +- [l10n] Improve Vietnamese (vi-VN) locale (#14216) @hungnd-casso + +#### `@mui/x-data-grid-pro@7.13.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.13.0`, plus: + +- [DataGridPro] Fix Tree Data and Row Grouping rows accessibility (#13623) @arminmeh + +#### `@mui/x-data-grid-premium@7.13.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.13.0`. + +### Date and Time Pickers + +#### `@mui/x-date-pickers@7.13.0` + +- [pickers] Fix date and time merging to retain milliseconds (#14173) @LukasTy + +#### `@mui/x-date-pickers-pro@7.13.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.13.0`. + +### Charts + +#### `@mui/x-charts@7.13.0` + +- [charts] Add `baseline` property to the `LineChart` `series` (#14153) @JCQuintas +- [charts] Fix issue where tooltip would disappear on mouse click (#14187) @alexfauquette +- [charts] Rename `CartesianContextProvider` to `CartesianProvider` (#14102) @JCQuintas +- [charts] Support axis with the same value for all data points (#14191) @alexfauquette + +#### `@mui/x-charts-pro@7.0.0-alpha.1` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-charts@7.13.0`. + +### Tree View + +#### `@mui/x-tree-view@7.13.0` + +- [TreeView] Add label editing feature (#13388) @noraleonte +- [TreeView] Fix the parameters passed for the `canMoveItemToNewPosition` prop (#14176) @flaviendelangle + +### Docs + +- [docs] Extract dataset in the Line chart docs (#14034) @alexfauquette +- [docs] Remove redundant encoding in the mock data source server (#14185) @MBilalShafi +- [docs] Use Netflix financial results to document bar charts (#13991) @alexfauquette +- [docs] Remove relience of abbreviations (#14226) @oliviertassinari + +### Core + +- [core] Bump monorepo (#14141) @Janpot +- [core] Fix ESLint issue (#14207) @LukasTy +- [core] Fix Netlify build cache issue (#14182) @cherniavskii +- [code-infra] Refactor Netlify `cache-docs` plugin setup (#14105) @LukasTy +- [internals] Move utils needed for tree view virtualization to shared package (#14202) @flaviendelangle + +## 7.12.1 + +_Aug 8, 2024_ + +We'd like to offer a big thanks to the 9 contributors who made this release possible. Here are some highlights āœØ: + +- šŸŽØ Charts get a new component to display color mapping in the legend +- šŸš€ The `@mui/x-charts-pro` is released in alpha version šŸ§Ŗ. This new package introduces two main features: + - The Heatmap component + - The zoom interaction on the bar, line, and scatter charts +- šŸŒ Improve Dutch (nl-NL) locale on the Date and Time Pickers +- šŸž Bugfixes + + + +### Data Grid + +#### `@mui/x-data-grid@7.12.1` + +- [DataGrid] Fix `checkboxSelectionVisibleOnly` behavior with server-side pagination (#14083) @MBilalShafi +- [DataGrid] Fix `columnHeadersContainerRef` being `undefined` before mount (#14051) @samwato +- [DataGrid] Support Yarn PnP (#14126) @cherniavskii + +#### `@mui/x-data-grid-pro@7.12.1` [![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.12.1`. + +#### `@mui/x-data-grid-premium@7.12.1` [![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.12.1`. + +### Date and Time Pickers + +#### `@mui/x-date-pickers@7.12.1` + +- [l10n] Improve Dutch (nlNL) locale (pickers) (#14036) @Robin1896 + +#### `@mui/x-date-pickers-pro@7.12.1` [![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.12.1`. + +### Charts + +#### `@mui/x-charts@7.12.1` + +- [charts] Fix charts vendor publish config (#14073) @JCQuintas +- [charts] Move `plugins` to `PluginProvider` (#14056) @JCQuintas + +#### `@mui/x-charts-pro@7.0.0-alpha.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-charts@7.12.1`, plus: + +- [charts-pro] Release the pro package in alpha (#13859) @alexfauquette + +### Tree View + +#### `@mui/x-tree-view@7.12.1` + +### Docs + +- [docs] Add a warning to promote the usage of `updateRows` (#14027) @MBilalShafi +- [docs] Disable ad in `Rich Tree View-Ordering` page (#14123) @oliviertassinari +- [docs] Redesign Date and Time Pickers overview page (#13241) @noraleonte + +- [CHANGELOG] Polish details @oliviertassinari +- [code-infra] Use concurrency 1 in CircleCI (#14110) @JCQuintas +- [infra] Re-added the removal of `Latest Version` section (#14132) @michelengelen + ## 7.12.0 _Aug 1, 2024_ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab7f01f18cde6..179ac396d3c66 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ # Contributing to MUIĀ X -## MUIĀ X vs. MaterialĀ UI, BaseĀ UI, MUIĀ System, JoyĀ UI +## MUIĀ X vs. MUI organization -MUIĀ X is an extension of the core open-source libraries of MUI (MaterialĀ UI, BaseĀ UI, MUIĀ System, JoyĀ UI). +MUIĀ X is an extension of the core open-source libraries of the MUI organization (MaterialĀ UI, BaseĀ UI, MUIĀ System, JoyĀ UI). The repositories are part of the same codebase. `mui/mui-x` imports the code infrastructure from [`mui/material-ui`](https://github.com/mui/material-ui). You can find the "contributing" guide for the main repository in [mui/material-ui/CONTRIBUTING.md](https://github.com/mui/material-ui/blob/HEAD/CONTRIBUTING.md). diff --git a/SECURITY.md b/SECURITY.md index e659bc8bc9574..fe4dd1edca0e3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,11 +4,12 @@ The versions of the project that are currently supported with security updates. -| Version | Supported | -| ------: | :----------------- | -| 6.x | :white_check_mark: | -| 5.x | :white_check_mark: | -| < 5.0 | :x: | +| MUIĀ X version | Release | Supported | +| ------------: | :--------- | :----------------------------------- | +| ^7.0.0 | 2024-03-23 | :white_check_mark: Stable major | +| ^6.0.0 | 2023-03-03 | :white_check_mark: Long-term support | +| ^5.0.0 | 2021-11-23 | :x: | +| ^4.0.0 | 2021-09-28 | :x: | ## Reporting a vulnerability diff --git a/babel.config.js b/babel.config.js index 4d49a519eca6b..6d812eba0163b 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,6 +1,16 @@ +// @ts-check const path = require('path'); const generateReleaseInfo = require('./packages/x-license/generateReleaseInfo'); +/** + * @typedef {import('@babel/core')} babel + */ + +/** + * + * @param {string} relativeToBabelConf + * @returns {string} + */ function resolveAliasPath(relativeToBabelConf) { const resolvedPath = path.relative(process.cwd(), path.resolve(__dirname, relativeToBabelConf)); return `./${resolvedPath.replace('\\', '/')}`; @@ -33,6 +43,7 @@ const productionPlugins = [ ['babel-plugin-react-remove-properties', { properties: ['data-mui-test'] }], ]; +/** @type {babel.ConfigFunction} */ module.exports = function getBabelConfig(api) { const useESModules = api.env(['modern', 'stable', 'rollup']); @@ -56,6 +67,16 @@ module.exports = function getBabelConfig(api) { '@babel/preset-typescript', ]; + const usesAliases = + // in this config: + api.env(['coverage', 'development', 'test', 'benchmark']) || + process.env.NODE_ENV === 'test' || + // in webpack config: + api.env(['regressions']); + + const outFileExtension = '.js'; + + /** @type {babel.PluginItem[]} */ const plugins = [ 'babel-plugin-optimize-clsx', // Need the following 3 transforms for all targets in .browserslistrc. @@ -125,6 +146,17 @@ module.exports = function getBabelConfig(api) { } } + if (useESModules) { + plugins.push([ + '@mui/internal-babel-plugin-resolve-imports', + { + // Don't replace the extension when we're using aliases. + // Essentially only replace in production builds. + outExtension: usesAliases ? null : outFileExtension, + }, + ]); + } + return { assumptions: { noDocumentAll: true, diff --git a/changelogOld/CHANGELOG.v4.md b/changelogOld/CHANGELOG.v4.md index 2c3c89c28fc77..756445e851a6f 100644 --- a/changelogOld/CHANGELOG.v4.md +++ b/changelogOld/CHANGELOG.v4.md @@ -1,4 +1,4 @@ -# Change Log for v4 releases +# Changelog for v4 releases ## 4.0.0 @@ -8,8 +8,8 @@ _Aug 27, 2021_ We have been iterating on the component for [18 months](https://github.com/mui/mui-x/commit/705cb0f387b5f3aa056bf40c4183a2342b317447). With the introduction of the [row edit](https://mui.com/x/react-data-grid/editing/#row-editing) feature, many bug fixes, and polishing of the documentation, we believe the component is ready for a stable release. -The MUIĀ X v4.0.0 release supports [MUIĀ Core](https://github.com/mui/material-ui) v4 and has partial support for v5-beta. With the soon-to-be-released v5 version of the core components, we are moving ongoing work to the v5 release line (Core and X). -The support for existing projects on MaterialĀ UI v4 won't be a priority going forward. We encourage you to migrate to MUIĀ Core v5-beta and soon MUIĀ X v5-beta. We don't patch, fix, or alter older versions. Using MUIĀ Core v4 with MUIĀ X v5 might lead to extra bundle size and configuration. +The MUIĀ X v4.0.0 release supports [MaterialĀ UI](https://github.com/mui/material-ui) v4 and has partial support for v5-beta. With the soon-to-be-released v5 version of the core components, we are moving ongoing work to the v5 release line (Core and X). +The support for existing projects on MaterialĀ UI v4 won't be a priority going forward. We encourage you to migrate to MUIĀ Core v5-beta and soon MUIĀ X v5-beta. We don't patch, fix, or alter older versions. Using MaterialĀ UI v4 with MUIĀ X v5 might lead to extra bundle size and configuration. A big thanks to the 6 contributors who made this release possible. Here are some highlights āœØ: @@ -600,7 +600,7 @@ Big thanks to the 10 contributors who made this release possible. Here are some - āš”ļø Components that use portals, like `Select` and `Autocomplete`, can now be used in the cell editing (#1772) @m4theushw - šŸ“ƒ Apply the `valueFormatter` to the CSV exporting (#1922) @DanailH - šŸ’… Rename CSS classes to match the convention of the core components (#1872) @DanailH -- šŸŒŽ Isolate translations from MUIĀ Core and MUIĀ X (#1913) @DanailH +- šŸŒŽ Isolate translations from MaterialĀ UI and MUIĀ X (#1913) @DanailH - šŸš€ Improve performance when finding column indexes and updating rows (#1903, #1923) @Janpot @N2D4 - šŸž Bugfixes @@ -609,7 +609,7 @@ Big thanks to the 10 contributors who made this release possible. Here are some #### Breaking changes - [DataGrid] The `onEditCellChangeCommitted` prop won't be called with an event when committing changes by clicking outside the cell (#1910) @m4theushw -- [DataGrid] Translation for MUIĀ Core components are no longer included in the MUIĀ X translation (#1913) @DanailH +- [DataGrid] Translation for MaterialĀ UI components are no longer included in the MUIĀ X translation (#1913) @DanailH ```diff import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles'; @@ -684,7 +684,7 @@ Big thanks to the 10 contributors who made this release possible. Here are some - [DataGrid] Improve Brazilian Portuguese (pt-BR) locale (#1861) @aline-matos - [DataGrid] Improve type of the blur event (#1918) @oliviertassinari - [DataGrid] Improve updateRows performance (#1923) @N2D4 -- [DataGrid] Include MUIĀ Core component localizations in `localeText` (#1913) @DanailH +- [DataGrid] Include MaterialĀ UI component localizations in `localeText` (#1913) @DanailH - [DataGrid] Make the CSV export respect the `valueFormatter` (#1922) @DanailH - [DataGrid] Remove `disableClickEventBubbling` (#1910) @m4theushw - [DataGrid] Rename CSS classes according to new convention (#1872) @DanailH diff --git a/changelogOld/CHANGELOG.v5.md b/changelogOld/CHANGELOG.v5.md index a544c6b2c162b..324ef65bc5fe1 100644 --- a/changelogOld/CHANGELOG.v5.md +++ b/changelogOld/CHANGELOG.v5.md @@ -1,4 +1,4 @@ -# Change Log for v5 releases +# Changelog for v5 releases ## 5.17.25 @@ -650,7 +650,7 @@ _Sep 9, 2022_ This release will the last regular release for our `v5` packages. From now on, we'll be focusing on developing MUIĀ X v6. -You can check the [roadmap](https://github.com/mui/mui-x/projects/1) for more details on what's coming next. +You can check the [roadmap](https://github.com/orgs/mui/projects/35) for more details on what's coming next. And if you'd like to help, please consider volunteering to give us a [user interview](https://forms.gle/vsBv6CLPz9h57xg8A). We'd love to know more about your use cases, pain points and expectations for the future. diff --git a/changelogOld/CHANGELOG.v6.md b/changelogOld/CHANGELOG.v6.md index 603ff9f699534..df1260fa0d65c 100644 --- a/changelogOld/CHANGELOG.v6.md +++ b/changelogOld/CHANGELOG.v6.md @@ -1,4 +1,4 @@ -# Change Log for v6 releases +# Changelog for v6 releases ## 6.19.12 @@ -2188,7 +2188,7 @@ We'd like to offer a big thanks to the 11 contributors who made this release pos It already contains [line](https://mui.com/x/react-charts/lines/), [bar](https://mui.com/x/react-charts/bars/), and [scatter](https://mui.com/x/react-charts/scatter/) charts, with basic customization features. Check out the [documentation](https://mui.com/x/react-charts/) to see what it can do, and open issues to get the feature you need implemented. -- šŸš€ Introducing UTC and timezone support for pickers. +- šŸš€ Introducing UTC and timezones support for pickers. Pickers time zone switching diff --git a/docs/data/charts-component-api-pages.ts b/docs/data/charts-component-api-pages.ts index aaaa09657dbea..2e1ad7d8eb6ff 100644 --- a/docs/data/charts-component-api-pages.ts +++ b/docs/data/charts-component-api-pages.ts @@ -1,4 +1,4 @@ -import type { MuiPage } from '@mui/monorepo/docs/src/MuiPage'; +import type { MuiPage } from 'docs/src/MuiPage'; const apiPages: MuiPage[] = [ { diff --git a/docs/data/charts/bars/BarAnimation.js b/docs/data/charts/bars/BarAnimation.js index fc02054c2216c..57230a2ea2124 100644 --- a/docs/data/charts/bars/BarAnimation.js +++ b/docs/data/charts/bars/BarAnimation.js @@ -68,8 +68,8 @@ export default function BarAnimation() { } const highlightScope = { - highlighted: 'series', - faded: 'global', + highlight: 'series', + fade: 'global', }; const series = [ diff --git a/docs/data/charts/bars/BarAnimation.tsx b/docs/data/charts/bars/BarAnimation.tsx index 882a33e02afcf..9bdd402a65e7f 100644 --- a/docs/data/charts/bars/BarAnimation.tsx +++ b/docs/data/charts/bars/BarAnimation.tsx @@ -68,8 +68,8 @@ export default function BarAnimation() { } const highlightScope = { - highlighted: 'series', - faded: 'global', + highlight: 'series', + fade: 'global', } as const; const series = [ diff --git a/docs/data/charts/bars/BarClickNoSnap.js b/docs/data/charts/bars/BarClickNoSnap.js index 779097099389e..8f5dc6ad8bf56 100644 --- a/docs/data/charts/bars/BarClickNoSnap.js +++ b/docs/data/charts/bars/BarClickNoSnap.js @@ -17,7 +17,7 @@ const barChartsParams = { label: 'A', stack: 'total', highlightScope: { - highlighted: 'item', + highlight: 'item', }, }, { @@ -26,7 +26,7 @@ const barChartsParams = { label: 'B', stack: 'total', highlightScope: { - highlighted: 'item', + highlight: 'item', }, }, { @@ -34,7 +34,7 @@ const barChartsParams = { data: [4, 2, 5, 4, 1], label: 'C', highlightScope: { - highlighted: 'item', + highlight: 'item', }, }, ], diff --git a/docs/data/charts/bars/BarGapNoSnap.js b/docs/data/charts/bars/BarGapNoSnap.js index 82d68c5e43b4c..aae37be33e7b2 100644 --- a/docs/data/charts/bars/BarGapNoSnap.js +++ b/docs/data/charts/bars/BarGapNoSnap.js @@ -1,14 +1,14 @@ import * as React from 'react'; import ChartsUsageDemo from 'docsx/src/modules/components/ChartsUsageDemo'; import { BarChart } from '@mui/x-charts/BarChart'; +import { balanceSheet, addLabels } from './netflixsBalanceSheet'; + +const series = addLabels([ + { dataKey: 'totAss' }, + { dataKey: 'totLia', stack: 'passive' }, + { dataKey: 'totEq', stack: 'passive' }, +]); -const series = [ - { data: [1, 5, 2], stack: 'a' }, - { data: [2, 3, 4], stack: 'a' }, - { data: [3, 2, 3], stack: 'b' }, - { data: [8, 3, 6], stack: 'b' }, - { data: [11, 6, 9] }, -]; export default function BarGapNoSnap() { return ( ( `$ ${v / 1000000}B` }]} + slotProps={{ legend: { hidden: true } }} /> )} getCode={({ props }) => { diff --git a/docs/data/charts/bars/StackBars.js b/docs/data/charts/bars/StackBars.js index 8ebf29b218502..c2fd8c6660d6d 100644 --- a/docs/data/charts/bars/StackBars.js +++ b/docs/data/charts/bars/StackBars.js @@ -1,16 +1,22 @@ import * as React from 'react'; import { BarChart } from '@mui/x-charts/BarChart'; +import { addLabels, balanceSheet } from './netflixsBalanceSheet'; export default function StackBars() { return ( diff --git a/docs/data/charts/bars/StackBars.tsx b/docs/data/charts/bars/StackBars.tsx index 8ebf29b218502..c2fd8c6660d6d 100644 --- a/docs/data/charts/bars/StackBars.tsx +++ b/docs/data/charts/bars/StackBars.tsx @@ -1,16 +1,22 @@ import * as React from 'react'; import { BarChart } from '@mui/x-charts/BarChart'; +import { addLabels, balanceSheet } from './netflixsBalanceSheet'; export default function StackBars() { return ( diff --git a/docs/data/charts/bars/StackBars.tsx.preview b/docs/data/charts/bars/StackBars.tsx.preview index c5d206d451243..8faa9bfaab85e 100644 --- a/docs/data/charts/bars/StackBars.tsx.preview +++ b/docs/data/charts/bars/StackBars.tsx.preview @@ -1,11 +1,16 @@ \ No newline at end of file diff --git a/docs/data/charts/bars/netflixsBalanceSheet.ts b/docs/data/charts/bars/netflixsBalanceSheet.ts new file mode 100644 index 0000000000000..1a1b1c116706d --- /dev/null +++ b/docs/data/charts/bars/netflixsBalanceSheet.ts @@ -0,0 +1,82 @@ +// Data from https://finance.yahoo.com/quote/NFLX/financials/ + +const translations = { + totAss: 'Total Assets', + currAss: 'Current Assets', + nCurrAss: 'non-Current Assets', + totLia: 'Total Liabilities', + curLia: 'Current Liabilities', + nCurLia: 'non-Current Liabilities', + totEq: 'Total Equity', + capStock: 'Capital Stock', + retEarn: 'Retained Earning', + treas: 'Treasury', + nonAffect: 'non Affected', +} as const; + +export function addLabels(series: T[]) { + return series.map((item) => ({ + ...item, + label: translations[item.dataKey], + valueFormatter: (v: number | null) => (v ? `$ ${v.toLocaleString()}k` : '-'), + })); +} + +export const balanceSheet = [ + { + year: '2020', + totAss: 39280359, + currAss: 9761580, + nCurrAss: 29518779, + totLia: 28215119, + curLia: 7805785, + nCurLia: 20409334, + totEq: 11065240, + capStock: 3447698, + retEarn: 7573144, + treas: null, + nonAffect: 44398, + }, + { + year: '2021', + totAss: 44584663, + currAss: 8069825, + nCurrAss: 36514838, + totLia: 28735415, + curLia: 8488966, + nCurLia: 20246449, + totEq: 15849248, + capStock: 4024561, + retEarn: 12689372, + treas: 824190, + nonAffect: -40495, + }, + { + year: '2022', + totAss: 48594768, + currAss: 9266473, + nCurrAss: 39328295, + totLia: 27817367, + curLia: 7930974, + nCurLia: 19886393, + totEq: 20777401, + capStock: 4637601, + retEarn: 17181296, + treas: 824190, + nonAffect: -217306, + }, + { + year: '2023', + totAss: 48731992, + currAss: 9918133, + nCurrAss: 38813859, + totLia: 28143679, + curLia: 8860655, + nCurLia: 19283024, + totEq: 20588313, + capStock: 5145172, + retEarn: 22589286, + treas: 6922200, + nonAffect: -223945, + }, +]; diff --git a/docs/data/charts/dataset/AMZN.json b/docs/data/charts/dataset/AMZN.json new file mode 100644 index 0000000000000..7a0281eaf0522 --- /dev/null +++ b/docs/data/charts/dataset/AMZN.json @@ -0,0 +1,2261 @@ +[ + { + "date": "2023-07-24", + "open": 130.309998, + "high": 131.660004, + "low": 128.350006, + "close": 128.800003, + "adjClose": 128.800003, + "volume": 45591100 + }, + { + "date": "2023-07-25", + "open": 129.309998, + "high": 129.580002, + "low": 128.529999, + "close": 129.130005, + "adjClose": 129.130005, + "volume": 39236700 + }, + { + "date": "2023-07-26", + "open": 126.510002, + "high": 129.080002, + "low": 126.110001, + "close": 128.149994, + "adjClose": 128.149994, + "volume": 53910100 + }, + { + "date": "2023-07-27", + "open": 131.0, + "high": 132.630005, + "low": 127.790001, + "close": 128.25, + "adjClose": 128.25, + "volume": 52610700 + }, + { + "date": "2023-07-28", + "open": 129.690002, + "high": 133.009995, + "low": 129.330002, + "close": 132.210007, + "adjClose": 132.210007, + "volume": 46317400 + }, + { + "date": "2023-07-31", + "open": 133.199997, + "high": 133.869995, + "low": 132.380005, + "close": 133.679993, + "adjClose": 133.679993, + "volume": 41901500 + }, + { + "date": "2023-08-01", + "open": 133.550003, + "high": 133.690002, + "low": 131.619995, + "close": 131.690002, + "adjClose": 131.690002, + "volume": 42098500 + }, + { + "date": "2023-08-02", + "open": 130.149994, + "high": 130.229996, + "low": 126.82, + "close": 128.210007, + "adjClose": 128.210007, + "volume": 51027600 + }, + { + "date": "2023-08-03", + "open": 127.480003, + "high": 129.839996, + "low": 126.410004, + "close": 128.910004, + "adjClose": 128.910004, + "volume": 88585200 + }, + { + "date": "2023-08-04", + "open": 141.059998, + "high": 143.630005, + "low": 139.320007, + "close": 139.570007, + "adjClose": 139.570007, + "volume": 152938700 + }, + { + "date": "2023-08-07", + "open": 140.990005, + "high": 142.539993, + "low": 138.949997, + "close": 142.220001, + "adjClose": 142.220001, + "volume": 71213100 + }, + { + "date": "2023-08-08", + "open": 140.619995, + "high": 140.839996, + "low": 138.419998, + "close": 139.940002, + "adjClose": 139.940002, + "volume": 51710500 + }, + { + "date": "2023-08-09", + "open": 139.970001, + "high": 140.320007, + "low": 137.100006, + "close": 137.850006, + "adjClose": 137.850006, + "volume": 50017300 + }, + { + "date": "2023-08-10", + "open": 139.070007, + "high": 140.410004, + "low": 137.490005, + "close": 138.559998, + "adjClose": 138.559998, + "volume": 58928400 + }, + { + "date": "2023-08-11", + "open": 137.399994, + "high": 139.330002, + "low": 137.0, + "close": 138.410004, + "adjClose": 138.410004, + "volume": 42832100 + }, + { + "date": "2023-08-14", + "open": 138.300003, + "high": 140.589996, + "low": 137.75, + "close": 140.570007, + "adjClose": 140.570007, + "volume": 47148700 + }, + { + "date": "2023-08-15", + "open": 140.050003, + "high": 141.279999, + "low": 137.229996, + "close": 137.669998, + "adjClose": 137.669998, + "volume": 42781500 + }, + { + "date": "2023-08-16", + "open": 137.190002, + "high": 137.270004, + "low": 135.009995, + "close": 135.070007, + "adjClose": 135.070007, + "volume": 41675900 + }, + { + "date": "2023-08-17", + "open": 135.460007, + "high": 136.089996, + "low": 133.529999, + "close": 133.979996, + "adjClose": 133.979996, + "volume": 48354100 + }, + { + "date": "2023-08-18", + "open": 131.619995, + "high": 134.070007, + "low": 131.149994, + "close": 133.220001, + "adjClose": 133.220001, + "volume": 48469400 + }, + { + "date": "2023-08-21", + "open": 133.740005, + "high": 135.190002, + "low": 132.710007, + "close": 134.679993, + "adjClose": 134.679993, + "volume": 41442500 + }, + { + "date": "2023-08-22", + "open": 135.080002, + "high": 135.649994, + "low": 133.729996, + "close": 134.25, + "adjClose": 134.25, + "volume": 32935100 + }, + { + "date": "2023-08-23", + "open": 134.5, + "high": 135.949997, + "low": 133.220001, + "close": 135.520004, + "adjClose": 135.520004, + "volume": 42801000 + }, + { + "date": "2023-08-24", + "open": 136.399994, + "high": 136.779999, + "low": 131.830002, + "close": 131.839996, + "adjClose": 131.839996, + "volume": 43646300 + }, + { + "date": "2023-08-25", + "open": 132.470001, + "high": 133.869995, + "low": 130.580002, + "close": 133.259995, + "adjClose": 133.259995, + "volume": 44147500 + }, + { + "date": "2023-08-28", + "open": 133.779999, + "high": 133.949997, + "low": 131.850006, + "close": 133.139999, + "adjClose": 133.139999, + "volume": 34108400 + }, + { + "date": "2023-08-29", + "open": 133.380005, + "high": 135.139999, + "low": 133.25, + "close": 134.910004, + "adjClose": 134.910004, + "volume": 38646100 + }, + { + "date": "2023-08-30", + "open": 134.929993, + "high": 135.679993, + "low": 133.919998, + "close": 135.070007, + "adjClose": 135.070007, + "volume": 36137000 + }, + { + "date": "2023-08-31", + "open": 135.059998, + "high": 138.789993, + "low": 135.0, + "close": 138.009995, + "adjClose": 138.009995, + "volume": 58781300 + }, + { + "date": "2023-09-01", + "open": 139.460007, + "high": 139.960007, + "low": 136.880005, + "close": 138.119995, + "adjClose": 138.119995, + "volume": 40948300 + }, + { + "date": "2023-09-05", + "open": 137.729996, + "high": 137.800003, + "low": 135.820007, + "close": 137.270004, + "adjClose": 137.270004, + "volume": 40636700 + }, + { + "date": "2023-09-06", + "open": 136.320007, + "high": 137.449997, + "low": 134.610001, + "close": 135.360001, + "adjClose": 135.360001, + "volume": 41785500 + }, + { + "date": "2023-09-07", + "open": 133.899994, + "high": 138.029999, + "low": 133.160004, + "close": 137.850006, + "adjClose": 137.850006, + "volume": 48498900 + }, + { + "date": "2023-09-08", + "open": 136.860001, + "high": 138.850006, + "low": 136.75, + "close": 138.229996, + "adjClose": 138.229996, + "volume": 38348200 + }, + { + "date": "2023-09-11", + "open": 138.75, + "high": 143.619995, + "low": 138.639999, + "close": 143.100006, + "adjClose": 143.100006, + "volume": 56764500 + }, + { + "date": "2023-09-12", + "open": 142.320007, + "high": 143.0, + "low": 140.610001, + "close": 141.229996, + "adjClose": 141.229996, + "volume": 42668500 + }, + { + "date": "2023-09-13", + "open": 140.949997, + "high": 144.979996, + "low": 140.869995, + "close": 144.850006, + "adjClose": 144.850006, + "volume": 60465200 + }, + { + "date": "2023-09-14", + "open": 145.080002, + "high": 145.860001, + "low": 142.949997, + "close": 144.720001, + "adjClose": 144.720001, + "volume": 64033600 + }, + { + "date": "2023-09-15", + "open": 142.690002, + "high": 143.570007, + "low": 140.089996, + "close": 140.389999, + "adjClose": 140.389999, + "volume": 102861700 + }, + { + "date": "2023-09-18", + "open": 140.479996, + "high": 141.75, + "low": 139.220001, + "close": 139.979996, + "adjClose": 139.979996, + "volume": 42823500 + }, + { + "date": "2023-09-19", + "open": 138.699997, + "high": 138.839996, + "low": 135.559998, + "close": 137.630005, + "adjClose": 137.630005, + "volume": 61482500 + }, + { + "date": "2023-09-20", + "open": 138.550003, + "high": 139.369995, + "low": 135.199997, + "close": 135.289993, + "adjClose": 135.289993, + "volume": 46263700 + }, + { + "date": "2023-09-21", + "open": 131.940002, + "high": 132.240005, + "low": 129.309998, + "close": 129.330002, + "adjClose": 129.330002, + "volume": 70234800 + }, + { + "date": "2023-09-22", + "open": 131.110001, + "high": 132.029999, + "low": 128.520004, + "close": 129.119995, + "adjClose": 129.119995, + "volume": 59904300 + }, + { + "date": "2023-09-25", + "open": 129.360001, + "high": 131.779999, + "low": 128.770004, + "close": 131.270004, + "adjClose": 131.270004, + "volume": 46017800 + }, + { + "date": "2023-09-26", + "open": 130.119995, + "high": 130.389999, + "low": 125.279999, + "close": 125.980003, + "adjClose": 125.980003, + "volume": 73048200 + }, + { + "date": "2023-09-27", + "open": 125.760002, + "high": 127.480003, + "low": 124.129997, + "close": 125.980003, + "adjClose": 125.980003, + "volume": 66553400 + }, + { + "date": "2023-09-28", + "open": 124.040001, + "high": 126.580002, + "low": 123.040001, + "close": 125.980003, + "adjClose": 125.980003, + "volume": 54555000 + }, + { + "date": "2023-09-29", + "open": 128.199997, + "high": 129.149994, + "low": 126.32, + "close": 127.120003, + "adjClose": 127.120003, + "volume": 62377600 + }, + { + "date": "2023-10-02", + "open": 127.279999, + "high": 130.470001, + "low": 126.540001, + "close": 129.460007, + "adjClose": 129.460007, + "volume": 48029700 + }, + { + "date": "2023-10-03", + "open": 128.059998, + "high": 128.520004, + "low": 124.25, + "close": 124.720001, + "adjClose": 124.720001, + "volume": 51565000 + }, + { + "date": "2023-10-04", + "open": 126.059998, + "high": 127.360001, + "low": 125.68, + "close": 127.0, + "adjClose": 127.0, + "volume": 44203900 + }, + { + "date": "2023-10-05", + "open": 126.709999, + "high": 126.730003, + "low": 124.330002, + "close": 125.959999, + "adjClose": 125.959999, + "volume": 39660600 + }, + { + "date": "2023-10-06", + "open": 124.160004, + "high": 128.449997, + "low": 124.129997, + "close": 127.959999, + "adjClose": 127.959999, + "volume": 46795900 + }, + { + "date": "2023-10-09", + "open": 126.220001, + "high": 128.789993, + "low": 124.760002, + "close": 128.259995, + "adjClose": 128.259995, + "volume": 38773700 + }, + { + "date": "2023-10-10", + "open": 128.820007, + "high": 130.740005, + "low": 128.050003, + "close": 129.479996, + "adjClose": 129.479996, + "volume": 42178600 + }, + { + "date": "2023-10-11", + "open": 129.740005, + "high": 132.050003, + "low": 129.610001, + "close": 131.830002, + "adjClose": 131.830002, + "volume": 40741800 + }, + { + "date": "2023-10-12", + "open": 132.169998, + "high": 134.479996, + "low": 131.229996, + "close": 132.330002, + "adjClose": 132.330002, + "volume": 55528600 + }, + { + "date": "2023-10-13", + "open": 132.979996, + "high": 133.309998, + "low": 128.949997, + "close": 129.789993, + "adjClose": 129.789993, + "volume": 45786600 + }, + { + "date": "2023-10-16", + "open": 130.690002, + "high": 133.070007, + "low": 130.429993, + "close": 132.550003, + "adjClose": 132.550003, + "volume": 42832900 + }, + { + "date": "2023-10-17", + "open": 130.389999, + "high": 132.580002, + "low": 128.710007, + "close": 131.470001, + "adjClose": 131.470001, + "volume": 49344600 + }, + { + "date": "2023-10-18", + "open": 129.899994, + "high": 130.669998, + "low": 127.510002, + "close": 128.130005, + "adjClose": 128.130005, + "volume": 42699500 + }, + { + "date": "2023-10-19", + "open": 130.570007, + "high": 132.240005, + "low": 127.470001, + "close": 128.399994, + "adjClose": 128.399994, + "volume": 60961400 + }, + { + "date": "2023-10-20", + "open": 128.050003, + "high": 128.169998, + "low": 124.970001, + "close": 125.169998, + "adjClose": 125.169998, + "volume": 56343300 + }, + { + "date": "2023-10-23", + "open": 124.629997, + "high": 127.879997, + "low": 123.980003, + "close": 126.559998, + "adjClose": 126.559998, + "volume": 48260000 + }, + { + "date": "2023-10-24", + "open": 127.739998, + "high": 128.800003, + "low": 126.339996, + "close": 128.559998, + "adjClose": 128.559998, + "volume": 46477400 + }, + { + "date": "2023-10-25", + "open": 126.040001, + "high": 126.339996, + "low": 120.790001, + "close": 121.389999, + "adjClose": 121.389999, + "volume": 74577500 + }, + { + "date": "2023-10-26", + "open": 120.629997, + "high": 121.639999, + "low": 118.349998, + "close": 119.57, + "adjClose": 119.57, + "volume": 100419500 + }, + { + "date": "2023-10-27", + "open": 126.199997, + "high": 130.020004, + "low": 125.519997, + "close": 127.739998, + "adjClose": 127.739998, + "volume": 125309300 + }, + { + "date": "2023-10-30", + "open": 129.720001, + "high": 133.0, + "low": 128.559998, + "close": 132.710007, + "adjClose": 132.710007, + "volume": 72485500 + }, + { + "date": "2023-10-31", + "open": 132.75, + "high": 133.570007, + "low": 131.710007, + "close": 133.089996, + "adjClose": 133.089996, + "volume": 51589400 + }, + { + "date": "2023-11-01", + "open": 133.960007, + "high": 137.350006, + "low": 133.710007, + "close": 137.0, + "adjClose": 137.0, + "volume": 61529400 + }, + { + "date": "2023-11-02", + "open": 138.729996, + "high": 138.809998, + "low": 136.470001, + "close": 138.070007, + "adjClose": 138.070007, + "volume": 52236700 + }, + { + "date": "2023-11-03", + "open": 138.990005, + "high": 139.490005, + "low": 137.449997, + "close": 138.600006, + "adjClose": 138.600006, + "volume": 44007200 + }, + { + "date": "2023-11-06", + "open": 138.759995, + "high": 140.729996, + "low": 138.360001, + "close": 139.740005, + "adjClose": 139.740005, + "volume": 44970400 + }, + { + "date": "2023-11-07", + "open": 140.550003, + "high": 143.369995, + "low": 140.5, + "close": 142.710007, + "adjClose": 142.710007, + "volume": 53553500 + }, + { + "date": "2023-11-08", + "open": 142.970001, + "high": 143.119995, + "low": 141.220001, + "close": 142.080002, + "adjClose": 142.080002, + "volume": 44521700 + }, + { + "date": "2023-11-09", + "open": 142.020004, + "high": 142.649994, + "low": 139.839996, + "close": 140.600006, + "adjClose": 140.600006, + "volume": 36235400 + }, + { + "date": "2023-11-10", + "open": 140.460007, + "high": 143.649994, + "low": 139.910004, + "close": 143.559998, + "adjClose": 143.559998, + "volume": 49287800 + }, + { + "date": "2023-11-13", + "open": 142.080002, + "high": 143.229996, + "low": 140.669998, + "close": 142.589996, + "adjClose": 142.589996, + "volume": 35680600 + }, + { + "date": "2023-11-14", + "open": 145.0, + "high": 147.259995, + "low": 144.679993, + "close": 145.800003, + "adjClose": 145.800003, + "volume": 56674600 + }, + { + "date": "2023-11-15", + "open": 147.059998, + "high": 147.289993, + "low": 142.589996, + "close": 143.199997, + "adjClose": 143.199997, + "volume": 63875700 + }, + { + "date": "2023-11-16", + "open": 140.910004, + "high": 143.320007, + "low": 139.520004, + "close": 142.830002, + "adjClose": 142.830002, + "volume": 49653500 + }, + { + "date": "2023-11-17", + "open": 142.660004, + "high": 145.229996, + "low": 142.539993, + "close": 145.179993, + "adjClose": 145.179993, + "volume": 49636700 + }, + { + "date": "2023-11-20", + "open": 145.130005, + "high": 146.630005, + "low": 144.729996, + "close": 146.130005, + "adjClose": 146.130005, + "volume": 41951200 + }, + { + "date": "2023-11-21", + "open": 143.910004, + "high": 144.050003, + "low": 141.5, + "close": 143.899994, + "adjClose": 143.899994, + "volume": 71226000 + }, + { + "date": "2023-11-22", + "open": 144.570007, + "high": 147.740005, + "low": 144.570007, + "close": 146.710007, + "adjClose": 146.710007, + "volume": 45669100 + }, + { + "date": "2023-11-24", + "open": 146.699997, + "high": 147.199997, + "low": 145.320007, + "close": 146.740005, + "adjClose": 146.740005, + "volume": 22378400 + }, + { + "date": "2023-11-27", + "open": 147.529999, + "high": 149.259995, + "low": 146.880005, + "close": 147.729996, + "adjClose": 147.729996, + "volume": 53762400 + }, + { + "date": "2023-11-28", + "open": 146.979996, + "high": 147.600006, + "low": 145.529999, + "close": 147.029999, + "adjClose": 147.029999, + "volume": 42711700 + }, + { + "date": "2023-11-29", + "open": 147.850006, + "high": 148.539993, + "low": 145.970001, + "close": 146.320007, + "adjClose": 146.320007, + "volume": 40610900 + }, + { + "date": "2023-11-30", + "open": 144.759995, + "high": 146.929993, + "low": 144.330002, + "close": 146.089996, + "adjClose": 146.089996, + "volume": 65814000 + }, + { + "date": "2023-12-01", + "open": 146.0, + "high": 147.25, + "low": 145.550003, + "close": 147.029999, + "adjClose": 147.029999, + "volume": 39924600 + }, + { + "date": "2023-12-04", + "open": 145.25, + "high": 145.350006, + "low": 142.809998, + "close": 144.839996, + "adjClose": 144.839996, + "volume": 48294200 + }, + { + "date": "2023-12-05", + "open": 143.550003, + "high": 148.570007, + "low": 143.130005, + "close": 146.880005, + "adjClose": 146.880005, + "volume": 46822400 + }, + { + "date": "2023-12-06", + "open": 147.580002, + "high": 147.850006, + "low": 144.279999, + "close": 144.520004, + "adjClose": 144.520004, + "volume": 39679000 + }, + { + "date": "2023-12-07", + "open": 146.149994, + "high": 147.919998, + "low": 145.339996, + "close": 146.880005, + "adjClose": 146.880005, + "volume": 52352800 + }, + { + "date": "2023-12-08", + "open": 145.479996, + "high": 147.839996, + "low": 145.399994, + "close": 147.419998, + "adjClose": 147.419998, + "volume": 41858000 + }, + { + "date": "2023-12-11", + "open": 145.660004, + "high": 146.190002, + "low": 143.639999, + "close": 145.889999, + "adjClose": 145.889999, + "volume": 50907300 + }, + { + "date": "2023-12-12", + "open": 145.520004, + "high": 147.5, + "low": 145.300003, + "close": 147.479996, + "adjClose": 147.479996, + "volume": 44944300 + }, + { + "date": "2023-12-13", + "open": 148.119995, + "high": 149.460007, + "low": 146.820007, + "close": 148.839996, + "adjClose": 148.839996, + "volume": 52766200 + }, + { + "date": "2023-12-14", + "open": 149.929993, + "high": 150.539993, + "low": 145.520004, + "close": 147.419998, + "adjClose": 147.419998, + "volume": 58400800 + }, + { + "date": "2023-12-15", + "open": 148.380005, + "high": 150.570007, + "low": 147.880005, + "close": 149.970001, + "adjClose": 149.970001, + "volume": 110039100 + }, + { + "date": "2023-12-18", + "open": 150.559998, + "high": 154.850006, + "low": 150.050003, + "close": 154.070007, + "adjClose": 154.070007, + "volume": 62512800 + }, + { + "date": "2023-12-19", + "open": 154.399994, + "high": 155.119995, + "low": 152.690002, + "close": 153.789993, + "adjClose": 153.789993, + "volume": 43171300 + }, + { + "date": "2023-12-20", + "open": 152.899994, + "high": 155.630005, + "low": 151.559998, + "close": 152.119995, + "adjClose": 152.119995, + "volume": 50322100 + }, + { + "date": "2023-12-21", + "open": 153.300003, + "high": 153.970001, + "low": 152.100006, + "close": 153.839996, + "adjClose": 153.839996, + "volume": 36305700 + }, + { + "date": "2023-12-22", + "open": 153.770004, + "high": 154.350006, + "low": 152.710007, + "close": 153.419998, + "adjClose": 153.419998, + "volume": 29480100 + }, + { + "date": "2023-12-26", + "open": 153.559998, + "high": 153.979996, + "low": 153.029999, + "close": 153.410004, + "adjClose": 153.410004, + "volume": 25067200 + }, + { + "date": "2023-12-27", + "open": 153.559998, + "high": 154.779999, + "low": 153.119995, + "close": 153.339996, + "adjClose": 153.339996, + "volume": 31434700 + }, + { + "date": "2023-12-28", + "open": 153.720001, + "high": 154.080002, + "low": 152.949997, + "close": 153.380005, + "adjClose": 153.380005, + "volume": 27057000 + }, + { + "date": "2023-12-29", + "open": 153.100006, + "high": 153.889999, + "low": 151.029999, + "close": 151.940002, + "adjClose": 151.940002, + "volume": 39789000 + }, + { + "date": "2024-01-02", + "open": 151.539993, + "high": 152.380005, + "low": 148.389999, + "close": 149.929993, + "adjClose": 149.929993, + "volume": 47339400 + }, + { + "date": "2024-01-03", + "open": 149.199997, + "high": 151.050003, + "low": 148.330002, + "close": 148.470001, + "adjClose": 148.470001, + "volume": 49425500 + }, + { + "date": "2024-01-04", + "open": 145.589996, + "high": 147.380005, + "low": 144.050003, + "close": 144.570007, + "adjClose": 144.570007, + "volume": 56039800 + }, + { + "date": "2024-01-05", + "open": 144.690002, + "high": 146.589996, + "low": 144.529999, + "close": 145.240005, + "adjClose": 145.240005, + "volume": 45124800 + }, + { + "date": "2024-01-08", + "open": 146.740005, + "high": 149.399994, + "low": 146.149994, + "close": 149.100006, + "adjClose": 149.100006, + "volume": 46757100 + }, + { + "date": "2024-01-09", + "open": 148.330002, + "high": 151.710007, + "low": 148.210007, + "close": 151.369995, + "adjClose": 151.369995, + "volume": 43812600 + }, + { + "date": "2024-01-10", + "open": 152.059998, + "high": 154.419998, + "low": 151.880005, + "close": 153.729996, + "adjClose": 153.729996, + "volume": 44421800 + }, + { + "date": "2024-01-11", + "open": 155.039993, + "high": 157.169998, + "low": 153.119995, + "close": 155.179993, + "adjClose": 155.179993, + "volume": 49072700 + }, + { + "date": "2024-01-12", + "open": 155.389999, + "high": 156.199997, + "low": 154.009995, + "close": 154.619995, + "adjClose": 154.619995, + "volume": 40460300 + }, + { + "date": "2024-01-16", + "open": 153.529999, + "high": 154.990005, + "low": 152.149994, + "close": 153.160004, + "adjClose": 153.160004, + "volume": 41384600 + }, + { + "date": "2024-01-17", + "open": 151.490005, + "high": 152.149994, + "low": 149.910004, + "close": 151.710007, + "adjClose": 151.710007, + "volume": 34953400 + }, + { + "date": "2024-01-18", + "open": 152.770004, + "high": 153.779999, + "low": 151.820007, + "close": 153.5, + "adjClose": 153.5, + "volume": 37850200 + }, + { + "date": "2024-01-19", + "open": 153.830002, + "high": 155.759995, + "low": 152.740005, + "close": 155.339996, + "adjClose": 155.339996, + "volume": 51033700 + }, + { + "date": "2024-01-22", + "open": 156.889999, + "high": 157.050003, + "low": 153.899994, + "close": 154.779999, + "adjClose": 154.779999, + "volume": 43687500 + }, + { + "date": "2024-01-23", + "open": 154.850006, + "high": 156.210007, + "low": 153.929993, + "close": 156.020004, + "adjClose": 156.020004, + "volume": 37986000 + }, + { + "date": "2024-01-24", + "open": 157.800003, + "high": 158.509995, + "low": 156.479996, + "close": 156.869995, + "adjClose": 156.869995, + "volume": 48547300 + }, + { + "date": "2024-01-25", + "open": 156.949997, + "high": 158.509995, + "low": 154.550003, + "close": 157.75, + "adjClose": 157.75, + "volume": 43638600 + }, + { + "date": "2024-01-26", + "open": 158.419998, + "high": 160.720001, + "low": 157.910004, + "close": 159.119995, + "adjClose": 159.119995, + "volume": 51047400 + }, + { + "date": "2024-01-29", + "open": 159.339996, + "high": 161.289993, + "low": 158.899994, + "close": 161.259995, + "adjClose": 161.259995, + "volume": 45270400 + }, + { + "date": "2024-01-30", + "open": 160.699997, + "high": 161.729996, + "low": 158.490005, + "close": 159.0, + "adjClose": 159.0, + "volume": 45207400 + }, + { + "date": "2024-01-31", + "open": 157.0, + "high": 159.009995, + "low": 154.809998, + "close": 155.199997, + "adjClose": 155.199997, + "volume": 50284400 + }, + { + "date": "2024-02-01", + "open": 155.869995, + "high": 159.759995, + "low": 155.619995, + "close": 159.279999, + "adjClose": 159.279999, + "volume": 76542400 + }, + { + "date": "2024-02-02", + "open": 169.190002, + "high": 172.5, + "low": 167.330002, + "close": 171.809998, + "adjClose": 171.809998, + "volume": 117154900 + }, + { + "date": "2024-02-05", + "open": 170.199997, + "high": 170.550003, + "low": 167.699997, + "close": 170.309998, + "adjClose": 170.309998, + "volume": 55081300 + }, + { + "date": "2024-02-06", + "open": 169.389999, + "high": 170.710007, + "low": 167.649994, + "close": 169.149994, + "adjClose": 169.149994, + "volume": 42505500 + }, + { + "date": "2024-02-07", + "open": 169.479996, + "high": 170.880005, + "low": 168.940002, + "close": 170.529999, + "adjClose": 170.529999, + "volume": 47174100 + }, + { + "date": "2024-02-08", + "open": 169.649994, + "high": 171.429993, + "low": 168.880005, + "close": 169.839996, + "adjClose": 169.839996, + "volume": 42316500 + }, + { + "date": "2024-02-09", + "open": 170.899994, + "high": 175.0, + "low": 170.580002, + "close": 174.449997, + "adjClose": 174.449997, + "volume": 56986000 + }, + { + "date": "2024-02-12", + "open": 174.800003, + "high": 175.389999, + "low": 171.539993, + "close": 172.339996, + "adjClose": 172.339996, + "volume": 51050400 + }, + { + "date": "2024-02-13", + "open": 167.729996, + "high": 170.949997, + "low": 165.75, + "close": 168.639999, + "adjClose": 168.639999, + "volume": 56345100 + }, + { + "date": "2024-02-14", + "open": 169.210007, + "high": 171.210007, + "low": 168.279999, + "close": 170.979996, + "adjClose": 170.979996, + "volume": 42815500 + }, + { + "date": "2024-02-15", + "open": 170.580002, + "high": 171.169998, + "low": 167.589996, + "close": 169.800003, + "adjClose": 169.800003, + "volume": 49855200 + }, + { + "date": "2024-02-16", + "open": 168.740005, + "high": 170.419998, + "low": 167.169998, + "close": 169.509995, + "adjClose": 169.509995, + "volume": 48074600 + }, + { + "date": "2024-02-20", + "open": 167.830002, + "high": 168.710007, + "low": 165.740005, + "close": 167.080002, + "adjClose": 167.080002, + "volume": 41980300 + }, + { + "date": "2024-02-21", + "open": 168.940002, + "high": 170.229996, + "low": 167.139999, + "close": 168.589996, + "adjClose": 168.589996, + "volume": 44575600 + }, + { + "date": "2024-02-22", + "open": 173.100006, + "high": 174.800003, + "low": 171.770004, + "close": 174.580002, + "adjClose": 174.580002, + "volume": 55392400 + }, + { + "date": "2024-02-23", + "open": 174.279999, + "high": 175.75, + "low": 173.699997, + "close": 174.990005, + "adjClose": 174.990005, + "volume": 59715200 + }, + { + "date": "2024-02-26", + "open": 175.699997, + "high": 176.369995, + "low": 174.259995, + "close": 174.729996, + "adjClose": 174.729996, + "volume": 44368600 + }, + { + "date": "2024-02-27", + "open": 174.080002, + "high": 174.619995, + "low": 172.860001, + "close": 173.539993, + "adjClose": 173.539993, + "volume": 31141700 + }, + { + "date": "2024-02-28", + "open": 172.440002, + "high": 174.050003, + "low": 172.270004, + "close": 173.160004, + "adjClose": 173.160004, + "volume": 28180500 + }, + { + "date": "2024-02-29", + "open": 173.009995, + "high": 177.220001, + "low": 172.850006, + "close": 176.759995, + "adjClose": 176.759995, + "volume": 53805400 + }, + { + "date": "2024-03-01", + "open": 176.75, + "high": 178.729996, + "low": 176.070007, + "close": 178.220001, + "adjClose": 178.220001, + "volume": 31956200 + }, + { + "date": "2024-03-04", + "open": 177.529999, + "high": 180.139999, + "low": 177.490005, + "close": 177.580002, + "adjClose": 177.580002, + "volume": 37381500 + }, + { + "date": "2024-03-05", + "open": 176.929993, + "high": 176.929993, + "low": 173.300003, + "close": 174.119995, + "adjClose": 174.119995, + "volume": 37228300 + }, + { + "date": "2024-03-06", + "open": 175.539993, + "high": 176.460007, + "low": 173.259995, + "close": 173.509995, + "adjClose": 173.509995, + "volume": 32090900 + }, + { + "date": "2024-03-07", + "open": 174.830002, + "high": 177.990005, + "low": 173.720001, + "close": 176.820007, + "adjClose": 176.820007, + "volume": 34063300 + }, + { + "date": "2024-03-08", + "open": 176.440002, + "high": 178.789993, + "low": 174.330002, + "close": 175.350006, + "adjClose": 175.350006, + "volume": 37853500 + }, + { + "date": "2024-03-11", + "open": 174.309998, + "high": 174.470001, + "low": 171.470001, + "close": 171.960007, + "adjClose": 171.960007, + "volume": 28484800 + }, + { + "date": "2024-03-12", + "open": 173.5, + "high": 176.759995, + "low": 171.979996, + "close": 175.389999, + "adjClose": 175.389999, + "volume": 36610600 + }, + { + "date": "2024-03-13", + "open": 175.899994, + "high": 177.619995, + "low": 175.550003, + "close": 176.559998, + "adjClose": 176.559998, + "volume": 30772600 + }, + { + "date": "2024-03-14", + "open": 177.690002, + "high": 179.529999, + "low": 176.470001, + "close": 178.75, + "adjClose": 178.75, + "volume": 43705800 + }, + { + "date": "2024-03-15", + "open": 176.639999, + "high": 177.929993, + "low": 173.899994, + "close": 174.419998, + "adjClose": 174.419998, + "volume": 72115500 + }, + { + "date": "2024-03-18", + "open": 175.800003, + "high": 176.690002, + "low": 174.279999, + "close": 174.479996, + "adjClose": 174.479996, + "volume": 31250700 + }, + { + "date": "2024-03-19", + "open": 174.220001, + "high": 176.089996, + "low": 173.520004, + "close": 175.899994, + "adjClose": 175.899994, + "volume": 26880900 + }, + { + "date": "2024-03-20", + "open": 176.139999, + "high": 178.529999, + "low": 174.639999, + "close": 178.149994, + "adjClose": 178.149994, + "volume": 29947200 + }, + { + "date": "2024-03-21", + "open": 179.990005, + "high": 181.419998, + "low": 178.149994, + "close": 178.149994, + "adjClose": 178.149994, + "volume": 32824300 + }, + { + "date": "2024-03-22", + "open": 177.75, + "high": 179.259995, + "low": 176.75, + "close": 178.869995, + "adjClose": 178.869995, + "volume": 27964100 + }, + { + "date": "2024-03-25", + "open": 178.009995, + "high": 180.990005, + "low": 177.240005, + "close": 179.710007, + "adjClose": 179.710007, + "volume": 29815500 + }, + { + "date": "2024-03-26", + "open": 180.149994, + "high": 180.449997, + "low": 177.949997, + "close": 178.300003, + "adjClose": 178.300003, + "volume": 29659000 + }, + { + "date": "2024-03-27", + "open": 179.880005, + "high": 180.0, + "low": 177.309998, + "close": 179.830002, + "adjClose": 179.830002, + "volume": 33272600 + }, + { + "date": "2024-03-28", + "open": 180.169998, + "high": 181.699997, + "low": 179.259995, + "close": 180.380005, + "adjClose": 180.380005, + "volume": 38051600 + }, + { + "date": "2024-04-01", + "open": 180.789993, + "high": 183.0, + "low": 179.949997, + "close": 180.970001, + "adjClose": 180.970001, + "volume": 29174500 + }, + { + "date": "2024-04-02", + "open": 179.070007, + "high": 180.789993, + "low": 178.380005, + "close": 180.690002, + "adjClose": 180.690002, + "volume": 32611500 + }, + { + "date": "2024-04-03", + "open": 179.899994, + "high": 182.869995, + "low": 179.800003, + "close": 182.410004, + "adjClose": 182.410004, + "volume": 31046600 + }, + { + "date": "2024-04-04", + "open": 184.0, + "high": 185.100006, + "low": 180.0, + "close": 180.0, + "adjClose": 180.0, + "volume": 41624300 + }, + { + "date": "2024-04-05", + "open": 182.380005, + "high": 186.270004, + "low": 181.970001, + "close": 185.070007, + "adjClose": 185.070007, + "volume": 42335200 + }, + { + "date": "2024-04-08", + "open": 186.899994, + "high": 187.289993, + "low": 184.809998, + "close": 185.190002, + "adjClose": 185.190002, + "volume": 39221300 + }, + { + "date": "2024-04-09", + "open": 187.240005, + "high": 187.339996, + "low": 184.199997, + "close": 185.669998, + "adjClose": 185.669998, + "volume": 36546900 + }, + { + "date": "2024-04-10", + "open": 182.770004, + "high": 186.270004, + "low": 182.669998, + "close": 185.949997, + "adjClose": 185.949997, + "volume": 35879200 + }, + { + "date": "2024-04-11", + "open": 186.740005, + "high": 189.770004, + "low": 185.509995, + "close": 189.050003, + "adjClose": 189.050003, + "volume": 40020700 + }, + { + "date": "2024-04-12", + "open": 187.720001, + "high": 188.380005, + "low": 185.080002, + "close": 186.130005, + "adjClose": 186.130005, + "volume": 38554300 + }, + { + "date": "2024-04-15", + "open": 187.429993, + "high": 188.690002, + "low": 183.0, + "close": 183.619995, + "adjClose": 183.619995, + "volume": 48052400 + }, + { + "date": "2024-04-16", + "open": 183.270004, + "high": 184.830002, + "low": 182.259995, + "close": 183.320007, + "adjClose": 183.320007, + "volume": 32891300 + }, + { + "date": "2024-04-17", + "open": 184.309998, + "high": 184.570007, + "low": 179.820007, + "close": 181.279999, + "adjClose": 181.279999, + "volume": 31359700 + }, + { + "date": "2024-04-18", + "open": 181.470001, + "high": 182.389999, + "low": 178.649994, + "close": 179.220001, + "adjClose": 179.220001, + "volume": 30723800 + }, + { + "date": "2024-04-19", + "open": 178.740005, + "high": 179.0, + "low": 173.440002, + "close": 174.630005, + "adjClose": 174.630005, + "volume": 55950000 + }, + { + "date": "2024-04-22", + "open": 176.940002, + "high": 178.869995, + "low": 174.559998, + "close": 177.229996, + "adjClose": 177.229996, + "volume": 37924900 + }, + { + "date": "2024-04-23", + "open": 178.080002, + "high": 179.929993, + "low": 175.979996, + "close": 179.539993, + "adjClose": 179.539993, + "volume": 37046500 + }, + { + "date": "2024-04-24", + "open": 179.940002, + "high": 180.320007, + "low": 176.179993, + "close": 176.589996, + "adjClose": 176.589996, + "volume": 34185100 + }, + { + "date": "2024-04-25", + "open": 169.679993, + "high": 173.919998, + "low": 166.320007, + "close": 173.669998, + "adjClose": 173.669998, + "volume": 49249400 + }, + { + "date": "2024-04-26", + "open": 177.800003, + "high": 180.820007, + "low": 176.130005, + "close": 179.619995, + "adjClose": 179.619995, + "volume": 43919800 + }, + { + "date": "2024-04-29", + "open": 182.75, + "high": 183.529999, + "low": 179.389999, + "close": 180.960007, + "adjClose": 180.960007, + "volume": 54063900 + }, + { + "date": "2024-04-30", + "open": 181.089996, + "high": 182.990005, + "low": 174.800003, + "close": 175.0, + "adjClose": 175.0, + "volume": 94639800 + }, + { + "date": "2024-05-01", + "open": 181.639999, + "high": 185.149994, + "low": 176.559998, + "close": 179.0, + "adjClose": 179.0, + "volume": 94645100 + }, + { + "date": "2024-05-02", + "open": 180.850006, + "high": 185.100006, + "low": 179.910004, + "close": 184.720001, + "adjClose": 184.720001, + "volume": 54303500 + }, + { + "date": "2024-05-03", + "open": 186.990005, + "high": 187.869995, + "low": 185.419998, + "close": 186.210007, + "adjClose": 186.210007, + "volume": 39172000 + }, + { + "date": "2024-05-06", + "open": 186.279999, + "high": 188.75, + "low": 184.800003, + "close": 188.699997, + "adjClose": 188.699997, + "volume": 34725300 + }, + { + "date": "2024-05-07", + "open": 188.919998, + "high": 189.940002, + "low": 187.309998, + "close": 188.759995, + "adjClose": 188.759995, + "volume": 34048900 + }, + { + "date": "2024-05-08", + "open": 187.440002, + "high": 188.429993, + "low": 186.389999, + "close": 188.0, + "adjClose": 188.0, + "volume": 26136400 + }, + { + "date": "2024-05-09", + "open": 188.880005, + "high": 191.699997, + "low": 187.440002, + "close": 189.5, + "adjClose": 189.5, + "volume": 43368400 + }, + { + "date": "2024-05-10", + "open": 189.160004, + "high": 189.889999, + "low": 186.929993, + "close": 187.479996, + "adjClose": 187.479996, + "volume": 34141800 + }, + { + "date": "2024-05-13", + "open": 188.0, + "high": 188.309998, + "low": 185.360001, + "close": 186.570007, + "adjClose": 186.570007, + "volume": 24898600 + }, + { + "date": "2024-05-14", + "open": 183.820007, + "high": 187.720001, + "low": 183.449997, + "close": 187.070007, + "adjClose": 187.070007, + "volume": 38698200 + }, + { + "date": "2024-05-15", + "open": 185.970001, + "high": 186.720001, + "low": 182.729996, + "close": 185.990005, + "adjClose": 185.990005, + "volume": 75459900 + }, + { + "date": "2024-05-16", + "open": 185.600006, + "high": 187.309998, + "low": 183.460007, + "close": 183.630005, + "adjClose": 183.630005, + "volume": 38834500 + }, + { + "date": "2024-05-17", + "open": 183.759995, + "high": 185.300003, + "low": 183.350006, + "close": 184.699997, + "adjClose": 184.699997, + "volume": 33175700 + }, + { + "date": "2024-05-20", + "open": 184.339996, + "high": 186.669998, + "low": 183.279999, + "close": 183.539993, + "adjClose": 183.539993, + "volume": 30511800 + }, + { + "date": "2024-05-21", + "open": 182.300003, + "high": 183.259995, + "low": 180.75, + "close": 183.149994, + "adjClose": 183.149994, + "volume": 50839100 + }, + { + "date": "2024-05-22", + "open": 183.880005, + "high": 185.220001, + "low": 181.970001, + "close": 183.130005, + "adjClose": 183.130005, + "volume": 28148800 + }, + { + "date": "2024-05-23", + "open": 183.660004, + "high": 184.759995, + "low": 180.080002, + "close": 181.050003, + "adjClose": 181.050003, + "volume": 33670200 + }, + { + "date": "2024-05-24", + "open": 181.649994, + "high": 182.440002, + "low": 180.300003, + "close": 180.75, + "adjClose": 180.75, + "volume": 27434100 + }, + { + "date": "2024-05-28", + "open": 179.929993, + "high": 182.240005, + "low": 179.490005, + "close": 182.149994, + "adjClose": 182.149994, + "volume": 29927000 + }, + { + "date": "2024-05-29", + "open": 181.699997, + "high": 184.080002, + "low": 181.550003, + "close": 182.020004, + "adjClose": 182.020004, + "volume": 32009300 + }, + { + "date": "2024-05-30", + "open": 181.309998, + "high": 181.339996, + "low": 178.360001, + "close": 179.320007, + "adjClose": 179.320007, + "volume": 29249200 + }, + { + "date": "2024-05-31", + "open": 178.300003, + "high": 179.210007, + "low": 173.869995, + "close": 176.440002, + "adjClose": 176.440002, + "volume": 58903900 + }, + { + "date": "2024-06-03", + "open": 177.699997, + "high": 178.699997, + "low": 175.919998, + "close": 178.339996, + "adjClose": 178.339996, + "volume": 30786600 + }, + { + "date": "2024-06-04", + "open": 177.639999, + "high": 179.820007, + "low": 176.440002, + "close": 179.339996, + "adjClose": 179.339996, + "volume": 27198400 + }, + { + "date": "2024-06-05", + "open": 180.100006, + "high": 181.5, + "low": 178.75, + "close": 181.279999, + "adjClose": 181.279999, + "volume": 32116400 + }, + { + "date": "2024-06-06", + "open": 181.75, + "high": 185.0, + "low": 181.490005, + "close": 185.0, + "adjClose": 185.0, + "volume": 31371200 + }, + { + "date": "2024-06-07", + "open": 184.899994, + "high": 186.289993, + "low": 183.360001, + "close": 184.300003, + "adjClose": 184.300003, + "volume": 28021500 + }, + { + "date": "2024-06-10", + "open": 184.070007, + "high": 187.229996, + "low": 183.789993, + "close": 187.059998, + "adjClose": 187.059998, + "volume": 34494500 + }, + { + "date": "2024-06-11", + "open": 187.059998, + "high": 187.770004, + "low": 184.539993, + "close": 187.229996, + "adjClose": 187.229996, + "volume": 27265100 + }, + { + "date": "2024-06-12", + "open": 188.020004, + "high": 188.350006, + "low": 185.429993, + "close": 186.889999, + "adjClose": 186.889999, + "volume": 33984200 + }, + { + "date": "2024-06-13", + "open": 186.089996, + "high": 187.669998, + "low": 182.669998, + "close": 183.830002, + "adjClose": 183.830002, + "volume": 39721500 + }, + { + "date": "2024-06-14", + "open": 183.080002, + "high": 183.720001, + "low": 182.229996, + "close": 183.660004, + "adjClose": 183.660004, + "volume": 25456400 + }, + { + "date": "2024-06-17", + "open": 182.520004, + "high": 185.0, + "low": 181.220001, + "close": 184.059998, + "adjClose": 184.059998, + "volume": 35601900 + }, + { + "date": "2024-06-18", + "open": 183.740005, + "high": 184.289993, + "low": 181.429993, + "close": 182.809998, + "adjClose": 182.809998, + "volume": 36659200 + }, + { + "date": "2024-06-20", + "open": 182.910004, + "high": 186.509995, + "low": 182.720001, + "close": 186.100006, + "adjClose": 186.100006, + "volume": 44726800 + }, + { + "date": "2024-06-21", + "open": 187.800003, + "high": 189.279999, + "low": 185.860001, + "close": 189.080002, + "adjClose": 189.080002, + "volume": 72931800 + }, + { + "date": "2024-06-24", + "open": 189.330002, + "high": 191.0, + "low": 185.330002, + "close": 185.570007, + "adjClose": 185.570007, + "volume": 50610400 + }, + { + "date": "2024-06-25", + "open": 186.809998, + "high": 188.839996, + "low": 185.419998, + "close": 186.339996, + "adjClose": 186.339996, + "volume": 45898500 + }, + { + "date": "2024-06-26", + "open": 186.919998, + "high": 194.800003, + "low": 186.259995, + "close": 193.610001, + "adjClose": 193.610001, + "volume": 65103900 + }, + { + "date": "2024-06-27", + "open": 195.009995, + "high": 199.839996, + "low": 194.199997, + "close": 197.850006, + "adjClose": 197.850006, + "volume": 74397500 + }, + { + "date": "2024-06-28", + "open": 197.729996, + "high": 198.850006, + "low": 192.5, + "close": 193.25, + "adjClose": 193.25, + "volume": 76930200 + }, + { + "date": "2024-07-01", + "open": 193.490005, + "high": 198.300003, + "low": 192.820007, + "close": 197.199997, + "adjClose": 197.199997, + "volume": 41192000 + }, + { + "date": "2024-07-02", + "open": 197.279999, + "high": 200.429993, + "low": 195.929993, + "close": 200.0, + "adjClose": 200.0, + "volume": 45600000 + }, + { + "date": "2024-07-03", + "open": 199.940002, + "high": 200.029999, + "low": 196.759995, + "close": 197.589996, + "adjClose": 197.589996, + "volume": 31597900 + }, + { + "date": "2024-07-05", + "open": 198.649994, + "high": 200.550003, + "low": 198.169998, + "close": 200.0, + "adjClose": 200.0, + "volume": 39858900 + }, + { + "date": "2024-07-08", + "open": 200.039993, + "high": 201.199997, + "low": 197.960007, + "close": 199.289993, + "adjClose": 199.289993, + "volume": 34767300 + }, + { + "date": "2024-07-09", + "open": 199.399994, + "high": 200.570007, + "low": 199.050003, + "close": 199.339996, + "adjClose": 199.339996, + "volume": 32700100 + }, + { + "date": "2024-07-10", + "open": 200.0, + "high": 200.110001, + "low": 197.690002, + "close": 199.789993, + "adjClose": 199.789993, + "volume": 32883800 + }, + { + "date": "2024-07-11", + "open": 200.089996, + "high": 200.270004, + "low": 192.860001, + "close": 195.050003, + "adjClose": 195.050003, + "volume": 44565000 + }, + { + "date": "2024-07-12", + "open": 194.800003, + "high": 196.470001, + "low": 193.830002, + "close": 194.490005, + "adjClose": 194.490005, + "volume": 30598500 + }, + { + "date": "2024-07-15", + "open": 194.559998, + "high": 196.190002, + "low": 190.830002, + "close": 192.720001, + "adjClose": 192.720001, + "volume": 40683200 + }, + { + "date": "2024-07-16", + "open": 195.589996, + "high": 196.619995, + "low": 192.240005, + "close": 193.020004, + "adjClose": 193.020004, + "volume": 33994700 + }, + { + "date": "2024-07-17", + "open": 191.350006, + "high": 191.580002, + "low": 185.990005, + "close": 187.929993, + "adjClose": 187.929993, + "volume": 48076100 + }, + { + "date": "2024-07-18", + "open": 189.589996, + "high": 189.679993, + "low": 181.449997, + "close": 183.75, + "adjClose": 183.75, + "volume": 51043600 + }, + { + "date": "2024-07-19", + "open": 181.139999, + "high": 184.929993, + "low": 180.110001, + "close": 183.130005, + "adjClose": 183.130005, + "volume": 43081800 + }, + { + "date": "2024-07-22", + "open": 185.0, + "high": 185.059998, + "low": 182.479996, + "close": 182.550003, + "adjClose": 182.550003, + "volume": 39874900 + } +] diff --git a/docs/data/charts/dataset/GOOGL.json b/docs/data/charts/dataset/GOOGL.json new file mode 100644 index 0000000000000..7680847baff9a --- /dev/null +++ b/docs/data/charts/dataset/GOOGL.json @@ -0,0 +1,2261 @@ +[ + { + "date": "2023-07-24", + "open": 121.660004, + "high": 123.0, + "low": 120.980003, + "close": 121.529999, + "adjClose": 121.390678, + "volume": 29686100 + }, + { + "date": "2023-07-25", + "open": 121.360001, + "high": 123.150002, + "low": 121.019997, + "close": 122.209999, + "adjClose": 122.069901, + "volume": 52509600 + }, + { + "date": "2023-07-26", + "open": 130.070007, + "high": 130.979996, + "low": 128.320007, + "close": 129.270004, + "adjClose": 129.121811, + "volume": 61682100 + }, + { + "date": "2023-07-27", + "open": 131.669998, + "high": 133.240005, + "low": 128.789993, + "close": 129.399994, + "adjClose": 129.251648, + "volume": 44952100 + }, + { + "date": "2023-07-28", + "open": 130.779999, + "high": 133.740005, + "low": 130.570007, + "close": 132.580002, + "adjClose": 132.428009, + "volume": 36591200 + }, + { + "date": "2023-07-31", + "open": 132.729996, + "high": 133.529999, + "low": 131.779999, + "close": 132.720001, + "adjClose": 132.567856, + "volume": 28055500 + }, + { + "date": "2023-08-01", + "open": 130.779999, + "high": 132.630005, + "low": 130.679993, + "close": 131.550003, + "adjClose": 131.3992, + "volume": 23166800 + }, + { + "date": "2023-08-02", + "open": 129.449997, + "high": 130.089996, + "low": 127.559998, + "close": 128.380005, + "adjClose": 128.232834, + "volume": 26273300 + }, + { + "date": "2023-08-03", + "open": 127.970001, + "high": 129.389999, + "low": 127.419998, + "close": 128.449997, + "adjClose": 128.30275, + "volume": 20089500 + }, + { + "date": "2023-08-04", + "open": 129.279999, + "high": 131.509995, + "low": 127.910004, + "close": 128.110001, + "adjClose": 127.963135, + "volume": 26130000 + }, + { + "date": "2023-08-07", + "open": 129.160004, + "high": 131.610001, + "low": 129.020004, + "close": 131.529999, + "adjClose": 131.379211, + "volume": 22746300 + }, + { + "date": "2023-08-08", + "open": 130.619995, + "high": 131.509995, + "low": 129.539993, + "close": 131.399994, + "adjClose": 131.249359, + "volume": 23535200 + }, + { + "date": "2023-08-09", + "open": 131.660004, + "high": 132.039993, + "low": 129.0, + "close": 129.660004, + "adjClose": 129.511368, + "volume": 24912900 + }, + { + "date": "2023-08-10", + "open": 131.320007, + "high": 132.050003, + "low": 129.449997, + "close": 129.690002, + "adjClose": 129.541336, + "volume": 20857800 + }, + { + "date": "2023-08-11", + "open": 128.660004, + "high": 129.929993, + "low": 128.169998, + "close": 129.559998, + "adjClose": 129.411469, + "volume": 19569200 + }, + { + "date": "2023-08-14", + "open": 129.389999, + "high": 131.369995, + "low": 128.960007, + "close": 131.330002, + "adjClose": 131.179443, + "volume": 24695600 + }, + { + "date": "2023-08-15", + "open": 131.100006, + "high": 131.419998, + "low": 129.279999, + "close": 129.779999, + "adjClose": 129.631226, + "volume": 19770700 + }, + { + "date": "2023-08-16", + "open": 128.699997, + "high": 130.279999, + "low": 127.870003, + "close": 128.699997, + "adjClose": 128.55246, + "volume": 25216100 + }, + { + "date": "2023-08-17", + "open": 129.800003, + "high": 131.990005, + "low": 129.289993, + "close": 129.919998, + "adjClose": 129.771057, + "volume": 33446300 + }, + { + "date": "2023-08-18", + "open": 128.509995, + "high": 129.25, + "low": 126.379997, + "close": 127.459999, + "adjClose": 127.313881, + "volume": 30491300 + }, + { + "date": "2023-08-21", + "open": 127.18, + "high": 128.729996, + "low": 126.559998, + "close": 128.369995, + "adjClose": 128.222839, + "volume": 25248700 + }, + { + "date": "2023-08-22", + "open": 128.509995, + "high": 130.279999, + "low": 128.320007, + "close": 129.080002, + "adjClose": 128.932022, + "volume": 22067500 + }, + { + "date": "2023-08-23", + "open": 130.179993, + "high": 133.410004, + "low": 129.869995, + "close": 132.369995, + "adjClose": 132.218246, + "volume": 27819700 + }, + { + "date": "2023-08-24", + "open": 133.949997, + "high": 134.25, + "low": 129.570007, + "close": 129.779999, + "adjClose": 129.631226, + "volume": 28500700 + }, + { + "date": "2023-08-25", + "open": 129.539993, + "high": 130.759995, + "low": 127.25, + "close": 129.880005, + "adjClose": 129.73111, + "volume": 26762900 + }, + { + "date": "2023-08-28", + "open": 131.309998, + "high": 132.539993, + "low": 130.139999, + "close": 131.009995, + "adjClose": 130.859802, + "volume": 20543300 + }, + { + "date": "2023-08-29", + "open": 132.240005, + "high": 136.570007, + "low": 132.240005, + "close": 134.570007, + "adjClose": 134.415741, + "volume": 43075600 + }, + { + "date": "2023-08-30", + "open": 134.779999, + "high": 136.279999, + "low": 134.070007, + "close": 135.880005, + "adjClose": 135.724228, + "volume": 28315800 + }, + { + "date": "2023-08-31", + "open": 136.009995, + "high": 138.0, + "low": 135.789993, + "close": 136.169998, + "adjClose": 136.013901, + "volume": 30053800 + }, + { + "date": "2023-09-01", + "open": 137.460007, + "high": 137.460007, + "low": 134.850006, + "close": 135.660004, + "adjClose": 135.504486, + "volume": 21524600 + }, + { + "date": "2023-09-05", + "open": 135.440002, + "high": 136.419998, + "low": 134.580002, + "close": 135.770004, + "adjClose": 135.614365, + "volume": 19403100 + }, + { + "date": "2023-09-06", + "open": 136.020004, + "high": 136.529999, + "low": 133.669998, + "close": 134.460007, + "adjClose": 134.305862, + "volume": 18684500 + }, + { + "date": "2023-09-07", + "open": 133.589996, + "high": 135.580002, + "low": 132.949997, + "close": 135.259995, + "adjClose": 135.104935, + "volume": 18844300 + }, + { + "date": "2023-09-08", + "open": 134.910004, + "high": 136.660004, + "low": 134.850006, + "close": 136.380005, + "adjClose": 136.223663, + "volume": 23558300 + }, + { + "date": "2023-09-11", + "open": 136.539993, + "high": 137.479996, + "low": 135.789993, + "close": 136.919998, + "adjClose": 136.763031, + "volume": 20763400 + }, + { + "date": "2023-09-12", + "open": 136.259995, + "high": 136.869995, + "low": 135.190002, + "close": 135.339996, + "adjClose": 135.184845, + "volume": 18405500 + }, + { + "date": "2023-09-13", + "open": 135.089996, + "high": 136.899994, + "low": 134.149994, + "close": 136.710007, + "adjClose": 136.553284, + "volume": 20749500 + }, + { + "date": "2023-09-14", + "open": 137.600006, + "high": 138.699997, + "low": 136.240005, + "close": 138.100006, + "adjClose": 137.941696, + "volume": 24751000 + }, + { + "date": "2023-09-15", + "open": 137.979996, + "high": 138.520004, + "low": 136.479996, + "close": 137.399994, + "adjClose": 137.242477, + "volume": 38908400 + }, + { + "date": "2023-09-18", + "open": 136.610001, + "high": 139.160004, + "low": 136.610001, + "close": 138.210007, + "adjClose": 138.051559, + "volume": 21861300 + }, + { + "date": "2023-09-19", + "open": 137.419998, + "high": 138.410004, + "low": 136.619995, + "close": 138.039993, + "adjClose": 137.881744, + "volume": 20353700 + }, + { + "date": "2023-09-20", + "open": 138.080002, + "high": 138.080002, + "low": 133.619995, + "close": 133.740005, + "adjClose": 133.586685, + "volume": 29927500 + }, + { + "date": "2023-09-21", + "open": 131.440002, + "high": 132.229996, + "low": 130.070007, + "close": 130.440002, + "adjClose": 130.290466, + "volume": 31488700 + }, + { + "date": "2023-09-22", + "open": 130.759995, + "high": 132.029999, + "low": 129.600006, + "close": 130.25, + "adjClose": 130.100677, + "volume": 26397300 + }, + { + "date": "2023-09-25", + "open": 129.830002, + "high": 131.169998, + "low": 128.960007, + "close": 131.110001, + "adjClose": 130.959702, + "volume": 20094600 + }, + { + "date": "2023-09-26", + "open": 129.770004, + "high": 130.360001, + "low": 127.220001, + "close": 128.570007, + "adjClose": 128.422623, + "volume": 25718700 + }, + { + "date": "2023-09-27", + "open": 128.570007, + "high": 130.899994, + "low": 128.570007, + "close": 130.539993, + "adjClose": 130.39035, + "volume": 22746500 + }, + { + "date": "2023-09-28", + "open": 129.839996, + "high": 133.300003, + "low": 129.789993, + "close": 132.309998, + "adjClose": 132.158325, + "volume": 22513100 + }, + { + "date": "2023-09-29", + "open": 133.279999, + "high": 134.050003, + "low": 130.360001, + "close": 130.860001, + "adjClose": 130.709991, + "volume": 30848100 + }, + { + "date": "2023-10-02", + "open": 131.210007, + "high": 134.419998, + "low": 131.169998, + "close": 134.169998, + "adjClose": 134.01619, + "volume": 22288000 + }, + { + "date": "2023-10-03", + "open": 133.940002, + "high": 134.259995, + "low": 131.839996, + "close": 132.429993, + "adjClose": 132.278183, + "volume": 22989400 + }, + { + "date": "2023-10-04", + "open": 132.789993, + "high": 135.570007, + "low": 132.529999, + "close": 135.240005, + "adjClose": 135.084976, + "volume": 26752300 + }, + { + "date": "2023-10-05", + "open": 135.070007, + "high": 135.490005, + "low": 133.449997, + "close": 135.070007, + "adjClose": 134.915161, + "volume": 19832600 + }, + { + "date": "2023-10-06", + "open": 134.009995, + "high": 138.160004, + "low": 134.009995, + "close": 137.580002, + "adjClose": 137.422287, + "volume": 27583200 + }, + { + "date": "2023-10-09", + "open": 136.940002, + "high": 138.940002, + "low": 135.610001, + "close": 138.419998, + "adjClose": 138.261322, + "volume": 19278100 + }, + { + "date": "2023-10-10", + "open": 138.5, + "high": 139.720001, + "low": 137.330002, + "close": 138.059998, + "adjClose": 137.901733, + "volume": 27786600 + }, + { + "date": "2023-10-11", + "open": 138.580002, + "high": 141.110001, + "low": 138.580002, + "close": 140.550003, + "adjClose": 140.388885, + "volume": 25884300 + }, + { + "date": "2023-10-12", + "open": 141.050003, + "high": 141.220001, + "low": 138.259995, + "close": 138.970001, + "adjClose": 138.810684, + "volume": 24765500 + }, + { + "date": "2023-10-13", + "open": 139.380005, + "high": 140.0, + "low": 136.619995, + "close": 137.360001, + "adjClose": 137.20253, + "volume": 23420500 + }, + { + "date": "2023-10-16", + "open": 138.169998, + "high": 139.630005, + "low": 137.990005, + "close": 139.100006, + "adjClose": 138.940552, + "volume": 28501900 + }, + { + "date": "2023-10-17", + "open": 138.630005, + "high": 139.899994, + "low": 137.179993, + "close": 139.720001, + "adjClose": 139.55983, + "volume": 23515800 + }, + { + "date": "2023-10-18", + "open": 139.449997, + "high": 140.720001, + "low": 137.380005, + "close": 137.960007, + "adjClose": 137.801849, + "volume": 23375000 + }, + { + "date": "2023-10-19", + "open": 138.5, + "high": 139.660004, + "low": 137.380005, + "close": 137.75, + "adjClose": 137.592087, + "volume": 26066000 + }, + { + "date": "2023-10-20", + "open": 137.330002, + "high": 137.869995, + "low": 135.080002, + "close": 135.600006, + "adjClose": 135.444565, + "volume": 26315200 + }, + { + "date": "2023-10-23", + "open": 135.039993, + "high": 137.660004, + "low": 133.949997, + "close": 136.5, + "adjClose": 136.343521, + "volume": 26317900 + }, + { + "date": "2023-10-24", + "open": 137.830002, + "high": 139.360001, + "low": 137.419998, + "close": 138.809998, + "adjClose": 138.650864, + "volume": 44814300 + }, + { + "date": "2023-10-25", + "open": 128.160004, + "high": 128.309998, + "low": 125.07, + "close": 125.610001, + "adjClose": 125.466003, + "volume": 84366200 + }, + { + "date": "2023-10-26", + "open": 123.269997, + "high": 124.330002, + "low": 121.269997, + "close": 122.279999, + "adjClose": 122.139816, + "volume": 57061100 + }, + { + "date": "2023-10-27", + "open": 122.879997, + "high": 123.309998, + "low": 120.209999, + "close": 122.169998, + "adjClose": 122.029945, + "volume": 44566500 + }, + { + "date": "2023-10-30", + "open": 123.209999, + "high": 125.400002, + "low": 122.75, + "close": 124.459999, + "adjClose": 124.317322, + "volume": 28940100 + }, + { + "date": "2023-10-31", + "open": 125.059998, + "high": 125.370003, + "low": 122.690002, + "close": 124.080002, + "adjClose": 123.937759, + "volume": 26292300 + }, + { + "date": "2023-11-01", + "open": 124.07, + "high": 126.489998, + "low": 123.720001, + "close": 126.449997, + "adjClose": 126.305038, + "volume": 30082400 + }, + { + "date": "2023-11-02", + "open": 128.419998, + "high": 128.979996, + "low": 126.93, + "close": 127.489998, + "adjClose": 127.343849, + "volume": 27124600 + }, + { + "date": "2023-11-03", + "open": 128.020004, + "high": 129.529999, + "low": 127.860001, + "close": 129.100006, + "adjClose": 128.952011, + "volume": 26380100 + }, + { + "date": "2023-11-06", + "open": 129.050003, + "high": 130.339996, + "low": 128.669998, + "close": 130.25, + "adjClose": 130.100677, + "volume": 19052700 + }, + { + "date": "2023-11-07", + "open": 130.710007, + "high": 131.910004, + "low": 129.880005, + "close": 130.970001, + "adjClose": 130.819855, + "volume": 29757300 + }, + { + "date": "2023-11-08", + "open": 130.970001, + "high": 132.210007, + "low": 130.779999, + "close": 131.839996, + "adjClose": 131.688858, + "volume": 26425800 + }, + { + "date": "2023-11-09", + "open": 131.960007, + "high": 132.550003, + "low": 130.070007, + "close": 130.240005, + "adjClose": 130.090698, + "volume": 23747800 + }, + { + "date": "2023-11-10", + "open": 130.100006, + "high": 132.800003, + "low": 129.410004, + "close": 132.589996, + "adjClose": 132.438004, + "volume": 26913300 + }, + { + "date": "2023-11-13", + "open": 131.779999, + "high": 132.589996, + "low": 131.25, + "close": 132.089996, + "adjClose": 131.938568, + "volume": 18324800 + }, + { + "date": "2023-11-14", + "open": 134.190002, + "high": 135.699997, + "low": 133.320007, + "close": 133.619995, + "adjClose": 133.466812, + "volume": 32395200 + }, + { + "date": "2023-11-15", + "open": 134.869995, + "high": 135.029999, + "low": 133.570007, + "close": 134.619995, + "adjClose": 134.465668, + "volume": 23861500 + }, + { + "date": "2023-11-16", + "open": 135.190002, + "high": 137.220001, + "low": 134.320007, + "close": 136.929993, + "adjClose": 136.773026, + "volume": 28013200 + }, + { + "date": "2023-11-17", + "open": 136.0, + "high": 136.059998, + "low": 133.649994, + "close": 135.309998, + "adjClose": 135.154877, + "volume": 37240600 + }, + { + "date": "2023-11-20", + "open": 133.690002, + "high": 136.660004, + "low": 133.619995, + "close": 136.25, + "adjClose": 136.093811, + "volume": 27815500 + }, + { + "date": "2023-11-21", + "open": 136.289993, + "high": 137.179993, + "low": 135.960007, + "close": 136.970001, + "adjClose": 136.812988, + "volume": 22635300 + }, + { + "date": "2023-11-22", + "open": 137.470001, + "high": 139.419998, + "low": 137.470001, + "close": 138.490005, + "adjClose": 138.331238, + "volume": 17813900 + }, + { + "date": "2023-11-24", + "open": 138.029999, + "high": 138.130005, + "low": 135.990005, + "close": 136.690002, + "adjClose": 136.53331, + "volume": 12514300 + }, + { + "date": "2023-11-27", + "open": 136.029999, + "high": 138.419998, + "low": 136.0, + "close": 136.410004, + "adjClose": 136.253632, + "volume": 23436500 + }, + { + "date": "2023-11-28", + "open": 136.080002, + "high": 137.25, + "low": 135.419998, + "close": 137.199997, + "adjClose": 137.042709, + "volume": 18730000 + }, + { + "date": "2023-11-29", + "open": 137.570007, + "high": 138.289993, + "low": 134.839996, + "close": 134.990005, + "adjClose": 134.835251, + "volume": 23967200 + }, + { + "date": "2023-11-30", + "open": 135.050003, + "high": 135.550003, + "low": 131.279999, + "close": 132.529999, + "adjClose": 132.378067, + "volume": 38988300 + }, + { + "date": "2023-12-01", + "open": 131.860001, + "high": 132.110001, + "low": 130.669998, + "close": 131.860001, + "adjClose": 131.708847, + "volume": 31431200 + }, + { + "date": "2023-12-04", + "open": 129.880005, + "high": 130.029999, + "low": 127.900002, + "close": 129.270004, + "adjClose": 129.121811, + "volume": 36669900 + }, + { + "date": "2023-12-05", + "open": 128.949997, + "high": 132.139999, + "low": 128.25, + "close": 130.990005, + "adjClose": 130.839844, + "volume": 27384800 + }, + { + "date": "2023-12-06", + "open": 131.440002, + "high": 131.839996, + "low": 129.880005, + "close": 130.020004, + "adjClose": 129.870956, + "volume": 23576200 + }, + { + "date": "2023-12-07", + "open": 135.039993, + "high": 138.559998, + "low": 134.699997, + "close": 136.929993, + "adjClose": 136.773026, + "volume": 56767100 + }, + { + "date": "2023-12-08", + "open": 134.199997, + "high": 136.399994, + "low": 134.029999, + "close": 134.990005, + "adjClose": 134.835251, + "volume": 32233900 + }, + { + "date": "2023-12-11", + "open": 132.380005, + "high": 133.339996, + "low": 131.360001, + "close": 133.289993, + "adjClose": 133.137192, + "volume": 31138000 + }, + { + "date": "2023-12-12", + "open": 131.809998, + "high": 133.0, + "low": 131.259995, + "close": 132.520004, + "adjClose": 132.368088, + "volume": 29032800 + }, + { + "date": "2023-12-13", + "open": 133.380005, + "high": 133.5, + "low": 131.570007, + "close": 132.570007, + "adjClose": 132.41803, + "volume": 30104800 + }, + { + "date": "2023-12-14", + "open": 133.380005, + "high": 133.720001, + "low": 129.690002, + "close": 131.940002, + "adjClose": 131.788742, + "volume": 38722400 + }, + { + "date": "2023-12-15", + "open": 131.619995, + "high": 133.509995, + "low": 131.179993, + "close": 132.600006, + "adjClose": 132.447998, + "volume": 50815200 + }, + { + "date": "2023-12-18", + "open": 132.630005, + "high": 137.149994, + "low": 132.429993, + "close": 135.800003, + "adjClose": 135.644318, + "volume": 32258000 + }, + { + "date": "2023-12-19", + "open": 136.839996, + "high": 137.470001, + "low": 136.080002, + "close": 136.649994, + "adjClose": 136.493347, + "volume": 25476800 + }, + { + "date": "2023-12-20", + "open": 138.970001, + "high": 141.699997, + "low": 138.070007, + "close": 138.339996, + "adjClose": 138.181412, + "volume": 49107200 + }, + { + "date": "2023-12-21", + "open": 139.490005, + "high": 140.690002, + "low": 139.179993, + "close": 140.419998, + "adjClose": 140.259018, + "volume": 27488300 + }, + { + "date": "2023-12-22", + "open": 140.770004, + "high": 141.990005, + "low": 140.710007, + "close": 141.490005, + "adjClose": 141.327805, + "volume": 26514600 + }, + { + "date": "2023-12-26", + "open": 141.589996, + "high": 142.679993, + "low": 141.190002, + "close": 141.520004, + "adjClose": 141.357773, + "volume": 16780300 + }, + { + "date": "2023-12-27", + "open": 141.589996, + "high": 142.080002, + "low": 139.889999, + "close": 140.369995, + "adjClose": 140.209076, + "volume": 19628600 + }, + { + "date": "2023-12-28", + "open": 140.779999, + "high": 141.139999, + "low": 139.75, + "close": 140.229996, + "adjClose": 140.069244, + "volume": 16045700 + }, + { + "date": "2023-12-29", + "open": 139.630005, + "high": 140.360001, + "low": 138.779999, + "close": 139.690002, + "adjClose": 139.529861, + "volume": 18727200 + }, + { + "date": "2024-01-02", + "open": 138.550003, + "high": 139.449997, + "low": 136.479996, + "close": 138.169998, + "adjClose": 138.011597, + "volume": 23711200 + }, + { + "date": "2024-01-03", + "open": 137.25, + "high": 139.630005, + "low": 137.080002, + "close": 138.919998, + "adjClose": 138.760742, + "volume": 24212100 + }, + { + "date": "2024-01-04", + "open": 138.419998, + "high": 139.160004, + "low": 136.350006, + "close": 136.389999, + "adjClose": 136.233643, + "volume": 27137700 + }, + { + "date": "2024-01-05", + "open": 136.75, + "high": 137.160004, + "low": 135.149994, + "close": 135.729996, + "adjClose": 135.574402, + "volume": 22506000 + }, + { + "date": "2024-01-08", + "open": 136.289993, + "high": 139.009995, + "low": 136.259995, + "close": 138.839996, + "adjClose": 138.680832, + "volume": 21404000 + }, + { + "date": "2024-01-09", + "open": 138.5, + "high": 141.490005, + "low": 138.149994, + "close": 140.949997, + "adjClose": 140.788422, + "volume": 24759600 + }, + { + "date": "2024-01-10", + "open": 141.0, + "high": 143.0, + "low": 140.910004, + "close": 142.279999, + "adjClose": 142.116898, + "volume": 21320200 + }, + { + "date": "2024-01-11", + "open": 143.490005, + "high": 145.220001, + "low": 140.639999, + "close": 142.080002, + "adjClose": 141.91713, + "volume": 24008700 + }, + { + "date": "2024-01-12", + "open": 142.669998, + "high": 143.199997, + "low": 141.820007, + "close": 142.649994, + "adjClose": 142.486465, + "volume": 18768600 + }, + { + "date": "2024-01-16", + "open": 142.0, + "high": 144.350006, + "low": 141.449997, + "close": 142.490005, + "adjClose": 142.32666, + "volume": 22670500 + }, + { + "date": "2024-01-17", + "open": 141.350006, + "high": 141.839996, + "low": 138.899994, + "close": 141.470001, + "adjClose": 141.307831, + "volume": 20968600 + }, + { + "date": "2024-01-18", + "open": 142.050003, + "high": 144.210007, + "low": 141.990005, + "close": 143.479996, + "adjClose": 143.315521, + "volume": 25746400 + }, + { + "date": "2024-01-19", + "open": 144.740005, + "high": 146.449997, + "low": 144.380005, + "close": 146.380005, + "adjClose": 146.212204, + "volume": 33300700 + }, + { + "date": "2024-01-22", + "open": 147.100006, + "high": 148.389999, + "low": 145.839996, + "close": 145.990005, + "adjClose": 145.822647, + "volume": 32200400 + }, + { + "date": "2024-01-23", + "open": 145.889999, + "high": 147.179993, + "low": 145.5, + "close": 147.039993, + "adjClose": 146.871429, + "volume": 21636100 + }, + { + "date": "2024-01-24", + "open": 148.539993, + "high": 149.850006, + "low": 148.100006, + "close": 148.699997, + "adjClose": 148.529526, + "volume": 25233500 + }, + { + "date": "2024-01-25", + "open": 150.070007, + "high": 153.050003, + "low": 149.539993, + "close": 151.869995, + "adjClose": 151.695892, + "volume": 29149100 + }, + { + "date": "2024-01-26", + "open": 151.100006, + "high": 152.539993, + "low": 151.009995, + "close": 152.190002, + "adjClose": 152.015533, + "volume": 26115500 + }, + { + "date": "2024-01-29", + "open": 152.059998, + "high": 153.779999, + "low": 151.429993, + "close": 153.509995, + "adjClose": 153.334015, + "volume": 27784300 + }, + { + "date": "2024-01-30", + "open": 152.800003, + "high": 153.619995, + "low": 151.190002, + "close": 151.460007, + "adjClose": 151.286377, + "volume": 36331800 + }, + { + "date": "2024-01-31", + "open": 143.619995, + "high": 144.0, + "low": 139.869995, + "close": 140.100006, + "adjClose": 139.939392, + "volume": 71910000 + }, + { + "date": "2024-02-01", + "open": 142.119995, + "high": 143.059998, + "low": 140.789993, + "close": 141.160004, + "adjClose": 140.998184, + "volume": 40466500 + }, + { + "date": "2024-02-02", + "open": 139.259995, + "high": 142.619995, + "low": 136.5, + "close": 142.380005, + "adjClose": 142.216782, + "volume": 62470600 + }, + { + "date": "2024-02-05", + "open": 142.820007, + "high": 145.470001, + "low": 142.779999, + "close": 143.679993, + "adjClose": 143.515274, + "volume": 38505400 + }, + { + "date": "2024-02-06", + "open": 144.649994, + "high": 145.360001, + "low": 143.190002, + "close": 144.100006, + "adjClose": 143.934814, + "volume": 29128200 + }, + { + "date": "2024-02-07", + "open": 144.759995, + "high": 145.619995, + "low": 143.929993, + "close": 145.539993, + "adjClose": 145.373154, + "volume": 25208900 + }, + { + "date": "2024-02-08", + "open": 145.830002, + "high": 146.330002, + "low": 145.100006, + "close": 145.910004, + "adjClose": 145.742737, + "volume": 22563800 + }, + { + "date": "2024-02-09", + "open": 146.679993, + "high": 149.440002, + "low": 146.179993, + "close": 149.0, + "adjClose": 148.829193, + "volume": 26829500 + }, + { + "date": "2024-02-12", + "open": 148.419998, + "high": 149.339996, + "low": 147.369995, + "close": 147.529999, + "adjClose": 147.36087, + "volume": 21564100 + }, + { + "date": "2024-02-13", + "open": 144.919998, + "high": 146.669998, + "low": 143.690002, + "close": 145.139999, + "adjClose": 144.973618, + "volume": 27837700 + }, + { + "date": "2024-02-14", + "open": 146.080002, + "high": 146.520004, + "low": 144.089996, + "close": 145.940002, + "adjClose": 145.772705, + "volume": 22704200 + }, + { + "date": "2024-02-15", + "open": 143.139999, + "high": 143.520004, + "low": 140.460007, + "close": 142.770004, + "adjClose": 142.606339, + "volume": 37590700 + }, + { + "date": "2024-02-16", + "open": 142.990005, + "high": 143.190002, + "low": 140.139999, + "close": 140.520004, + "adjClose": 140.358917, + "volume": 31451100 + }, + { + "date": "2024-02-20", + "open": 139.660004, + "high": 142.080002, + "low": 139.490005, + "close": 141.119995, + "adjClose": 140.958221, + "volume": 25144700 + }, + { + "date": "2024-02-21", + "open": 141.449997, + "high": 142.690002, + "low": 140.679993, + "close": 142.550003, + "adjClose": 142.386581, + "volume": 23315700 + }, + { + "date": "2024-02-22", + "open": 144.929993, + "high": 145.0, + "low": 142.800003, + "close": 144.089996, + "adjClose": 143.92482, + "volume": 27191900 + }, + { + "date": "2024-02-23", + "open": 143.669998, + "high": 144.679993, + "low": 143.429993, + "close": 143.960007, + "adjClose": 143.794968, + "volume": 19493800 + }, + { + "date": "2024-02-26", + "open": 142.139999, + "high": 142.440002, + "low": 137.389999, + "close": 137.570007, + "adjClose": 137.412308, + "volume": 53641800 + }, + { + "date": "2024-02-27", + "open": 138.020004, + "high": 139.25, + "low": 137.089996, + "close": 138.880005, + "adjClose": 138.720795, + "volume": 33099200 + }, + { + "date": "2024-02-28", + "open": 137.899994, + "high": 138.009995, + "low": 135.410004, + "close": 136.380005, + "adjClose": 136.223663, + "volume": 37328600 + }, + { + "date": "2024-02-29", + "open": 137.279999, + "high": 138.860001, + "low": 136.399994, + "close": 138.460007, + "adjClose": 138.301285, + "volume": 42133000 + }, + { + "date": "2024-03-01", + "open": 138.429993, + "high": 138.869995, + "low": 136.919998, + "close": 137.139999, + "adjClose": 136.982788, + "volume": 31119500 + }, + { + "date": "2024-03-04", + "open": 135.660004, + "high": 135.660004, + "low": 131.910004, + "close": 133.350006, + "adjClose": 133.197144, + "volume": 55999400 + }, + { + "date": "2024-03-05", + "open": 131.880005, + "high": 133.240005, + "low": 130.669998, + "close": 132.669998, + "adjClose": 132.517914, + "volume": 40194800 + }, + { + "date": "2024-03-06", + "open": 133.119995, + "high": 133.580002, + "low": 130.850006, + "close": 131.399994, + "adjClose": 131.249359, + "volume": 35318600 + }, + { + "date": "2024-03-07", + "open": 132.789993, + "high": 134.940002, + "low": 131.610001, + "close": 134.380005, + "adjClose": 134.225952, + "volume": 37738200 + }, + { + "date": "2024-03-08", + "open": 134.210007, + "high": 138.089996, + "low": 134.0, + "close": 135.410004, + "adjClose": 135.254776, + "volume": 39343100 + }, + { + "date": "2024-03-11", + "open": 136.130005, + "high": 139.100006, + "low": 136.130005, + "close": 137.669998, + "adjClose": 137.512177, + "volume": 32437800 + }, + { + "date": "2024-03-12", + "open": 137.029999, + "high": 139.380005, + "low": 137.029999, + "close": 138.5, + "adjClose": 138.341232, + "volume": 27563400 + }, + { + "date": "2024-03-13", + "open": 139.0, + "high": 141.089996, + "low": 138.990005, + "close": 139.789993, + "adjClose": 139.629745, + "volume": 23347200 + }, + { + "date": "2024-03-14", + "open": 141.190002, + "high": 143.589996, + "low": 140.460007, + "close": 143.100006, + "adjClose": 142.935959, + "volume": 42753400 + }, + { + "date": "2024-03-15", + "open": 142.5, + "high": 143.179993, + "low": 140.029999, + "close": 141.179993, + "adjClose": 141.018143, + "volume": 49460600 + }, + { + "date": "2024-03-18", + "open": 148.610001, + "high": 152.149994, + "low": 147.169998, + "close": 147.679993, + "adjClose": 147.510696, + "volume": 69273700 + }, + { + "date": "2024-03-19", + "open": 148.160004, + "high": 148.789993, + "low": 146.080002, + "close": 147.029999, + "adjClose": 146.86145, + "volume": 24070400 + }, + { + "date": "2024-03-20", + "open": 148.0, + "high": 148.860001, + "low": 146.740005, + "close": 148.740005, + "adjClose": 148.569489, + "volume": 21311500 + }, + { + "date": "2024-03-21", + "open": 149.470001, + "high": 150.369995, + "low": 146.899994, + "close": 147.600006, + "adjClose": 147.430801, + "volume": 24755600 + }, + { + "date": "2024-03-22", + "open": 149.119995, + "high": 151.580002, + "low": 148.979996, + "close": 150.770004, + "adjClose": 150.597168, + "volume": 29175700 + }, + { + "date": "2024-03-25", + "open": 149.940002, + "high": 150.380005, + "low": 147.820007, + "close": 150.070007, + "adjClose": 149.897964, + "volume": 19229300 + }, + { + "date": "2024-03-26", + "open": 150.220001, + "high": 152.259995, + "low": 149.979996, + "close": 150.669998, + "adjClose": 150.497269, + "volume": 22149100 + }, + { + "date": "2024-03-27", + "open": 151.179993, + "high": 151.639999, + "low": 148.899994, + "close": 150.869995, + "adjClose": 150.697037, + "volume": 22879200 + }, + { + "date": "2024-03-28", + "open": 150.850006, + "high": 151.429993, + "low": 150.169998, + "close": 150.929993, + "adjClose": 150.756973, + "volume": 24485400 + }, + { + "date": "2024-04-01", + "open": 150.690002, + "high": 155.740005, + "low": 150.610001, + "close": 155.490005, + "adjClose": 155.311752, + "volume": 31730800 + }, + { + "date": "2024-04-02", + "open": 153.5, + "high": 154.699997, + "low": 152.149994, + "close": 154.559998, + "adjClose": 154.382813, + "volume": 24586000 + }, + { + "date": "2024-04-03", + "open": 153.600006, + "high": 155.080002, + "low": 152.729996, + "close": 154.919998, + "adjClose": 154.742401, + "volume": 24705000 + }, + { + "date": "2024-04-04", + "open": 153.5, + "high": 154.770004, + "low": 150.449997, + "close": 150.529999, + "adjClose": 150.357437, + "volume": 34724700 + }, + { + "date": "2024-04-05", + "open": 150.029999, + "high": 153.419998, + "low": 149.600006, + "close": 152.5, + "adjClose": 152.32518, + "volume": 23449300 + }, + { + "date": "2024-04-08", + "open": 152.779999, + "high": 155.270004, + "low": 152.610001, + "close": 154.850006, + "adjClose": 154.672485, + "volume": 20702000 + }, + { + "date": "2024-04-09", + "open": 156.089996, + "high": 158.559998, + "low": 155.190002, + "close": 156.600006, + "adjClose": 156.420486, + "volume": 31113000 + }, + { + "date": "2024-04-10", + "open": 156.210007, + "high": 156.610001, + "low": 154.679993, + "close": 156.139999, + "adjClose": 155.960999, + "volume": 22838600 + }, + { + "date": "2024-04-11", + "open": 156.910004, + "high": 159.679993, + "low": 156.460007, + "close": 159.410004, + "adjClose": 159.227264, + "volume": 27166400 + }, + { + "date": "2024-04-12", + "open": 157.960007, + "high": 160.220001, + "low": 157.139999, + "close": 157.729996, + "adjClose": 157.549179, + "volume": 25329200 + }, + { + "date": "2024-04-15", + "open": 158.860001, + "high": 159.240005, + "low": 154.589996, + "close": 154.860001, + "adjClose": 154.68248, + "volume": 27136500 + }, + { + "date": "2024-04-16", + "open": 154.190002, + "high": 155.649994, + "low": 153.429993, + "close": 154.399994, + "adjClose": 154.222992, + "volume": 20779500 + }, + { + "date": "2024-04-17", + "open": 155.619995, + "high": 157.080002, + "low": 154.580002, + "close": 155.470001, + "adjClose": 155.291779, + "volume": 21763100 + }, + { + "date": "2024-04-18", + "open": 155.339996, + "high": 156.940002, + "low": 154.619995, + "close": 156.009995, + "adjClose": 155.831146, + "volume": 19883000 + }, + { + "date": "2024-04-19", + "open": 156.199997, + "high": 156.360001, + "low": 152.300003, + "close": 154.089996, + "adjClose": 153.913345, + "volume": 32239100 + }, + { + "date": "2024-04-22", + "open": 154.309998, + "high": 157.639999, + "low": 154.059998, + "close": 156.279999, + "adjClose": 156.100845, + "volume": 26446200 + }, + { + "date": "2024-04-23", + "open": 156.960007, + "high": 158.970001, + "low": 156.279999, + "close": 158.259995, + "adjClose": 158.078568, + "volume": 21151600 + }, + { + "date": "2024-04-24", + "open": 157.490005, + "high": 159.570007, + "low": 157.169998, + "close": 159.130005, + "adjClose": 158.947586, + "volume": 22779100 + }, + { + "date": "2024-04-25", + "open": 151.330002, + "high": 156.490005, + "low": 150.869995, + "close": 156.0, + "adjClose": 155.821167, + "volume": 57109700 + }, + { + "date": "2024-04-26", + "open": 174.369995, + "high": 174.710007, + "low": 169.649994, + "close": 171.949997, + "adjClose": 171.752884, + "volume": 64665300 + }, + { + "date": "2024-04-29", + "open": 169.059998, + "high": 169.550003, + "low": 165.210007, + "close": 166.149994, + "adjClose": 165.959518, + "volume": 45610000 + }, + { + "date": "2024-04-30", + "open": 165.610001, + "high": 168.100006, + "low": 162.600006, + "close": 162.779999, + "adjClose": 162.593399, + "volume": 33562900 + }, + { + "date": "2024-05-01", + "open": 164.300003, + "high": 167.119995, + "low": 163.089996, + "close": 163.860001, + "adjClose": 163.67215, + "volume": 33493200 + }, + { + "date": "2024-05-02", + "open": 164.789993, + "high": 166.729996, + "low": 163.889999, + "close": 166.619995, + "adjClose": 166.428986, + "volume": 24294500 + }, + { + "date": "2024-05-03", + "open": 167.559998, + "high": 167.960007, + "low": 163.050003, + "close": 167.240005, + "adjClose": 167.048279, + "volume": 34662400 + }, + { + "date": "2024-05-06", + "open": 167.460007, + "high": 168.139999, + "low": 166.029999, + "close": 168.100006, + "adjClose": 167.907303, + "volume": 21871300 + }, + { + "date": "2024-05-07", + "open": 168.5, + "high": 171.759995, + "low": 168.389999, + "close": 171.25, + "adjClose": 171.05368, + "volume": 28039700 + }, + { + "date": "2024-05-08", + "open": 169.0, + "high": 170.149994, + "low": 168.740005, + "close": 169.380005, + "adjClose": 169.185837, + "volume": 19569100 + }, + { + "date": "2024-05-09", + "open": 169.389999, + "high": 170.690002, + "low": 168.179993, + "close": 169.960007, + "adjClose": 169.765167, + "volume": 15346700 + }, + { + "date": "2024-05-10", + "open": 168.029999, + "high": 169.850006, + "low": 166.190002, + "close": 168.649994, + "adjClose": 168.456665, + "volume": 29799900 + }, + { + "date": "2024-05-13", + "open": 164.259995, + "high": 169.279999, + "low": 164.0, + "close": 169.139999, + "adjClose": 168.946106, + "volume": 31327600 + }, + { + "date": "2024-05-14", + "open": 169.770004, + "high": 171.25, + "low": 168.800003, + "close": 170.339996, + "adjClose": 170.14473, + "volume": 25127100 + }, + { + "date": "2024-05-15", + "open": 170.630005, + "high": 172.649994, + "low": 170.509995, + "close": 172.509995, + "adjClose": 172.312241, + "volume": 26948400 + }, + { + "date": "2024-05-16", + "open": 173.289993, + "high": 175.119995, + "low": 172.690002, + "close": 174.179993, + "adjClose": 173.980316, + "volume": 27867900 + }, + { + "date": "2024-05-17", + "open": 174.179993, + "high": 176.270004, + "low": 173.690002, + "close": 176.059998, + "adjClose": 175.85817, + "volume": 24479300 + }, + { + "date": "2024-05-20", + "open": 176.190002, + "high": 178.770004, + "low": 176.080002, + "close": 176.919998, + "adjClose": 176.717178, + "volume": 22554400 + }, + { + "date": "2024-05-21", + "open": 176.899994, + "high": 178.149994, + "low": 175.809998, + "close": 177.850006, + "adjClose": 177.646118, + "volume": 16989400 + }, + { + "date": "2024-05-22", + "open": 176.639999, + "high": 177.149994, + "low": 175.210007, + "close": 176.380005, + "adjClose": 176.177811, + "volume": 17880000 + }, + { + "date": "2024-05-23", + "open": 177.070007, + "high": 178.25, + "low": 172.949997, + "close": 173.550003, + "adjClose": 173.351044, + "volume": 21024900 + }, + { + "date": "2024-05-24", + "open": 174.979996, + "high": 175.770004, + "low": 173.649994, + "close": 174.990005, + "adjClose": 174.789398, + "volume": 16572500 + }, + { + "date": "2024-05-28", + "open": 174.449997, + "high": 177.270004, + "low": 174.369995, + "close": 176.399994, + "adjClose": 176.197769, + "volume": 20572200 + }, + { + "date": "2024-05-29", + "open": 175.429993, + "high": 176.839996, + "low": 174.720001, + "close": 175.899994, + "adjClose": 175.698349, + "volume": 23388700 + }, + { + "date": "2024-05-30", + "open": 175.199997, + "high": 175.220001, + "low": 171.789993, + "close": 172.110001, + "adjClose": 171.912704, + "volume": 22958700 + }, + { + "date": "2024-05-31", + "open": 171.860001, + "high": 173.059998, + "low": 169.440002, + "close": 172.5, + "adjClose": 172.302246, + "volume": 37638900 + }, + { + "date": "2024-06-03", + "open": 172.539993, + "high": 174.529999, + "low": 171.160004, + "close": 173.169998, + "adjClose": 172.971481, + "volume": 27459100 + }, + { + "date": "2024-06-04", + "open": 173.279999, + "high": 173.850006, + "low": 171.889999, + "close": 173.789993, + "adjClose": 173.590759, + "volume": 26879600 + }, + { + "date": "2024-06-05", + "open": 175.199997, + "high": 176.649994, + "low": 173.929993, + "close": 175.410004, + "adjClose": 175.208923, + "volume": 22068500 + }, + { + "date": "2024-06-06", + "open": 175.899994, + "high": 177.149994, + "low": 175.75, + "close": 176.729996, + "adjClose": 176.52739, + "volume": 23251000 + }, + { + "date": "2024-06-07", + "open": 177.050003, + "high": 177.869995, + "low": 174.300003, + "close": 174.460007, + "adjClose": 174.26001, + "volume": 19661400 + }, + { + "date": "2024-06-10", + "open": 174.970001, + "high": 177.059998, + "low": 172.759995, + "close": 175.009995, + "adjClose": 175.009995, + "volume": 23779200 + }, + { + "date": "2024-06-11", + "open": 176.220001, + "high": 176.839996, + "low": 173.770004, + "close": 176.619995, + "adjClose": 176.619995, + "volume": 21540600 + }, + { + "date": "2024-06-12", + "open": 178.25, + "high": 180.410004, + "low": 176.110001, + "close": 177.789993, + "adjClose": 177.789993, + "volume": 27864700 + }, + { + "date": "2024-06-13", + "open": 176.110001, + "high": 176.740005, + "low": 174.880005, + "close": 175.160004, + "adjClose": 175.160004, + "volume": 20913300 + }, + { + "date": "2024-06-14", + "open": 174.220001, + "high": 177.059998, + "low": 174.149994, + "close": 176.789993, + "adjClose": 176.789993, + "volume": 18063600 + }, + { + "date": "2024-06-17", + "open": 175.460007, + "high": 178.360001, + "low": 174.809998, + "close": 177.240005, + "adjClose": 177.240005, + "volume": 19618500 + }, + { + "date": "2024-06-18", + "open": 177.139999, + "high": 177.389999, + "low": 174.100006, + "close": 175.089996, + "adjClose": 175.089996, + "volume": 21869900 + }, + { + "date": "2024-06-20", + "open": 175.369995, + "high": 177.289993, + "low": 174.990005, + "close": 176.300003, + "adjClose": 176.300003, + "volume": 20160100 + }, + { + "date": "2024-06-21", + "open": 177.0, + "high": 180.850006, + "low": 176.610001, + "close": 179.630005, + "adjClose": 179.630005, + "volume": 58582700 + }, + { + "date": "2024-06-24", + "open": 180.160004, + "high": 180.889999, + "low": 178.669998, + "close": 179.220001, + "adjClose": 179.220001, + "volume": 18298000 + }, + { + "date": "2024-06-25", + "open": 179.619995, + "high": 184.289993, + "low": 179.419998, + "close": 184.029999, + "adjClose": 184.029999, + "volume": 23235600 + }, + { + "date": "2024-06-26", + "open": 182.630005, + "high": 184.509995, + "low": 182.479996, + "close": 183.880005, + "adjClose": 183.880005, + "volume": 19839000 + }, + { + "date": "2024-06-27", + "open": 184.179993, + "high": 186.050003, + "low": 184.020004, + "close": 185.410004, + "adjClose": 185.410004, + "volume": 18848900 + }, + { + "date": "2024-06-28", + "open": 184.320007, + "high": 185.130005, + "low": 181.960007, + "close": 182.149994, + "adjClose": 182.149994, + "volume": 29156600 + }, + { + "date": "2024-07-01", + "open": 183.029999, + "high": 183.880005, + "low": 181.300003, + "close": 182.990005, + "adjClose": 182.990005, + "volume": 16006100 + }, + { + "date": "2024-07-02", + "open": 182.050003, + "high": 185.570007, + "low": 181.559998, + "close": 185.240005, + "adjClose": 185.240005, + "volume": 17372500 + }, + { + "date": "2024-07-03", + "open": 184.850006, + "high": 186.089996, + "low": 184.0, + "close": 185.820007, + "adjClose": 185.820007, + "volume": 10242100 + }, + { + "date": "2024-07-05", + "open": 185.860001, + "high": 190.860001, + "low": 185.800003, + "close": 190.600006, + "adjClose": 190.600006, + "volume": 20967500 + }, + { + "date": "2024-07-08", + "open": 189.899994, + "high": 190.169998, + "low": 187.779999, + "close": 189.029999, + "adjClose": 189.029999, + "volume": 21035900 + }, + { + "date": "2024-07-09", + "open": 190.309998, + "high": 191.360001, + "low": 188.720001, + "close": 188.979996, + "adjClose": 188.979996, + "volume": 15121400 + }, + { + "date": "2024-07-10", + "open": 189.149994, + "high": 191.75, + "low": 189.029999, + "close": 191.179993, + "adjClose": 191.179993, + "volume": 15952500 + }, + { + "date": "2024-07-11", + "open": 189.850006, + "high": 190.860001, + "low": 185.080002, + "close": 185.570007, + "adjClose": 185.570007, + "volume": 25625800 + }, + { + "date": "2024-07-12", + "open": 185.080002, + "high": 187.110001, + "low": 184.490005, + "close": 185.070007, + "adjClose": 185.070007, + "volume": 22898400 + }, + { + "date": "2024-07-15", + "open": 184.919998, + "high": 188.240005, + "low": 184.919998, + "close": 186.529999, + "adjClose": 186.529999, + "volume": 16474000 + }, + { + "date": "2024-07-16", + "open": 187.360001, + "high": 188.679993, + "low": 183.369995, + "close": 183.919998, + "adjClose": 183.919998, + "volume": 18290700 + }, + { + "date": "2024-07-17", + "open": 182.970001, + "high": 183.550003, + "low": 179.899994, + "close": 181.020004, + "adjClose": 181.020004, + "volume": 20734100 + }, + { + "date": "2024-07-18", + "open": 181.929993, + "high": 182.5, + "low": 176.470001, + "close": 177.690002, + "adjClose": 177.690002, + "volume": 25315700 + }, + { + "date": "2024-07-19", + "open": 178.880005, + "high": 180.289993, + "low": 177.130005, + "close": 177.660004, + "adjClose": 177.660004, + "volume": 18881900 + }, + { + "date": "2024-07-22", + "open": 180.589996, + "high": 182.699997, + "low": 180.229996, + "close": 181.669998, + "adjClose": 181.669998, + "volume": 24060800 + } +] diff --git a/docs/data/charts/dataset/META.json b/docs/data/charts/dataset/META.json new file mode 100644 index 0000000000000..31d2aa11dc743 --- /dev/null +++ b/docs/data/charts/dataset/META.json @@ -0,0 +1,2261 @@ +[ + { + "date": "2023-07-24", + "open": 295.779999, + "high": 297.519989, + "low": 288.299988, + "close": 291.609985, + "adjClose": 291.011963, + "volume": 24915700 + }, + { + "date": "2023-07-25", + "open": 295.190002, + "high": 298.299988, + "low": 291.859985, + "close": 294.470001, + "adjClose": 293.866119, + "volume": 19585600 + }, + { + "date": "2023-07-26", + "open": 301.190002, + "high": 301.769989, + "low": 291.899994, + "close": 298.570007, + "adjClose": 297.957733, + "volume": 47256900 + }, + { + "date": "2023-07-27", + "open": 325.119995, + "high": 325.350006, + "low": 309.839996, + "close": 311.709991, + "adjClose": 311.07077, + "volume": 64229200 + }, + { + "date": "2023-07-28", + "open": 316.880005, + "high": 326.200012, + "low": 314.25, + "close": 325.480011, + "adjClose": 324.812531, + "volume": 39220300 + }, + { + "date": "2023-07-31", + "open": 323.690002, + "high": 325.660004, + "low": 317.589996, + "close": 318.600006, + "adjClose": 317.946655, + "volume": 25799600 + }, + { + "date": "2023-08-01", + "open": 317.540009, + "high": 324.140015, + "low": 314.660004, + "close": 322.709991, + "adjClose": 322.048187, + "volume": 22817900 + }, + { + "date": "2023-08-02", + "open": 318.0, + "high": 318.390015, + "low": 310.649994, + "close": 314.309998, + "adjClose": 313.665436, + "volume": 20461100 + }, + { + "date": "2023-08-03", + "open": 309.929993, + "high": 315.950012, + "low": 309.929993, + "close": 313.190002, + "adjClose": 312.54776, + "volume": 15180200 + }, + { + "date": "2023-08-04", + "open": 314.959991, + "high": 318.410004, + "low": 310.200012, + "close": 310.730011, + "adjClose": 310.092804, + "volume": 17600200 + }, + { + "date": "2023-08-07", + "open": 313.230011, + "high": 317.070007, + "low": 310.459991, + "close": 316.559998, + "adjClose": 315.910828, + "volume": 16236500 + }, + { + "date": "2023-08-08", + "open": 314.399994, + "high": 317.890015, + "low": 310.109985, + "close": 312.640015, + "adjClose": 311.998901, + "volume": 15183500 + }, + { + "date": "2023-08-09", + "open": 312.880005, + "high": 313.630005, + "low": 302.850006, + "close": 305.209991, + "adjClose": 304.584076, + "volume": 19955800 + }, + { + "date": "2023-08-10", + "open": 307.940002, + "high": 312.339996, + "low": 303.869995, + "close": 305.73999, + "adjClose": 305.113007, + "volume": 14358900 + }, + { + "date": "2023-08-11", + "open": 302.570007, + "high": 304.720001, + "low": 300.359985, + "close": 301.640015, + "adjClose": 301.021423, + "volume": 13967800 + }, + { + "date": "2023-08-14", + "open": 300.980011, + "high": 306.209991, + "low": 298.25, + "close": 306.190002, + "adjClose": 305.562073, + "volume": 15641900 + }, + { + "date": "2023-08-15", + "open": 306.140015, + "high": 307.230011, + "low": 300.029999, + "close": 301.950012, + "adjClose": 301.330811, + "volume": 11623600 + }, + { + "date": "2023-08-16", + "open": 300.200012, + "high": 301.079987, + "low": 294.279999, + "close": 294.290009, + "adjClose": 293.686493, + "volume": 18547700 + }, + { + "date": "2023-08-17", + "open": 293.049988, + "high": 296.049988, + "low": 284.950012, + "close": 285.089996, + "adjClose": 284.505371, + "volume": 23950100 + }, + { + "date": "2023-08-18", + "open": 279.029999, + "high": 285.690002, + "low": 274.380005, + "close": 283.25, + "adjClose": 282.669128, + "volume": 34061200 + }, + { + "date": "2023-08-21", + "open": 283.450012, + "high": 290.5, + "low": 281.850006, + "close": 289.899994, + "adjClose": 289.305511, + "volume": 20181500 + }, + { + "date": "2023-08-22", + "open": 292.549988, + "high": 292.899994, + "low": 286.75, + "close": 287.600006, + "adjClose": 287.010193, + "volume": 12999900 + }, + { + "date": "2023-08-23", + "open": 288.5, + "high": 297.399994, + "low": 287.670013, + "close": 294.23999, + "adjClose": 293.636597, + "volume": 18287000 + }, + { + "date": "2023-08-24", + "open": 298.5, + "high": 299.459991, + "low": 286.640015, + "close": 286.75, + "adjClose": 286.161957, + "volume": 18360900 + }, + { + "date": "2023-08-25", + "open": 286.130005, + "high": 288.390015, + "low": 276.029999, + "close": 285.5, + "adjClose": 284.91452, + "volume": 23701400 + }, + { + "date": "2023-08-28", + "open": 288.0, + "high": 291.450012, + "low": 285.799988, + "close": 290.26001, + "adjClose": 289.664764, + "volume": 14239300 + }, + { + "date": "2023-08-29", + "open": 288.579987, + "high": 299.149994, + "low": 288.179993, + "close": 297.98999, + "adjClose": 297.378906, + "volume": 20844500 + }, + { + "date": "2023-08-30", + "open": 297.170013, + "high": 298.290009, + "low": 293.429993, + "close": 295.100006, + "adjClose": 294.494843, + "volume": 17717000 + }, + { + "date": "2023-08-31", + "open": 295.799988, + "high": 301.100006, + "low": 295.660004, + "close": 295.890015, + "adjClose": 295.283234, + "volume": 17229900 + }, + { + "date": "2023-09-01", + "open": 299.369995, + "high": 301.73999, + "low": 294.470001, + "close": 296.380005, + "adjClose": 295.772217, + "volume": 12819800 + }, + { + "date": "2023-09-05", + "open": 297.019989, + "high": 301.390015, + "low": 295.51001, + "close": 300.149994, + "adjClose": 299.534485, + "volume": 14956000 + }, + { + "date": "2023-09-06", + "open": 301.709991, + "high": 303.299988, + "low": 295.660004, + "close": 299.170013, + "adjClose": 298.556488, + "volume": 15418100 + }, + { + "date": "2023-09-07", + "open": 298.0, + "high": 307.049988, + "low": 292.220001, + "close": 298.670013, + "adjClose": 298.057526, + "volume": 33748700 + }, + { + "date": "2023-09-08", + "open": 299.220001, + "high": 305.25, + "low": 296.779999, + "close": 297.890015, + "adjClose": 297.279114, + "volume": 17548000 + }, + { + "date": "2023-09-11", + "open": 301.410004, + "high": 309.040009, + "low": 301.279999, + "close": 307.559998, + "adjClose": 306.929291, + "volume": 19489300 + }, + { + "date": "2023-09-12", + "open": 306.329987, + "high": 308.660004, + "low": 300.230011, + "close": 301.660004, + "adjClose": 301.041382, + "volume": 13480400 + }, + { + "date": "2023-09-13", + "open": 302.359985, + "high": 307.179993, + "low": 301.320007, + "close": 305.059998, + "adjClose": 304.434418, + "volume": 13210900 + }, + { + "date": "2023-09-14", + "open": 306.73999, + "high": 312.869995, + "low": 305.029999, + "close": 311.720001, + "adjClose": 311.08075, + "volume": 19343100 + }, + { + "date": "2023-09-15", + "open": 311.609985, + "high": 312.0, + "low": 298.75, + "close": 300.309998, + "adjClose": 299.694122, + "volume": 28106400 + }, + { + "date": "2023-09-18", + "open": 298.190002, + "high": 303.600006, + "low": 297.799988, + "close": 302.549988, + "adjClose": 301.929565, + "volume": 14234200 + }, + { + "date": "2023-09-19", + "open": 302.480011, + "high": 306.170013, + "low": 299.809998, + "close": 305.070007, + "adjClose": 304.444397, + "volume": 15924400 + }, + { + "date": "2023-09-20", + "open": 305.049988, + "high": 308.059998, + "low": 299.429993, + "close": 299.670013, + "adjClose": 299.055481, + "volume": 19379500 + }, + { + "date": "2023-09-21", + "open": 295.700012, + "high": 300.26001, + "low": 293.269989, + "close": 295.730011, + "adjClose": 295.123535, + "volume": 21300500 + }, + { + "date": "2023-09-22", + "open": 299.299988, + "high": 305.380005, + "low": 298.269989, + "close": 299.079987, + "adjClose": 298.466644, + "volume": 25369600 + }, + { + "date": "2023-09-25", + "open": 295.640015, + "high": 300.950012, + "low": 293.700012, + "close": 300.829987, + "adjClose": 300.213043, + "volume": 18987000 + }, + { + "date": "2023-09-26", + "open": 297.660004, + "high": 300.299988, + "low": 296.01001, + "close": 298.959991, + "adjClose": 298.346893, + "volume": 19417200 + }, + { + "date": "2023-09-27", + "open": 300.450012, + "high": 301.299988, + "low": 286.790009, + "close": 297.73999, + "adjClose": 297.129425, + "volume": 36429800 + }, + { + "date": "2023-09-28", + "open": 298.940002, + "high": 306.329987, + "low": 296.700012, + "close": 303.959991, + "adjClose": 303.33667, + "volume": 22167100 + }, + { + "date": "2023-09-29", + "open": 307.380005, + "high": 310.640015, + "low": 299.359985, + "close": 300.209991, + "adjClose": 299.59436, + "volume": 25356600 + }, + { + "date": "2023-10-02", + "open": 302.73999, + "high": 307.179993, + "low": 301.630005, + "close": 306.820007, + "adjClose": 306.190826, + "volume": 16265600 + }, + { + "date": "2023-10-03", + "open": 304.26001, + "high": 306.769989, + "low": 299.640015, + "close": 300.940002, + "adjClose": 300.322876, + "volume": 17362300 + }, + { + "date": "2023-10-04", + "open": 298.730011, + "high": 306.899994, + "low": 298.5, + "close": 305.579987, + "adjClose": 304.953339, + "volume": 16880500 + }, + { + "date": "2023-10-05", + "open": 304.630005, + "high": 306.209991, + "low": 299.5, + "close": 304.790009, + "adjClose": 304.164978, + "volume": 19130000 + }, + { + "date": "2023-10-06", + "open": 301.440002, + "high": 316.309998, + "low": 300.910004, + "close": 315.429993, + "adjClose": 314.783142, + "volume": 21784000 + }, + { + "date": "2023-10-09", + "open": 312.5, + "high": 320.329987, + "low": 311.820007, + "close": 318.359985, + "adjClose": 317.707123, + "volume": 22503700 + }, + { + "date": "2023-10-10", + "open": 319.119995, + "high": 324.660004, + "low": 318.160004, + "close": 321.839996, + "adjClose": 321.179993, + "volume": 19038000 + }, + { + "date": "2023-10-11", + "open": 323.01001, + "high": 328.839996, + "low": 322.950012, + "close": 327.820007, + "adjClose": 327.147736, + "volume": 22036300 + }, + { + "date": "2023-10-12", + "open": 328.0, + "high": 330.540009, + "low": 322.690002, + "close": 324.160004, + "adjClose": 323.495239, + "volume": 20530500 + }, + { + "date": "2023-10-13", + "open": 323.529999, + "high": 325.049988, + "low": 312.369995, + "close": 314.690002, + "adjClose": 314.044678, + "volume": 21341000 + }, + { + "date": "2023-10-16", + "open": 318.640015, + "high": 321.820007, + "low": 315.519989, + "close": 321.149994, + "adjClose": 320.491394, + "volume": 16536100 + }, + { + "date": "2023-10-17", + "open": 318.179993, + "high": 324.399994, + "low": 317.299988, + "close": 324.0, + "adjClose": 323.335541, + "volume": 16387800 + }, + { + "date": "2023-10-18", + "open": 321.390015, + "high": 325.940002, + "low": 315.559998, + "close": 316.970001, + "adjClose": 316.319977, + "volume": 16851000 + }, + { + "date": "2023-10-19", + "open": 319.880005, + "high": 321.890015, + "low": 311.75, + "close": 312.809998, + "adjClose": 312.168518, + "volume": 18709200 + }, + { + "date": "2023-10-20", + "open": 314.140015, + "high": 315.299988, + "low": 306.470001, + "close": 308.649994, + "adjClose": 308.017029, + "volume": 22287400 + }, + { + "date": "2023-10-23", + "open": 309.5, + "high": 317.359985, + "low": 307.26001, + "close": 314.01001, + "adjClose": 313.366058, + "volume": 17796800 + }, + { + "date": "2023-10-24", + "open": 316.779999, + "high": 318.350006, + "low": 310.630005, + "close": 312.549988, + "adjClose": 311.909058, + "volume": 19525500 + }, + { + "date": "2023-10-25", + "open": 310.0, + "high": 310.880005, + "low": 298.839996, + "close": 299.529999, + "adjClose": 298.915771, + "volume": 42192500 + }, + { + "date": "2023-10-26", + "open": 295.0, + "high": 295.0, + "low": 279.399994, + "close": 288.350006, + "adjClose": 287.758667, + "volume": 66684100 + }, + { + "date": "2023-10-27", + "open": 294.480011, + "high": 299.309998, + "low": 292.970001, + "close": 296.730011, + "adjClose": 296.121521, + "volume": 29596300 + }, + { + "date": "2023-10-30", + "open": 299.089996, + "high": 309.399994, + "low": 299.049988, + "close": 302.660004, + "adjClose": 302.039337, + "volume": 28435100 + }, + { + "date": "2023-10-31", + "open": 303.309998, + "high": 303.679993, + "low": 296.859985, + "close": 301.269989, + "adjClose": 300.652161, + "volume": 19434200 + }, + { + "date": "2023-11-01", + "open": 301.850006, + "high": 312.73999, + "low": 301.850006, + "close": 311.850006, + "adjClose": 311.21048, + "volume": 20434600 + }, + { + "date": "2023-11-02", + "open": 317.299988, + "high": 318.820007, + "low": 308.329987, + "close": 310.869995, + "adjClose": 310.232483, + "volume": 21631800 + }, + { + "date": "2023-11-03", + "open": 312.549988, + "high": 315.549988, + "low": 311.019989, + "close": 314.600006, + "adjClose": 313.954865, + "volume": 16754100 + }, + { + "date": "2023-11-06", + "open": 315.980011, + "high": 318.329987, + "low": 314.450012, + "close": 315.799988, + "adjClose": 315.152374, + "volume": 12887700 + }, + { + "date": "2023-11-07", + "open": 317.059998, + "high": 321.0, + "low": 315.119995, + "close": 318.820007, + "adjClose": 318.166199, + "volume": 14055600 + }, + { + "date": "2023-11-08", + "open": 318.140015, + "high": 321.329987, + "low": 314.880005, + "close": 319.779999, + "adjClose": 319.124237, + "volume": 13609700 + }, + { + "date": "2023-11-09", + "open": 319.420013, + "high": 324.179993, + "low": 318.799988, + "close": 320.549988, + "adjClose": 319.892609, + "volume": 16103100 + }, + { + "date": "2023-11-10", + "open": 319.940002, + "high": 329.100006, + "low": 319.459991, + "close": 328.769989, + "adjClose": 328.095764, + "volume": 19096200 + }, + { + "date": "2023-11-13", + "open": 326.200012, + "high": 332.329987, + "low": 325.700012, + "close": 329.190002, + "adjClose": 328.514923, + "volume": 16908900 + }, + { + "date": "2023-11-14", + "open": 334.540009, + "high": 338.100006, + "low": 333.329987, + "close": 336.309998, + "adjClose": 335.6203, + "volume": 17179400 + }, + { + "date": "2023-11-15", + "open": 337.929993, + "high": 338.399994, + "low": 330.019989, + "close": 332.709991, + "adjClose": 332.027679, + "volume": 14531200 + }, + { + "date": "2023-11-16", + "open": 329.369995, + "high": 334.579987, + "low": 326.380005, + "close": 334.190002, + "adjClose": 333.504669, + "volume": 18932600 + }, + { + "date": "2023-11-17", + "open": 330.26001, + "high": 335.5, + "low": 329.350006, + "close": 335.040009, + "adjClose": 334.352936, + "volume": 14494400 + }, + { + "date": "2023-11-20", + "open": 334.890015, + "high": 341.869995, + "low": 334.190002, + "close": 339.970001, + "adjClose": 339.272797, + "volume": 16960500 + }, + { + "date": "2023-11-21", + "open": 338.329987, + "high": 339.899994, + "low": 335.899994, + "close": 336.980011, + "adjClose": 336.28894, + "volume": 12027900 + }, + { + "date": "2023-11-22", + "open": 339.209991, + "high": 342.920013, + "low": 338.579987, + "close": 341.48999, + "adjClose": 340.789703, + "volume": 10702700 + }, + { + "date": "2023-11-24", + "open": 340.130005, + "high": 341.859985, + "low": 336.769989, + "close": 338.230011, + "adjClose": 337.536407, + "volume": 5467500 + }, + { + "date": "2023-11-27", + "open": 336.179993, + "high": 339.899994, + "low": 334.200012, + "close": 334.700012, + "adjClose": 334.013641, + "volume": 15684500 + }, + { + "date": "2023-11-28", + "open": 333.399994, + "high": 339.380005, + "low": 333.399994, + "close": 338.98999, + "adjClose": 338.2948, + "volume": 12637200 + }, + { + "date": "2023-11-29", + "open": 339.690002, + "high": 339.899994, + "low": 330.779999, + "close": 332.200012, + "adjClose": 331.518768, + "volume": 16024500 + }, + { + "date": "2023-11-30", + "open": 331.890015, + "high": 333.5, + "low": 322.399994, + "close": 327.149994, + "adjClose": 326.479095, + "volume": 23146400 + }, + { + "date": "2023-12-01", + "open": 325.480011, + "high": 326.859985, + "low": 320.76001, + "close": 324.820007, + "adjClose": 324.1539, + "volume": 15264700 + }, + { + "date": "2023-12-04", + "open": 317.290009, + "high": 320.859985, + "low": 313.660004, + "close": 320.019989, + "adjClose": 319.363739, + "volume": 19037100 + }, + { + "date": "2023-12-05", + "open": 318.980011, + "high": 321.880005, + "low": 315.390015, + "close": 318.290009, + "adjClose": 317.637299, + "volume": 16952100 + }, + { + "date": "2023-12-06", + "open": 321.929993, + "high": 322.25, + "low": 317.040009, + "close": 317.450012, + "adjClose": 316.799011, + "volume": 11294300 + }, + { + "date": "2023-12-07", + "open": 317.769989, + "high": 328.23999, + "low": 317.769989, + "close": 326.589996, + "adjClose": 325.920258, + "volume": 15905100 + }, + { + "date": "2023-12-08", + "open": 323.089996, + "high": 333.170013, + "low": 323.0, + "close": 332.75, + "adjClose": 332.067627, + "volume": 14077500 + }, + { + "date": "2023-12-11", + "open": 329.399994, + "high": 329.890015, + "low": 320.0, + "close": 325.279999, + "adjClose": 324.612946, + "volume": 25802500 + }, + { + "date": "2023-12-12", + "open": 324.600006, + "high": 334.470001, + "low": 324.559998, + "close": 334.220001, + "adjClose": 333.534607, + "volume": 18485500 + }, + { + "date": "2023-12-13", + "open": 333.929993, + "high": 338.369995, + "low": 332.640015, + "close": 334.73999, + "adjClose": 334.053528, + "volume": 16353300 + }, + { + "date": "2023-12-14", + "open": 333.850006, + "high": 334.700012, + "low": 328.640015, + "close": 333.170013, + "adjClose": 332.486755, + "volume": 19607300 + }, + { + "date": "2023-12-15", + "open": 331.98999, + "high": 338.660004, + "low": 331.220001, + "close": 334.920013, + "adjClose": 334.233185, + "volume": 30001600 + }, + { + "date": "2023-12-18", + "open": 337.480011, + "high": 347.559998, + "low": 337.019989, + "close": 344.619995, + "adjClose": 343.913269, + "volume": 18993900 + }, + { + "date": "2023-12-19", + "open": 345.579987, + "high": 353.600006, + "low": 345.119995, + "close": 350.359985, + "adjClose": 349.64151, + "volume": 17729400 + }, + { + "date": "2023-12-20", + "open": 348.649994, + "high": 354.959991, + "low": 347.790009, + "close": 349.279999, + "adjClose": 348.563721, + "volume": 16369900 + }, + { + "date": "2023-12-21", + "open": 352.980011, + "high": 356.410004, + "low": 349.209991, + "close": 354.089996, + "adjClose": 353.363831, + "volume": 15289600 + }, + { + "date": "2023-12-22", + "open": 355.579987, + "high": 357.200012, + "low": 351.220001, + "close": 353.390015, + "adjClose": 352.665314, + "volume": 11764200 + }, + { + "date": "2023-12-26", + "open": 354.98999, + "high": 356.980011, + "low": 353.450012, + "close": 354.829987, + "adjClose": 354.102325, + "volume": 9898600 + }, + { + "date": "2023-12-27", + "open": 356.070007, + "high": 359.0, + "low": 355.309998, + "close": 357.829987, + "adjClose": 357.096191, + "volume": 13207900 + }, + { + "date": "2023-12-28", + "open": 359.700012, + "high": 361.899994, + "low": 357.809998, + "close": 358.320007, + "adjClose": 357.585205, + "volume": 11798800 + }, + { + "date": "2023-12-29", + "open": 358.98999, + "high": 360.0, + "low": 351.820007, + "close": 353.959991, + "adjClose": 353.234131, + "volume": 14980500 + }, + { + "date": "2024-01-02", + "open": 351.320007, + "high": 353.160004, + "low": 340.01001, + "close": 346.290009, + "adjClose": 345.579865, + "volume": 19042200 + }, + { + "date": "2024-01-03", + "open": 344.980011, + "high": 347.950012, + "low": 343.179993, + "close": 344.470001, + "adjClose": 343.76358, + "volume": 15451100 + }, + { + "date": "2024-01-04", + "open": 344.5, + "high": 348.149994, + "low": 343.399994, + "close": 347.119995, + "adjClose": 346.408142, + "volume": 12099900 + }, + { + "date": "2024-01-05", + "open": 346.98999, + "high": 353.5, + "low": 346.26001, + "close": 351.950012, + "adjClose": 351.228271, + "volume": 13920700 + }, + { + "date": "2024-01-08", + "open": 354.700012, + "high": 358.980011, + "low": 352.049988, + "close": 358.660004, + "adjClose": 357.9245, + "volume": 13890200 + }, + { + "date": "2024-01-09", + "open": 356.399994, + "high": 360.640015, + "low": 355.359985, + "close": 357.429993, + "adjClose": 356.697021, + "volume": 13463900 + }, + { + "date": "2024-01-10", + "open": 360.170013, + "high": 372.940002, + "low": 359.079987, + "close": 370.470001, + "adjClose": 369.710266, + "volume": 22117200 + }, + { + "date": "2024-01-11", + "open": 372.130005, + "high": 372.779999, + "low": 362.929993, + "close": 369.670013, + "adjClose": 368.911926, + "volume": 17205400 + }, + { + "date": "2024-01-12", + "open": 370.160004, + "high": 377.059998, + "low": 369.540009, + "close": 374.48999, + "adjClose": 373.722015, + "volume": 19295700 + }, + { + "date": "2024-01-16", + "open": 373.649994, + "high": 375.609985, + "low": 367.230011, + "close": 367.459991, + "adjClose": 366.706451, + "volume": 15306900 + }, + { + "date": "2024-01-17", + "open": 366.299988, + "high": 368.540009, + "low": 358.609985, + "close": 368.369995, + "adjClose": 367.614563, + "volume": 12724800 + }, + { + "date": "2024-01-18", + "open": 371.48999, + "high": 376.850006, + "low": 370.950012, + "close": 376.130005, + "adjClose": 375.358673, + "volume": 16354300 + }, + { + "date": "2024-01-19", + "open": 379.0, + "high": 384.359985, + "low": 377.970001, + "close": 383.450012, + "adjClose": 382.663666, + "volume": 21470100 + }, + { + "date": "2024-01-22", + "open": 387.950012, + "high": 390.350006, + "low": 381.160004, + "close": 381.779999, + "adjClose": 380.99707, + "volume": 17680500 + }, + { + "date": "2024-01-23", + "open": 384.619995, + "high": 388.380005, + "low": 382.079987, + "close": 385.200012, + "adjClose": 384.410065, + "volume": 15506100 + }, + { + "date": "2024-01-24", + "open": 390.0, + "high": 396.149994, + "low": 387.809998, + "close": 390.700012, + "adjClose": 389.898773, + "volume": 15698500 + }, + { + "date": "2024-01-25", + "open": 390.170013, + "high": 395.48999, + "low": 385.660004, + "close": 393.179993, + "adjClose": 392.373688, + "volume": 15091100 + }, + { + "date": "2024-01-26", + "open": 394.350006, + "high": 396.790009, + "low": 391.589996, + "close": 394.140015, + "adjClose": 393.331757, + "volume": 13163700 + }, + { + "date": "2024-01-29", + "open": 394.98999, + "high": 402.929993, + "low": 393.100006, + "close": 401.019989, + "adjClose": 400.197601, + "volume": 18742400 + }, + { + "date": "2024-01-30", + "open": 403.589996, + "high": 406.359985, + "low": 399.570007, + "close": 400.059998, + "adjClose": 399.239594, + "volume": 18614700 + }, + { + "date": "2024-01-31", + "open": 389.0, + "high": 398.0, + "low": 387.100006, + "close": 390.140015, + "adjClose": 389.339966, + "volume": 20180800 + }, + { + "date": "2024-02-01", + "open": 393.940002, + "high": 400.5, + "low": 393.049988, + "close": 394.779999, + "adjClose": 393.970428, + "volume": 29727100 + }, + { + "date": "2024-02-02", + "open": 459.600006, + "high": 485.959991, + "low": 453.01001, + "close": 474.98999, + "adjClose": 474.01593, + "volume": 84615500 + }, + { + "date": "2024-02-05", + "open": 469.880005, + "high": 471.899994, + "low": 459.220001, + "close": 459.410004, + "adjClose": 458.467865, + "volume": 40832400 + }, + { + "date": "2024-02-06", + "open": 464.0, + "high": 467.119995, + "low": 453.0, + "close": 454.720001, + "adjClose": 453.787506, + "volume": 21655200 + }, + { + "date": "2024-02-07", + "open": 458.0, + "high": 471.519989, + "low": 456.179993, + "close": 469.589996, + "adjClose": 468.626984, + "volume": 23066000 + }, + { + "date": "2024-02-08", + "open": 468.320007, + "high": 470.589996, + "low": 465.029999, + "close": 470.0, + "adjClose": 469.036163, + "volume": 18815100 + }, + { + "date": "2024-02-09", + "open": 472.950012, + "high": 473.589996, + "low": 467.470001, + "close": 468.109985, + "adjClose": 467.150024, + "volume": 18413100 + }, + { + "date": "2024-02-12", + "open": 468.190002, + "high": 479.149994, + "low": 466.579987, + "close": 468.899994, + "adjClose": 467.938416, + "volume": 19382000 + }, + { + "date": "2024-02-13", + "open": 456.869995, + "high": 467.890015, + "low": 455.089996, + "close": 460.119995, + "adjClose": 459.176422, + "volume": 20916600 + }, + { + "date": "2024-02-14", + "open": 467.929993, + "high": 474.109985, + "low": 466.089996, + "close": 473.279999, + "adjClose": 472.309418, + "volume": 16858400 + }, + { + "date": "2024-02-15", + "open": 475.279999, + "high": 488.619995, + "low": 472.220001, + "close": 484.029999, + "adjClose": 483.037384, + "volume": 24212300 + }, + { + "date": "2024-02-16", + "open": 478.109985, + "high": 478.959991, + "low": 469.209991, + "close": 473.320007, + "adjClose": 472.349365, + "volume": 23306500 + }, + { + "date": "2024-02-20", + "open": 469.720001, + "high": 476.179993, + "low": 466.559998, + "close": 471.75, + "adjClose": 470.782562, + "volume": 18015500 + }, + { + "date": "2024-02-21", + "open": 466.5, + "high": 469.0, + "low": 461.790009, + "close": 468.029999, + "adjClose": 467.565765, + "volume": 12977100 + }, + { + "date": "2024-02-22", + "open": 480.23999, + "high": 489.98999, + "low": 476.059998, + "close": 486.130005, + "adjClose": 485.647827, + "volume": 21625800 + }, + { + "date": "2024-02-23", + "open": 488.049988, + "high": 494.359985, + "low": 482.350006, + "close": 484.029999, + "adjClose": 483.549896, + "volume": 18374300 + }, + { + "date": "2024-02-26", + "open": 483.470001, + "high": 486.140015, + "low": 480.600006, + "close": 481.73999, + "adjClose": 481.262177, + "volume": 12101400 + }, + { + "date": "2024-02-27", + "open": 479.980011, + "high": 487.269989, + "low": 479.920013, + "close": 487.049988, + "adjClose": 486.566895, + "volume": 10809600 + }, + { + "date": "2024-02-28", + "open": 485.0, + "high": 491.049988, + "low": 482.75, + "close": 484.019989, + "adjClose": 483.539886, + "volume": 12715500 + }, + { + "date": "2024-02-29", + "open": 488.440002, + "high": 491.700012, + "low": 482.609985, + "close": 490.130005, + "adjClose": 489.64386, + "volume": 17732000 + }, + { + "date": "2024-03-01", + "open": 492.109985, + "high": 504.25, + "low": 491.850006, + "close": 502.299988, + "adjClose": 501.801758, + "volume": 16273600 + }, + { + "date": "2024-03-04", + "open": 503.0, + "high": 504.420013, + "low": 496.420013, + "close": 498.190002, + "adjClose": 497.695862, + "volume": 12324100 + }, + { + "date": "2024-03-05", + "open": 495.0, + "high": 495.579987, + "low": 487.890015, + "close": 490.220001, + "adjClose": 489.733765, + "volume": 15325300 + }, + { + "date": "2024-03-06", + "open": 497.630005, + "high": 502.970001, + "low": 494.290009, + "close": 496.089996, + "adjClose": 495.597931, + "volume": 11757900 + }, + { + "date": "2024-03-07", + "open": 503.279999, + "high": 519.849976, + "low": 501.380005, + "close": 512.190002, + "adjClose": 511.681976, + "volume": 18586400 + }, + { + "date": "2024-03-08", + "open": 514.190002, + "high": 523.570007, + "low": 499.350006, + "close": 505.950012, + "adjClose": 505.448181, + "volume": 18575200 + }, + { + "date": "2024-03-11", + "open": 497.01001, + "high": 497.320007, + "low": 476.0, + "close": 483.589996, + "adjClose": 483.110321, + "volume": 20428300 + }, + { + "date": "2024-03-12", + "open": 493.26001, + "high": 502.309998, + "low": 484.730011, + "close": 499.75, + "adjClose": 499.254303, + "volume": 15448200 + }, + { + "date": "2024-03-13", + "open": 495.390015, + "high": 500.980011, + "low": 491.029999, + "close": 495.570007, + "adjClose": 495.078461, + "volume": 12090700 + }, + { + "date": "2024-03-14", + "open": 500.26001, + "high": 501.350006, + "low": 488.160004, + "close": 491.829987, + "adjClose": 491.342163, + "volume": 12620000 + }, + { + "date": "2024-03-15", + "open": 489.01001, + "high": 491.829987, + "low": 481.299988, + "close": 484.100006, + "adjClose": 483.619843, + "volume": 29141700 + }, + { + "date": "2024-03-18", + "open": 491.910004, + "high": 497.420013, + "low": 486.809998, + "close": 496.980011, + "adjClose": 496.487061, + "volume": 11755300 + }, + { + "date": "2024-03-19", + "open": 488.170013, + "high": 496.630005, + "low": 481.279999, + "close": 496.23999, + "adjClose": 495.747772, + "volume": 10903100 + }, + { + "date": "2024-03-20", + "open": 499.5, + "high": 508.200012, + "low": 495.170013, + "close": 505.519989, + "adjClose": 505.018585, + "volume": 11711100 + }, + { + "date": "2024-03-21", + "open": 514.710022, + "high": 515.039978, + "low": 506.01001, + "close": 507.76001, + "adjClose": 507.256378, + "volume": 9712500 + }, + { + "date": "2024-03-22", + "open": 507.0, + "high": 509.970001, + "low": 504.339996, + "close": 509.579987, + "adjClose": 509.074554, + "volume": 8117000 + }, + { + "date": "2024-03-25", + "open": 505.790009, + "high": 507.220001, + "low": 500.23999, + "close": 503.019989, + "adjClose": 502.521057, + "volume": 8380600 + }, + { + "date": "2024-03-26", + "open": 505.130005, + "high": 510.0, + "low": 495.209991, + "close": 495.890015, + "adjClose": 495.398163, + "volume": 11205400 + }, + { + "date": "2024-03-27", + "open": 499.299988, + "high": 499.890015, + "low": 488.070007, + "close": 493.859985, + "adjClose": 493.370148, + "volume": 9989700 + }, + { + "date": "2024-03-28", + "open": 492.839996, + "high": 492.890015, + "low": 485.149994, + "close": 485.579987, + "adjClose": 485.098358, + "volume": 15212800 + }, + { + "date": "2024-04-01", + "open": 487.200012, + "high": 497.429993, + "low": 481.779999, + "close": 491.350006, + "adjClose": 490.86264, + "volume": 9247000 + }, + { + "date": "2024-04-02", + "open": 485.100006, + "high": 497.529999, + "low": 484.649994, + "close": 497.369995, + "adjClose": 496.876678, + "volume": 11081000 + }, + { + "date": "2024-04-03", + "open": 498.929993, + "high": 507.23999, + "low": 498.75, + "close": 506.73999, + "adjClose": 506.237366, + "volume": 12099200 + }, + { + "date": "2024-04-04", + "open": 516.419983, + "high": 530.0, + "low": 510.579987, + "close": 510.920013, + "adjClose": 510.413239, + "volume": 26476300 + }, + { + "date": "2024-04-05", + "open": 516.859985, + "high": 530.700012, + "low": 514.409973, + "close": 527.340027, + "adjClose": 526.816956, + "volume": 19242000 + }, + { + "date": "2024-04-08", + "open": 529.280029, + "high": 531.48999, + "low": 518.890015, + "close": 519.25, + "adjClose": 518.734985, + "volume": 13260600 + }, + { + "date": "2024-04-09", + "open": 522.22998, + "high": 525.869995, + "low": 506.73999, + "close": 516.900024, + "adjClose": 516.387329, + "volume": 10881400 + }, + { + "date": "2024-04-10", + "open": 509.290009, + "high": 522.559998, + "low": 505.799988, + "close": 519.830017, + "adjClose": 519.314392, + "volume": 11418500 + }, + { + "date": "2024-04-11", + "open": 521.109985, + "high": 523.859985, + "low": 517.289978, + "close": 523.159973, + "adjClose": 522.641052, + "volume": 10369500 + }, + { + "date": "2024-04-12", + "open": 517.75, + "high": 520.190002, + "low": 509.329987, + "close": 511.899994, + "adjClose": 511.392242, + "volume": 11944900 + }, + { + "date": "2024-04-15", + "open": 516.719971, + "high": 518.530029, + "low": 497.279999, + "close": 500.230011, + "adjClose": 499.733856, + "volume": 13512900 + }, + { + "date": "2024-04-16", + "open": 498.109985, + "high": 504.769989, + "low": 497.109985, + "close": 499.76001, + "adjClose": 499.264313, + "volume": 9847900 + }, + { + "date": "2024-04-17", + "open": 503.100006, + "high": 503.160004, + "low": 487.140015, + "close": 494.170013, + "adjClose": 493.679871, + "volume": 12193700 + }, + { + "date": "2024-04-18", + "open": 499.820007, + "high": 512.210022, + "low": 499.040009, + "close": 501.799988, + "adjClose": 501.302277, + "volume": 14808700 + }, + { + "date": "2024-04-19", + "open": 502.799988, + "high": 502.799988, + "low": 475.730011, + "close": 481.070007, + "adjClose": 480.592834, + "volume": 25111000 + }, + { + "date": "2024-04-22", + "open": 489.720001, + "high": 492.01001, + "low": 473.399994, + "close": 481.730011, + "adjClose": 481.252197, + "volume": 17271100 + }, + { + "date": "2024-04-23", + "open": 491.25, + "high": 498.76001, + "low": 488.970001, + "close": 496.100006, + "adjClose": 495.607941, + "volume": 15079200 + }, + { + "date": "2024-04-24", + "open": 508.059998, + "high": 510.0, + "low": 484.579987, + "close": 493.5, + "adjClose": 493.010498, + "volume": 37772700 + }, + { + "date": "2024-04-25", + "open": 421.399994, + "high": 445.769989, + "low": 414.5, + "close": 441.380005, + "adjClose": 440.9422, + "volume": 82890700 + }, + { + "date": "2024-04-26", + "open": 441.459991, + "high": 446.440002, + "low": 431.959991, + "close": 443.290009, + "adjClose": 442.850311, + "volume": 32691400 + }, + { + "date": "2024-04-29", + "open": 439.559998, + "high": 439.76001, + "low": 428.559998, + "close": 432.619995, + "adjClose": 432.190887, + "volume": 21502600 + }, + { + "date": "2024-04-30", + "open": 431.049988, + "high": 439.619995, + "low": 429.720001, + "close": 430.170013, + "adjClose": 429.743347, + "volume": 18429500 + }, + { + "date": "2024-05-01", + "open": 428.600006, + "high": 449.959991, + "low": 427.109985, + "close": 439.190002, + "adjClose": 438.754364, + "volume": 20344900 + }, + { + "date": "2024-05-02", + "open": 438.839996, + "high": 443.959991, + "low": 432.279999, + "close": 441.679993, + "adjClose": 441.241913, + "volume": 15221300 + }, + { + "date": "2024-05-03", + "open": 445.929993, + "high": 454.170013, + "low": 443.850006, + "close": 451.959991, + "adjClose": 451.511688, + "volume": 16489100 + }, + { + "date": "2024-05-06", + "open": 455.579987, + "high": 466.160004, + "low": 453.339996, + "close": 465.679993, + "adjClose": 465.218079, + "volume": 15094600 + }, + { + "date": "2024-05-07", + "open": 466.290009, + "high": 471.529999, + "low": 461.309998, + "close": 468.23999, + "adjClose": 467.775543, + "volume": 13406800 + }, + { + "date": "2024-05-08", + "open": 463.5, + "high": 475.579987, + "low": 463.0, + "close": 472.600006, + "adjClose": 472.131256, + "volume": 11683900 + }, + { + "date": "2024-05-09", + "open": 470.0, + "high": 476.079987, + "low": 467.630005, + "close": 475.420013, + "adjClose": 474.948456, + "volume": 9437700 + }, + { + "date": "2024-05-10", + "open": 477.089996, + "high": 477.5, + "low": 469.600006, + "close": 476.200012, + "adjClose": 475.727692, + "volume": 10750000 + }, + { + "date": "2024-05-13", + "open": 472.75, + "high": 473.350006, + "low": 462.850006, + "close": 468.01001, + "adjClose": 467.545807, + "volume": 14668800 + }, + { + "date": "2024-05-14", + "open": 463.369995, + "high": 472.540009, + "low": 460.079987, + "close": 471.850006, + "adjClose": 471.381989, + "volume": 10478600 + }, + { + "date": "2024-05-15", + "open": 474.980011, + "high": 482.5, + "low": 471.200012, + "close": 481.540009, + "adjClose": 481.062378, + "volume": 13100500 + }, + { + "date": "2024-05-16", + "open": 475.0, + "high": 477.690002, + "low": 472.75, + "close": 473.230011, + "adjClose": 472.76062, + "volume": 16608200 + }, + { + "date": "2024-05-17", + "open": 470.829987, + "high": 472.799988, + "low": 468.420013, + "close": 471.910004, + "adjClose": 471.441925, + "volume": 10807300 + }, + { + "date": "2024-05-20", + "open": 469.950012, + "high": 473.200012, + "low": 467.040009, + "close": 468.839996, + "adjClose": 468.374969, + "volume": 11745100 + }, + { + "date": "2024-05-21", + "open": 467.119995, + "high": 470.700012, + "low": 462.269989, + "close": 464.630005, + "adjClose": 464.169159, + "volume": 11742200 + }, + { + "date": "2024-05-22", + "open": 467.869995, + "high": 473.720001, + "low": 465.649994, + "close": 467.779999, + "adjClose": 467.31601, + "volume": 10078600 + }, + { + "date": "2024-05-23", + "open": 472.880005, + "high": 474.359985, + "low": 461.540009, + "close": 465.779999, + "adjClose": 465.317993, + "volume": 11747900 + }, + { + "date": "2024-05-24", + "open": 467.619995, + "high": 479.850006, + "low": 466.299988, + "close": 478.220001, + "adjClose": 477.745667, + "volume": 12012300 + }, + { + "date": "2024-05-28", + "open": 476.579987, + "high": 480.859985, + "low": 474.839996, + "close": 479.920013, + "adjClose": 479.444, + "volume": 10175800 + }, + { + "date": "2024-05-29", + "open": 474.660004, + "high": 479.850006, + "low": 473.700012, + "close": 474.359985, + "adjClose": 473.889465, + "volume": 9226200 + }, + { + "date": "2024-05-30", + "open": 471.670013, + "high": 471.730011, + "low": 464.709991, + "close": 467.049988, + "adjClose": 466.586731, + "volume": 10735200 + }, + { + "date": "2024-05-31", + "open": 465.799988, + "high": 469.119995, + "low": 454.459991, + "close": 466.829987, + "adjClose": 466.366943, + "volume": 16919800 + }, + { + "date": "2024-06-03", + "open": 470.859985, + "high": 479.600006, + "low": 468.23999, + "close": 477.48999, + "adjClose": 477.016388, + "volume": 11279400 + }, + { + "date": "2024-06-04", + "open": 477.0, + "high": 478.890015, + "low": 473.230011, + "close": 476.98999, + "adjClose": 476.516876, + "volume": 7088700 + }, + { + "date": "2024-06-05", + "open": 484.450012, + "high": 496.649994, + "low": 483.910004, + "close": 495.059998, + "adjClose": 494.56897, + "volume": 15690500 + }, + { + "date": "2024-06-06", + "open": 492.980011, + "high": 502.820007, + "low": 490.890015, + "close": 493.76001, + "adjClose": 493.270264, + "volume": 10667300 + }, + { + "date": "2024-06-07", + "open": 495.910004, + "high": 498.910004, + "low": 490.170013, + "close": 492.959991, + "adjClose": 492.471039, + "volume": 9380700 + }, + { + "date": "2024-06-10", + "open": 493.859985, + "high": 502.660004, + "low": 493.410004, + "close": 502.600006, + "adjClose": 502.101501, + "volume": 11236900 + }, + { + "date": "2024-06-11", + "open": 500.160004, + "high": 507.600006, + "low": 498.269989, + "close": 507.470001, + "adjClose": 506.966644, + "volume": 9673700 + }, + { + "date": "2024-06-12", + "open": 513.98999, + "high": 514.01001, + "low": 504.470001, + "close": 508.839996, + "adjClose": 508.335297, + "volume": 11983200 + }, + { + "date": "2024-06-13", + "open": 505.709991, + "high": 509.359985, + "low": 501.359985, + "close": 504.100006, + "adjClose": 503.600006, + "volume": 9954600 + }, + { + "date": "2024-06-14", + "open": 502.649994, + "high": 507.149994, + "low": 500.75, + "close": 504.160004, + "adjClose": 504.160004, + "volume": 10243300 + }, + { + "date": "2024-06-17", + "open": 501.670013, + "high": 510.75, + "low": 496.01001, + "close": 506.630005, + "adjClose": 506.630005, + "volume": 11266600 + }, + { + "date": "2024-06-18", + "open": 504.559998, + "high": 506.0, + "low": 495.019989, + "close": 499.48999, + "adjClose": 499.48999, + "volume": 13060400 + }, + { + "date": "2024-06-20", + "open": 502.0, + "high": 503.670013, + "low": 496.769989, + "close": 501.700012, + "adjClose": 501.700012, + "volume": 11801200 + }, + { + "date": "2024-06-21", + "open": 503.450012, + "high": 503.450012, + "low": 492.390015, + "close": 494.779999, + "adjClose": 494.779999, + "volume": 23130700 + }, + { + "date": "2024-06-24", + "open": 499.200012, + "high": 507.799988, + "low": 494.290009, + "close": 498.910004, + "adjClose": 498.910004, + "volume": 13525300 + }, + { + "date": "2024-06-25", + "open": 497.049988, + "high": 510.709991, + "low": 495.5, + "close": 510.600006, + "adjClose": 510.600006, + "volume": 12109800 + }, + { + "date": "2024-06-26", + "open": 506.649994, + "high": 513.809998, + "low": 504.679993, + "close": 513.119995, + "adjClose": 513.119995, + "volume": 8882300 + }, + { + "date": "2024-06-27", + "open": 514.25, + "high": 522.880005, + "low": 513.900024, + "close": 519.559998, + "adjClose": 519.559998, + "volume": 10121200 + }, + { + "date": "2024-06-28", + "open": 517.150024, + "high": 521.880005, + "low": 503.839996, + "close": 504.220001, + "adjClose": 504.220001, + "volume": 15855100 + }, + { + "date": "2024-07-01", + "open": 504.950012, + "high": 506.579987, + "low": 493.170013, + "close": 504.679993, + "adjClose": 504.679993, + "volume": 10328200 + }, + { + "date": "2024-07-02", + "open": 500.76001, + "high": 510.5, + "low": 499.450012, + "close": 509.5, + "adjClose": 509.5, + "volume": 7739500 + }, + { + "date": "2024-07-03", + "open": 506.369995, + "high": 511.279999, + "low": 506.019989, + "close": 509.959991, + "adjClose": 509.959991, + "volume": 6005600 + }, + { + "date": "2024-07-05", + "open": 511.600006, + "high": 540.869995, + "low": 511.600006, + "close": 539.909973, + "adjClose": 539.909973, + "volume": 21354100 + }, + { + "date": "2024-07-08", + "open": 542.349976, + "high": 542.809998, + "low": 526.650024, + "close": 529.320007, + "adjClose": 529.320007, + "volume": 14917500 + }, + { + "date": "2024-07-09", + "open": 533.75, + "high": 537.47998, + "low": 528.190002, + "close": 530.0, + "adjClose": 530.0, + "volume": 8753200 + }, + { + "date": "2024-07-10", + "open": 530.789978, + "high": 538.880005, + "low": 528.359985, + "close": 534.690002, + "adjClose": 534.690002, + "volume": 10983300 + }, + { + "date": "2024-07-11", + "open": 530.890015, + "high": 535.460022, + "low": 508.369995, + "close": 512.700012, + "adjClose": 512.700012, + "volume": 16458300 + }, + { + "date": "2024-07-12", + "open": 497.76001, + "high": 508.089996, + "low": 494.230011, + "close": 498.869995, + "adjClose": 498.869995, + "volume": 19750500 + }, + { + "date": "2024-07-15", + "open": 498.630005, + "high": 506.679993, + "low": 493.369995, + "close": 496.160004, + "adjClose": 496.160004, + "volume": 12539200 + }, + { + "date": "2024-07-16", + "open": 501.5, + "high": 503.950012, + "low": 485.790009, + "close": 489.790009, + "adjClose": 489.790009, + "volume": 14075800 + }, + { + "date": "2024-07-17", + "open": 479.170013, + "high": 479.170013, + "low": 459.119995, + "close": 461.98999, + "adjClose": 461.98999, + "volume": 28076600 + }, + { + "date": "2024-07-18", + "open": 475.0, + "high": 479.23999, + "low": 464.540009, + "close": 475.850006, + "adjClose": 475.850006, + "volume": 19267200 + }, + { + "date": "2024-07-19", + "open": 476.059998, + "high": 486.709991, + "low": 475.709991, + "close": 476.790009, + "adjClose": 476.790009, + "volume": 15149400 + }, + { + "date": "2024-07-22", + "open": 486.579987, + "high": 492.059998, + "low": 483.899994, + "close": 487.399994, + "adjClose": 487.399994, + "volume": 12008500 + } +] diff --git a/docs/data/charts/dataset/MSFT.json b/docs/data/charts/dataset/MSFT.json new file mode 100644 index 0000000000000..84417f2f19cdd --- /dev/null +++ b/docs/data/charts/dataset/MSFT.json @@ -0,0 +1,2261 @@ +[ + { + "date": "2023-07-24", + "open": 345.850006, + "high": 346.920013, + "low": 342.309998, + "close": 345.109985, + "adjClose": 342.431244, + "volume": 26678100 + }, + { + "date": "2023-07-25", + "open": 347.109985, + "high": 351.890015, + "low": 345.070007, + "close": 350.980011, + "adjClose": 348.255707, + "volume": 41637700 + }, + { + "date": "2023-07-26", + "open": 341.440002, + "high": 344.670013, + "low": 333.109985, + "close": 337.769989, + "adjClose": 335.148224, + "volume": 58383700 + }, + { + "date": "2023-07-27", + "open": 340.480011, + "high": 341.329987, + "low": 329.049988, + "close": 330.720001, + "adjClose": 328.152954, + "volume": 39635300 + }, + { + "date": "2023-07-28", + "open": 333.670013, + "high": 340.01001, + "low": 333.170013, + "close": 338.369995, + "adjClose": 335.743561, + "volume": 28484900 + }, + { + "date": "2023-07-31", + "open": 336.920013, + "high": 337.700012, + "low": 333.359985, + "close": 335.920013, + "adjClose": 333.312622, + "volume": 25446000 + }, + { + "date": "2023-08-01", + "open": 335.190002, + "high": 338.540009, + "low": 333.700012, + "close": 336.339996, + "adjClose": 333.72934, + "volume": 18311900 + }, + { + "date": "2023-08-02", + "open": 333.630005, + "high": 333.630005, + "low": 326.359985, + "close": 327.5, + "adjClose": 324.957977, + "volume": 27761300 + }, + { + "date": "2023-08-03", + "open": 326.0, + "high": 329.880005, + "low": 325.950012, + "close": 326.660004, + "adjClose": 324.124481, + "volume": 18253700 + }, + { + "date": "2023-08-04", + "open": 331.880005, + "high": 335.140015, + "low": 327.23999, + "close": 327.779999, + "adjClose": 325.235809, + "volume": 23727700 + }, + { + "date": "2023-08-07", + "open": 328.369995, + "high": 331.109985, + "low": 327.519989, + "close": 330.109985, + "adjClose": 327.547729, + "volume": 17741500 + }, + { + "date": "2023-08-08", + "open": 326.959991, + "high": 328.75, + "low": 323.0, + "close": 326.049988, + "adjClose": 323.519196, + "volume": 22327600 + }, + { + "date": "2023-08-09", + "open": 326.470001, + "high": 327.109985, + "low": 321.049988, + "close": 322.230011, + "adjClose": 319.728912, + "volume": 22373300 + }, + { + "date": "2023-08-10", + "open": 326.019989, + "high": 328.26001, + "low": 321.179993, + "close": 322.929993, + "adjClose": 320.423462, + "volume": 20113700 + }, + { + "date": "2023-08-11", + "open": 320.26001, + "high": 322.410004, + "low": 319.209991, + "close": 321.01001, + "adjClose": 318.518341, + "volume": 24342600 + }, + { + "date": "2023-08-14", + "open": 321.390015, + "high": 324.059998, + "low": 320.079987, + "close": 324.040009, + "adjClose": 321.524811, + "volume": 18836100 + }, + { + "date": "2023-08-15", + "open": 323.0, + "high": 325.089996, + "low": 320.899994, + "close": 321.859985, + "adjClose": 319.361725, + "volume": 16966300 + }, + { + "date": "2023-08-16", + "open": 320.799988, + "high": 324.420013, + "low": 319.799988, + "close": 320.399994, + "adjClose": 318.586151, + "volume": 20698900 + }, + { + "date": "2023-08-17", + "open": 320.540009, + "high": 321.869995, + "low": 316.209991, + "close": 316.880005, + "adjClose": 315.086121, + "volume": 21257200 + }, + { + "date": "2023-08-18", + "open": 314.48999, + "high": 318.380005, + "low": 311.549988, + "close": 316.480011, + "adjClose": 314.688354, + "volume": 24744800 + }, + { + "date": "2023-08-21", + "open": 317.929993, + "high": 322.769989, + "low": 317.040009, + "close": 321.880005, + "adjClose": 320.0578, + "volume": 24040000 + }, + { + "date": "2023-08-22", + "open": 325.5, + "high": 326.079987, + "low": 321.459991, + "close": 322.459991, + "adjClose": 320.634491, + "volume": 16102000 + }, + { + "date": "2023-08-23", + "open": 323.820007, + "high": 329.200012, + "low": 323.459991, + "close": 327.0, + "adjClose": 325.148804, + "volume": 21166400 + }, + { + "date": "2023-08-24", + "open": 332.850006, + "high": 332.980011, + "low": 319.959991, + "close": 319.970001, + "adjClose": 318.15863, + "volume": 23281400 + }, + { + "date": "2023-08-25", + "open": 321.470001, + "high": 325.359985, + "low": 318.799988, + "close": 322.980011, + "adjClose": 321.15155, + "volume": 21684100 + }, + { + "date": "2023-08-28", + "open": 325.660004, + "high": 326.149994, + "low": 321.720001, + "close": 323.700012, + "adjClose": 321.867493, + "volume": 14808500 + }, + { + "date": "2023-08-29", + "open": 321.880005, + "high": 328.980011, + "low": 321.880005, + "close": 328.410004, + "adjClose": 326.550812, + "volume": 19284600 + }, + { + "date": "2023-08-30", + "open": 328.670013, + "high": 329.809998, + "low": 326.450012, + "close": 328.790009, + "adjClose": 326.92865, + "volume": 15222100 + }, + { + "date": "2023-08-31", + "open": 329.200012, + "high": 330.910004, + "low": 326.779999, + "close": 327.76001, + "adjClose": 325.90451, + "volume": 26411000 + }, + { + "date": "2023-09-01", + "open": 331.309998, + "high": 331.98999, + "low": 326.779999, + "close": 328.660004, + "adjClose": 326.799408, + "volume": 14931200 + }, + { + "date": "2023-09-05", + "open": 329.0, + "high": 334.850006, + "low": 328.660004, + "close": 333.549988, + "adjClose": 331.661713, + "volume": 18553900 + }, + { + "date": "2023-09-06", + "open": 333.380005, + "high": 334.459991, + "low": 330.179993, + "close": 332.880005, + "adjClose": 330.995544, + "volume": 17535800 + }, + { + "date": "2023-09-07", + "open": 331.290009, + "high": 333.079987, + "low": 329.029999, + "close": 329.910004, + "adjClose": 328.042328, + "volume": 18381000 + }, + { + "date": "2023-09-08", + "open": 330.089996, + "high": 336.160004, + "low": 329.459991, + "close": 334.269989, + "adjClose": 332.377625, + "volume": 19530100 + }, + { + "date": "2023-09-11", + "open": 337.23999, + "high": 338.420013, + "low": 335.429993, + "close": 337.940002, + "adjClose": 336.026886, + "volume": 16583300 + }, + { + "date": "2023-09-12", + "open": 335.820007, + "high": 336.790009, + "low": 331.480011, + "close": 331.769989, + "adjClose": 329.891785, + "volume": 17565500 + }, + { + "date": "2023-09-13", + "open": 331.309998, + "high": 336.850006, + "low": 331.170013, + "close": 336.059998, + "adjClose": 334.157501, + "volume": 16544400 + }, + { + "date": "2023-09-14", + "open": 339.149994, + "high": 340.859985, + "low": 336.570007, + "close": 338.700012, + "adjClose": 336.782562, + "volume": 20267000 + }, + { + "date": "2023-09-15", + "open": 336.920013, + "high": 337.399994, + "low": 329.649994, + "close": 330.220001, + "adjClose": 328.350555, + "volume": 37666900 + }, + { + "date": "2023-09-18", + "open": 327.799988, + "high": 330.399994, + "low": 326.359985, + "close": 329.059998, + "adjClose": 327.197113, + "volume": 16834200 + }, + { + "date": "2023-09-19", + "open": 326.170013, + "high": 329.390015, + "low": 324.51001, + "close": 328.649994, + "adjClose": 326.789459, + "volume": 16505900 + }, + { + "date": "2023-09-20", + "open": 329.51001, + "high": 329.589996, + "low": 320.51001, + "close": 320.769989, + "adjClose": 318.954102, + "volume": 21436500 + }, + { + "date": "2023-09-21", + "open": 319.26001, + "high": 325.350006, + "low": 315.0, + "close": 319.529999, + "adjClose": 317.7211, + "volume": 35529500 + }, + { + "date": "2023-09-22", + "open": 321.320007, + "high": 321.450012, + "low": 316.149994, + "close": 317.01001, + "adjClose": 315.215363, + "volume": 21447900 + }, + { + "date": "2023-09-25", + "open": 316.589996, + "high": 317.670013, + "low": 315.0, + "close": 317.540009, + "adjClose": 315.742371, + "volume": 17836000 + }, + { + "date": "2023-09-26", + "open": 315.130005, + "high": 315.880005, + "low": 310.019989, + "close": 312.140015, + "adjClose": 310.372955, + "volume": 26297600 + }, + { + "date": "2023-09-27", + "open": 312.299988, + "high": 314.299988, + "low": 309.690002, + "close": 312.790009, + "adjClose": 311.019226, + "volume": 19410100 + }, + { + "date": "2023-09-28", + "open": 310.98999, + "high": 315.480011, + "low": 309.450012, + "close": 313.640015, + "adjClose": 311.864441, + "volume": 19683600 + }, + { + "date": "2023-09-29", + "open": 317.75, + "high": 319.470001, + "low": 314.980011, + "close": 315.75, + "adjClose": 313.962494, + "volume": 24140300 + }, + { + "date": "2023-10-02", + "open": 316.279999, + "high": 321.890015, + "low": 315.179993, + "close": 321.799988, + "adjClose": 319.978241, + "volume": 20570000 + }, + { + "date": "2023-10-03", + "open": 320.829987, + "high": 321.390015, + "low": 311.209991, + "close": 313.390015, + "adjClose": 311.615845, + "volume": 21033500 + }, + { + "date": "2023-10-04", + "open": 314.029999, + "high": 320.040009, + "low": 314.0, + "close": 318.959991, + "adjClose": 317.154327, + "volume": 20720100 + }, + { + "date": "2023-10-05", + "open": 319.089996, + "high": 319.980011, + "low": 314.899994, + "close": 319.359985, + "adjClose": 317.552063, + "volume": 16965600 + }, + { + "date": "2023-10-06", + "open": 316.549988, + "high": 329.190002, + "low": 316.299988, + "close": 327.26001, + "adjClose": 325.407349, + "volume": 25645500 + }, + { + "date": "2023-10-09", + "open": 324.75, + "high": 330.299988, + "low": 323.179993, + "close": 329.820007, + "adjClose": 327.95285, + "volume": 19891200 + }, + { + "date": "2023-10-10", + "open": 330.959991, + "high": 331.100006, + "low": 327.670013, + "close": 328.390015, + "adjClose": 326.530945, + "volume": 20557100 + }, + { + "date": "2023-10-11", + "open": 331.209991, + "high": 332.820007, + "low": 329.140015, + "close": 332.420013, + "adjClose": 330.538147, + "volume": 20063200 + }, + { + "date": "2023-10-12", + "open": 330.570007, + "high": 333.630005, + "low": 328.720001, + "close": 331.160004, + "adjClose": 329.285278, + "volume": 19313100 + }, + { + "date": "2023-10-13", + "open": 332.380005, + "high": 333.829987, + "low": 326.359985, + "close": 327.730011, + "adjClose": 325.874695, + "volume": 21072400 + }, + { + "date": "2023-10-16", + "open": 331.049988, + "high": 336.140015, + "low": 330.600006, + "close": 332.640015, + "adjClose": 330.756897, + "volume": 22158000 + }, + { + "date": "2023-10-17", + "open": 329.589996, + "high": 333.459991, + "low": 327.410004, + "close": 332.059998, + "adjClose": 330.180145, + "volume": 18338500 + }, + { + "date": "2023-10-18", + "open": 332.48999, + "high": 335.589996, + "low": 328.299988, + "close": 330.109985, + "adjClose": 328.241211, + "volume": 23153600 + }, + { + "date": "2023-10-19", + "open": 332.149994, + "high": 336.880005, + "low": 330.910004, + "close": 331.320007, + "adjClose": 329.444336, + "volume": 25052100 + }, + { + "date": "2023-10-20", + "open": 331.720001, + "high": 331.920013, + "low": 325.450012, + "close": 326.670013, + "adjClose": 324.820679, + "volume": 25012600 + }, + { + "date": "2023-10-23", + "open": 325.470001, + "high": 332.730011, + "low": 324.390015, + "close": 329.320007, + "adjClose": 327.455658, + "volume": 24374700 + }, + { + "date": "2023-10-24", + "open": 331.299988, + "high": 331.839996, + "low": 327.600006, + "close": 330.529999, + "adjClose": 328.658844, + "volume": 31153600 + }, + { + "date": "2023-10-25", + "open": 345.019989, + "high": 346.200012, + "low": 337.619995, + "close": 340.670013, + "adjClose": 338.741455, + "volume": 55053800 + }, + { + "date": "2023-10-26", + "open": 340.540009, + "high": 341.630005, + "low": 326.940002, + "close": 327.890015, + "adjClose": 326.033783, + "volume": 37828500 + }, + { + "date": "2023-10-27", + "open": 330.429993, + "high": 336.720001, + "low": 328.399994, + "close": 329.809998, + "adjClose": 327.942902, + "volume": 29856500 + }, + { + "date": "2023-10-30", + "open": 333.410004, + "high": 339.450012, + "low": 331.829987, + "close": 337.309998, + "adjClose": 335.400452, + "volume": 22828100 + }, + { + "date": "2023-10-31", + "open": 338.850006, + "high": 339.0, + "low": 334.690002, + "close": 338.109985, + "adjClose": 336.195923, + "volume": 20265300 + }, + { + "date": "2023-11-01", + "open": 339.790009, + "high": 347.420013, + "low": 339.649994, + "close": 346.070007, + "adjClose": 344.11084, + "volume": 28158800 + }, + { + "date": "2023-11-02", + "open": 347.23999, + "high": 348.829987, + "low": 344.769989, + "close": 348.320007, + "adjClose": 346.348114, + "volume": 24348100 + }, + { + "date": "2023-11-03", + "open": 349.630005, + "high": 354.390015, + "low": 347.329987, + "close": 352.799988, + "adjClose": 350.802765, + "volume": 23624000 + }, + { + "date": "2023-11-06", + "open": 353.450012, + "high": 357.540009, + "low": 353.350006, + "close": 356.529999, + "adjClose": 354.511627, + "volume": 23828300 + }, + { + "date": "2023-11-07", + "open": 359.399994, + "high": 362.459991, + "low": 357.630005, + "close": 360.529999, + "adjClose": 358.489014, + "volume": 25833900 + }, + { + "date": "2023-11-08", + "open": 361.679993, + "high": 363.869995, + "low": 360.549988, + "close": 363.200012, + "adjClose": 361.14386, + "volume": 26767800 + }, + { + "date": "2023-11-09", + "open": 362.299988, + "high": 364.790009, + "low": 360.359985, + "close": 360.690002, + "adjClose": 358.648071, + "volume": 24847300 + }, + { + "date": "2023-11-10", + "open": 361.48999, + "high": 370.100006, + "low": 361.070007, + "close": 369.670013, + "adjClose": 367.57724, + "volume": 28042100 + }, + { + "date": "2023-11-13", + "open": 368.220001, + "high": 368.470001, + "low": 365.899994, + "close": 366.679993, + "adjClose": 364.604126, + "volume": 19986500 + }, + { + "date": "2023-11-14", + "open": 371.01001, + "high": 371.950012, + "low": 367.350006, + "close": 370.269989, + "adjClose": 368.173828, + "volume": 27683900 + }, + { + "date": "2023-11-15", + "open": 371.279999, + "high": 373.130005, + "low": 367.109985, + "close": 369.670013, + "adjClose": 368.323303, + "volume": 26860100 + }, + { + "date": "2023-11-16", + "open": 370.959991, + "high": 376.350006, + "low": 370.179993, + "close": 376.170013, + "adjClose": 374.799652, + "volume": 27182300 + }, + { + "date": "2023-11-17", + "open": 373.609985, + "high": 374.369995, + "low": 367.0, + "close": 369.850006, + "adjClose": 368.502655, + "volume": 40157000 + }, + { + "date": "2023-11-20", + "open": 371.220001, + "high": 378.869995, + "low": 371.0, + "close": 377.440002, + "adjClose": 376.065002, + "volume": 52465100 + }, + { + "date": "2023-11-21", + "open": 375.670013, + "high": 376.220001, + "low": 371.119995, + "close": 373.070007, + "adjClose": 371.710938, + "volume": 28423100 + }, + { + "date": "2023-11-22", + "open": 378.0, + "high": 379.790009, + "low": 374.970001, + "close": 377.850006, + "adjClose": 376.473511, + "volume": 23345300 + }, + { + "date": "2023-11-24", + "open": 377.329987, + "high": 377.970001, + "low": 375.140015, + "close": 377.429993, + "adjClose": 376.054993, + "volume": 10176600 + }, + { + "date": "2023-11-27", + "open": 376.779999, + "high": 380.640015, + "low": 376.200012, + "close": 378.609985, + "adjClose": 377.230713, + "volume": 22179200 + }, + { + "date": "2023-11-28", + "open": 378.350006, + "high": 383.0, + "low": 378.160004, + "close": 382.700012, + "adjClose": 381.305817, + "volume": 20453100 + }, + { + "date": "2023-11-29", + "open": 383.76001, + "high": 384.299988, + "low": 377.440002, + "close": 378.850006, + "adjClose": 377.469879, + "volume": 28963400 + }, + { + "date": "2023-11-30", + "open": 378.48999, + "high": 380.089996, + "low": 375.470001, + "close": 378.910004, + "adjClose": 377.529633, + "volume": 30554400 + }, + { + "date": "2023-12-01", + "open": 376.76001, + "high": 378.160004, + "low": 371.309998, + "close": 374.51001, + "adjClose": 373.145691, + "volume": 33020400 + }, + { + "date": "2023-12-04", + "open": 369.100006, + "high": 369.519989, + "low": 362.899994, + "close": 369.140015, + "adjClose": 367.795227, + "volume": 32063300 + }, + { + "date": "2023-12-05", + "open": 366.450012, + "high": 373.079987, + "low": 365.619995, + "close": 372.519989, + "adjClose": 371.162903, + "volume": 23065000 + }, + { + "date": "2023-12-06", + "open": 373.540009, + "high": 374.179993, + "low": 368.029999, + "close": 368.799988, + "adjClose": 367.456482, + "volume": 21182100 + }, + { + "date": "2023-12-07", + "open": 368.230011, + "high": 371.450012, + "low": 366.320007, + "close": 370.950012, + "adjClose": 369.598663, + "volume": 23118900 + }, + { + "date": "2023-12-08", + "open": 369.200012, + "high": 374.459991, + "low": 368.230011, + "close": 374.230011, + "adjClose": 372.866699, + "volume": 20144800 + }, + { + "date": "2023-12-11", + "open": 368.480011, + "high": 371.600006, + "low": 366.100006, + "close": 371.299988, + "adjClose": 369.947357, + "volume": 27708800 + }, + { + "date": "2023-12-12", + "open": 370.850006, + "high": 374.420013, + "low": 370.459991, + "close": 374.380005, + "adjClose": 373.016144, + "volume": 24838300 + }, + { + "date": "2023-12-13", + "open": 376.019989, + "high": 377.640015, + "low": 370.769989, + "close": 374.369995, + "adjClose": 373.006165, + "volume": 30955500 + }, + { + "date": "2023-12-14", + "open": 373.309998, + "high": 373.76001, + "low": 364.130005, + "close": 365.929993, + "adjClose": 364.596924, + "volume": 43277500 + }, + { + "date": "2023-12-15", + "open": 366.850006, + "high": 372.399994, + "low": 366.279999, + "close": 370.730011, + "adjClose": 369.379456, + "volume": 78478200 + }, + { + "date": "2023-12-18", + "open": 369.450012, + "high": 373.0, + "low": 368.679993, + "close": 372.649994, + "adjClose": 371.292419, + "volume": 21802900 + }, + { + "date": "2023-12-19", + "open": 371.48999, + "high": 373.26001, + "low": 369.839996, + "close": 373.26001, + "adjClose": 371.900208, + "volume": 20603700 + }, + { + "date": "2023-12-20", + "open": 375.0, + "high": 376.029999, + "low": 370.529999, + "close": 370.619995, + "adjClose": 369.269836, + "volume": 26316700 + }, + { + "date": "2023-12-21", + "open": 372.559998, + "high": 374.410004, + "low": 370.040009, + "close": 373.540009, + "adjClose": 372.179199, + "volume": 17708000 + }, + { + "date": "2023-12-22", + "open": 373.679993, + "high": 375.179993, + "low": 372.709991, + "close": 374.579987, + "adjClose": 373.215424, + "volume": 17091100 + }, + { + "date": "2023-12-26", + "open": 375.0, + "high": 376.940002, + "low": 373.5, + "close": 374.660004, + "adjClose": 373.295135, + "volume": 12673100 + }, + { + "date": "2023-12-27", + "open": 373.690002, + "high": 375.059998, + "low": 372.809998, + "close": 374.070007, + "adjClose": 372.707275, + "volume": 14905400 + }, + { + "date": "2023-12-28", + "open": 375.369995, + "high": 376.459991, + "low": 374.160004, + "close": 375.279999, + "adjClose": 373.912842, + "volume": 14327000 + }, + { + "date": "2023-12-29", + "open": 376.0, + "high": 377.160004, + "low": 373.480011, + "close": 376.040009, + "adjClose": 374.670074, + "volume": 18723000 + }, + { + "date": "2024-01-02", + "open": 373.859985, + "high": 375.899994, + "low": 366.769989, + "close": 370.869995, + "adjClose": 369.518921, + "volume": 25258600 + }, + { + "date": "2024-01-03", + "open": 369.01001, + "high": 373.26001, + "low": 368.51001, + "close": 370.600006, + "adjClose": 369.249908, + "volume": 23083500 + }, + { + "date": "2024-01-04", + "open": 370.670013, + "high": 373.100006, + "low": 367.170013, + "close": 367.940002, + "adjClose": 366.599579, + "volume": 20901500 + }, + { + "date": "2024-01-05", + "open": 368.970001, + "high": 372.059998, + "low": 366.5, + "close": 367.75, + "adjClose": 366.410278, + "volume": 20987000 + }, + { + "date": "2024-01-08", + "open": 369.299988, + "high": 375.200012, + "low": 369.01001, + "close": 374.690002, + "adjClose": 373.325012, + "volume": 23134000 + }, + { + "date": "2024-01-09", + "open": 372.01001, + "high": 375.98999, + "low": 371.190002, + "close": 375.790009, + "adjClose": 374.421021, + "volume": 20830000 + }, + { + "date": "2024-01-10", + "open": 376.369995, + "high": 384.170013, + "low": 376.320007, + "close": 382.769989, + "adjClose": 381.375549, + "volume": 25514200 + }, + { + "date": "2024-01-11", + "open": 386.0, + "high": 390.679993, + "low": 380.380005, + "close": 384.630005, + "adjClose": 383.228821, + "volume": 27850800 + }, + { + "date": "2024-01-12", + "open": 385.48999, + "high": 388.679993, + "low": 384.649994, + "close": 388.470001, + "adjClose": 387.05481, + "volume": 21645700 + }, + { + "date": "2024-01-16", + "open": 393.660004, + "high": 394.029999, + "low": 387.619995, + "close": 390.269989, + "adjClose": 388.848236, + "volume": 27202300 + }, + { + "date": "2024-01-17", + "open": 387.980011, + "high": 390.109985, + "low": 384.809998, + "close": 389.470001, + "adjClose": 388.051178, + "volume": 22234100 + }, + { + "date": "2024-01-18", + "open": 391.720001, + "high": 393.98999, + "low": 390.119995, + "close": 393.869995, + "adjClose": 392.43512, + "volume": 23392100 + }, + { + "date": "2024-01-19", + "open": 395.76001, + "high": 398.670013, + "low": 393.5, + "close": 398.670013, + "adjClose": 397.217651, + "volume": 29272000 + }, + { + "date": "2024-01-22", + "open": 400.019989, + "high": 400.619995, + "low": 393.589996, + "close": 396.51001, + "adjClose": 395.065552, + "volume": 27016900 + }, + { + "date": "2024-01-23", + "open": 395.75, + "high": 399.380005, + "low": 393.929993, + "close": 398.899994, + "adjClose": 397.446808, + "volume": 20525900 + }, + { + "date": "2024-01-24", + "open": 401.540009, + "high": 405.630005, + "low": 400.450012, + "close": 402.559998, + "adjClose": 401.093475, + "volume": 24867000 + }, + { + "date": "2024-01-25", + "open": 404.320007, + "high": 407.01001, + "low": 402.529999, + "close": 404.869995, + "adjClose": 403.39505, + "volume": 21021200 + }, + { + "date": "2024-01-26", + "open": 404.369995, + "high": 406.170013, + "low": 402.429993, + "close": 403.929993, + "adjClose": 402.458496, + "volume": 17803300 + }, + { + "date": "2024-01-29", + "open": 406.059998, + "high": 409.980011, + "low": 404.329987, + "close": 409.720001, + "adjClose": 408.227386, + "volume": 24510200 + }, + { + "date": "2024-01-30", + "open": 412.26001, + "high": 413.049988, + "low": 406.450012, + "close": 408.589996, + "adjClose": 407.101501, + "volume": 33477600 + }, + { + "date": "2024-01-31", + "open": 406.959991, + "high": 415.320007, + "low": 397.209991, + "close": 397.579987, + "adjClose": 396.131622, + "volume": 47871100 + }, + { + "date": "2024-02-01", + "open": 401.829987, + "high": 408.0, + "low": 401.799988, + "close": 403.779999, + "adjClose": 402.309052, + "volume": 30657700 + }, + { + "date": "2024-02-02", + "open": 403.809998, + "high": 412.649994, + "low": 403.559998, + "close": 411.220001, + "adjClose": 409.721924, + "volume": 28245000 + }, + { + "date": "2024-02-05", + "open": 409.899994, + "high": 411.160004, + "low": 403.98999, + "close": 405.649994, + "adjClose": 404.172211, + "volume": 25352300 + }, + { + "date": "2024-02-06", + "open": 405.880005, + "high": 407.970001, + "low": 402.910004, + "close": 405.48999, + "adjClose": 404.012787, + "volume": 18382600 + }, + { + "date": "2024-02-07", + "open": 407.440002, + "high": 414.299988, + "low": 407.399994, + "close": 414.049988, + "adjClose": 412.541626, + "volume": 22340500 + }, + { + "date": "2024-02-08", + "open": 414.049988, + "high": 415.559998, + "low": 412.529999, + "close": 414.109985, + "adjClose": 412.601379, + "volume": 21225300 + }, + { + "date": "2024-02-09", + "open": 415.25, + "high": 420.820007, + "low": 415.089996, + "close": 420.549988, + "adjClose": 419.017914, + "volume": 22032800 + }, + { + "date": "2024-02-12", + "open": 420.559998, + "high": 420.73999, + "low": 414.75, + "close": 415.26001, + "adjClose": 413.747192, + "volume": 21202900 + }, + { + "date": "2024-02-13", + "open": 404.940002, + "high": 410.070007, + "low": 403.390015, + "close": 406.320007, + "adjClose": 404.839783, + "volume": 27824900 + }, + { + "date": "2024-02-14", + "open": 408.070007, + "high": 409.839996, + "low": 404.570007, + "close": 409.48999, + "adjClose": 408.752716, + "volume": 20401200 + }, + { + "date": "2024-02-15", + "open": 408.140015, + "high": 409.130005, + "low": 404.290009, + "close": 406.559998, + "adjClose": 405.828003, + "volume": 21825500 + }, + { + "date": "2024-02-16", + "open": 407.959991, + "high": 408.290009, + "low": 403.440002, + "close": 404.059998, + "adjClose": 403.332489, + "volume": 22281100 + }, + { + "date": "2024-02-20", + "open": 403.23999, + "high": 404.48999, + "low": 398.01001, + "close": 402.790009, + "adjClose": 402.064789, + "volume": 24307900 + }, + { + "date": "2024-02-21", + "open": 400.170013, + "high": 402.290009, + "low": 397.220001, + "close": 402.179993, + "adjClose": 401.455872, + "volume": 18631100 + }, + { + "date": "2024-02-22", + "open": 410.190002, + "high": 412.829987, + "low": 408.570007, + "close": 411.649994, + "adjClose": 410.908813, + "volume": 27009900 + }, + { + "date": "2024-02-23", + "open": 415.670013, + "high": 415.859985, + "low": 408.970001, + "close": 410.339996, + "adjClose": 409.601196, + "volume": 16295900 + }, + { + "date": "2024-02-26", + "open": 411.459991, + "high": 412.160004, + "low": 407.359985, + "close": 407.540009, + "adjClose": 406.806244, + "volume": 16193500 + }, + { + "date": "2024-02-27", + "open": 407.98999, + "high": 408.320007, + "low": 403.850006, + "close": 407.480011, + "adjClose": 406.746338, + "volume": 14835800 + }, + { + "date": "2024-02-28", + "open": 408.179993, + "high": 409.299988, + "low": 405.320007, + "close": 407.720001, + "adjClose": 406.985901, + "volume": 13183100 + }, + { + "date": "2024-02-29", + "open": 408.640015, + "high": 414.200012, + "low": 405.920013, + "close": 413.640015, + "adjClose": 412.895264, + "volume": 31947300 + }, + { + "date": "2024-03-01", + "open": 411.269989, + "high": 415.869995, + "low": 410.880005, + "close": 415.5, + "adjClose": 414.751892, + "volume": 17800300 + }, + { + "date": "2024-03-04", + "open": 413.440002, + "high": 417.350006, + "low": 412.320007, + "close": 414.920013, + "adjClose": 414.172974, + "volume": 17596000 + }, + { + "date": "2024-03-05", + "open": 413.959991, + "high": 414.25, + "low": 400.640015, + "close": 402.649994, + "adjClose": 401.925018, + "volume": 26919200 + }, + { + "date": "2024-03-06", + "open": 402.970001, + "high": 405.160004, + "low": 398.390015, + "close": 402.089996, + "adjClose": 401.366028, + "volume": 22344100 + }, + { + "date": "2024-03-07", + "open": 406.119995, + "high": 409.779999, + "low": 402.23999, + "close": 409.140015, + "adjClose": 408.403381, + "volume": 18718500 + }, + { + "date": "2024-03-08", + "open": 407.959991, + "high": 410.420013, + "low": 404.329987, + "close": 406.220001, + "adjClose": 405.488617, + "volume": 17971700 + }, + { + "date": "2024-03-11", + "open": 403.76001, + "high": 405.679993, + "low": 401.26001, + "close": 404.519989, + "adjClose": 403.791656, + "volume": 16120800 + }, + { + "date": "2024-03-12", + "open": 407.619995, + "high": 415.570007, + "low": 406.790009, + "close": 415.279999, + "adjClose": 414.532288, + "volume": 22457000 + }, + { + "date": "2024-03-13", + "open": 418.100006, + "high": 418.179993, + "low": 411.450012, + "close": 415.100006, + "adjClose": 414.352631, + "volume": 17115900 + }, + { + "date": "2024-03-14", + "open": 420.23999, + "high": 427.820007, + "low": 417.98999, + "close": 425.220001, + "adjClose": 424.454407, + "volume": 34157300 + }, + { + "date": "2024-03-15", + "open": 419.290009, + "high": 422.600006, + "low": 412.790009, + "close": 416.420013, + "adjClose": 415.670258, + "volume": 45049800 + }, + { + "date": "2024-03-18", + "open": 414.25, + "high": 420.730011, + "low": 413.779999, + "close": 417.320007, + "adjClose": 416.568634, + "volume": 20106000 + }, + { + "date": "2024-03-19", + "open": 417.829987, + "high": 421.670013, + "low": 415.549988, + "close": 421.410004, + "adjClose": 420.651276, + "volume": 19837900 + }, + { + "date": "2024-03-20", + "open": 422.0, + "high": 425.959991, + "low": 420.660004, + "close": 425.230011, + "adjClose": 424.464386, + "volume": 17860100 + }, + { + "date": "2024-03-21", + "open": 429.829987, + "high": 430.820007, + "low": 427.160004, + "close": 429.369995, + "adjClose": 428.596924, + "volume": 21296200 + }, + { + "date": "2024-03-22", + "open": 429.700012, + "high": 429.859985, + "low": 426.070007, + "close": 428.73999, + "adjClose": 427.968048, + "volume": 17636500 + }, + { + "date": "2024-03-25", + "open": 425.23999, + "high": 427.410004, + "low": 421.609985, + "close": 422.859985, + "adjClose": 422.098633, + "volume": 18060500 + }, + { + "date": "2024-03-26", + "open": 425.609985, + "high": 425.98999, + "low": 421.350006, + "close": 421.649994, + "adjClose": 420.890808, + "volume": 16725600 + }, + { + "date": "2024-03-27", + "open": 424.440002, + "high": 424.450012, + "low": 419.01001, + "close": 421.429993, + "adjClose": 420.671204, + "volume": 16705000 + }, + { + "date": "2024-03-28", + "open": 420.959991, + "high": 421.869995, + "low": 419.119995, + "close": 420.720001, + "adjClose": 419.962494, + "volume": 21871200 + }, + { + "date": "2024-04-01", + "open": 423.950012, + "high": 427.890015, + "low": 422.220001, + "close": 424.570007, + "adjClose": 423.805573, + "volume": 16316000 + }, + { + "date": "2024-04-02", + "open": 420.109985, + "high": 422.380005, + "low": 417.839996, + "close": 421.440002, + "adjClose": 420.681213, + "volume": 17912000 + }, + { + "date": "2024-04-03", + "open": 419.730011, + "high": 423.26001, + "low": 419.089996, + "close": 420.450012, + "adjClose": 419.692993, + "volume": 16502300 + }, + { + "date": "2024-04-04", + "open": 424.98999, + "high": 428.670013, + "low": 417.570007, + "close": 417.880005, + "adjClose": 417.127625, + "volume": 19370900 + }, + { + "date": "2024-04-05", + "open": 420.01001, + "high": 426.51001, + "low": 418.320007, + "close": 425.519989, + "adjClose": 424.753845, + "volume": 16544300 + }, + { + "date": "2024-04-08", + "open": 425.170013, + "high": 427.279999, + "low": 423.299988, + "close": 424.589996, + "adjClose": 423.825531, + "volume": 14272400 + }, + { + "date": "2024-04-09", + "open": 426.440002, + "high": 427.73999, + "low": 421.619995, + "close": 426.279999, + "adjClose": 425.512482, + "volume": 12512300 + }, + { + "date": "2024-04-10", + "open": 422.190002, + "high": 424.029999, + "low": 419.700012, + "close": 423.26001, + "adjClose": 422.497925, + "volume": 16216600 + }, + { + "date": "2024-04-11", + "open": 425.820007, + "high": 429.369995, + "low": 422.359985, + "close": 427.929993, + "adjClose": 427.159515, + "volume": 17966400 + }, + { + "date": "2024-04-12", + "open": 424.049988, + "high": 425.179993, + "low": 419.769989, + "close": 421.899994, + "adjClose": 421.140381, + "volume": 19232100 + }, + { + "date": "2024-04-15", + "open": 426.600006, + "high": 426.820007, + "low": 413.429993, + "close": 413.640015, + "adjClose": 412.895264, + "volume": 20273500 + }, + { + "date": "2024-04-16", + "open": 414.570007, + "high": 418.399994, + "low": 413.730011, + "close": 414.579987, + "adjClose": 413.833557, + "volume": 16765600 + }, + { + "date": "2024-04-17", + "open": 417.25, + "high": 418.880005, + "low": 410.329987, + "close": 411.839996, + "adjClose": 411.09848, + "volume": 15855500 + }, + { + "date": "2024-04-18", + "open": 410.630005, + "high": 411.890015, + "low": 403.950012, + "close": 404.269989, + "adjClose": 403.542114, + "volume": 21029900 + }, + { + "date": "2024-04-19", + "open": 404.029999, + "high": 405.480011, + "low": 397.769989, + "close": 399.119995, + "adjClose": 398.401398, + "volume": 30276500 + }, + { + "date": "2024-04-22", + "open": 400.079987, + "high": 402.850006, + "low": 395.75, + "close": 400.959991, + "adjClose": 400.238068, + "volume": 20286900 + }, + { + "date": "2024-04-23", + "open": 404.23999, + "high": 408.200012, + "low": 403.059998, + "close": 407.570007, + "adjClose": 406.836182, + "volume": 15734500 + }, + { + "date": "2024-04-24", + "open": 409.559998, + "high": 412.470001, + "low": 406.779999, + "close": 409.059998, + "adjClose": 408.323486, + "volume": 15065300 + }, + { + "date": "2024-04-25", + "open": 394.029999, + "high": 399.890015, + "low": 388.029999, + "close": 399.040009, + "adjClose": 398.321533, + "volume": 40586500 + }, + { + "date": "2024-04-26", + "open": 412.170013, + "high": 413.0, + "low": 405.76001, + "close": 406.320007, + "adjClose": 405.58844, + "volume": 29694700 + }, + { + "date": "2024-04-29", + "open": 405.25, + "high": 406.320007, + "low": 399.190002, + "close": 402.25, + "adjClose": 401.525757, + "volume": 19582100 + }, + { + "date": "2024-04-30", + "open": 401.48999, + "high": 402.160004, + "low": 389.170013, + "close": 389.329987, + "adjClose": 388.628998, + "volume": 28781400 + }, + { + "date": "2024-05-01", + "open": 392.609985, + "high": 401.720001, + "low": 390.309998, + "close": 394.940002, + "adjClose": 394.228912, + "volume": 23562500 + }, + { + "date": "2024-05-02", + "open": 397.660004, + "high": 399.929993, + "low": 394.649994, + "close": 397.839996, + "adjClose": 397.123688, + "volume": 17709400 + }, + { + "date": "2024-05-03", + "open": 402.279999, + "high": 407.149994, + "low": 401.859985, + "close": 406.660004, + "adjClose": 405.927826, + "volume": 17446700 + }, + { + "date": "2024-05-06", + "open": 408.76001, + "high": 413.929993, + "low": 406.369995, + "close": 413.540009, + "adjClose": 412.795441, + "volume": 16996600 + }, + { + "date": "2024-05-07", + "open": 414.660004, + "high": 414.670013, + "low": 409.089996, + "close": 409.339996, + "adjClose": 408.602997, + "volume": 20018200 + }, + { + "date": "2024-05-08", + "open": 408.170013, + "high": 412.230011, + "low": 406.709991, + "close": 410.540009, + "adjClose": 409.800842, + "volume": 11792300 + }, + { + "date": "2024-05-09", + "open": 410.570007, + "high": 412.720001, + "low": 409.100006, + "close": 412.320007, + "adjClose": 411.577637, + "volume": 14689700 + }, + { + "date": "2024-05-10", + "open": 412.940002, + "high": 415.380005, + "low": 411.799988, + "close": 414.73999, + "adjClose": 413.993256, + "volume": 13402300 + }, + { + "date": "2024-05-13", + "open": 418.01001, + "high": 418.350006, + "low": 410.820007, + "close": 413.720001, + "adjClose": 412.975098, + "volume": 15440200 + }, + { + "date": "2024-05-14", + "open": 412.019989, + "high": 417.48999, + "low": 411.549988, + "close": 416.559998, + "adjClose": 415.809998, + "volume": 15109300 + }, + { + "date": "2024-05-15", + "open": 417.899994, + "high": 423.809998, + "low": 417.269989, + "close": 423.079987, + "adjClose": 423.079987, + "volume": 22239500 + }, + { + "date": "2024-05-16", + "open": 421.799988, + "high": 425.420013, + "low": 420.350006, + "close": 420.98999, + "adjClose": 420.98999, + "volume": 17530100 + }, + { + "date": "2024-05-17", + "open": 422.540009, + "high": 422.920013, + "low": 418.029999, + "close": 420.209991, + "adjClose": 420.209991, + "volume": 15352200 + }, + { + "date": "2024-05-20", + "open": 420.209991, + "high": 426.769989, + "low": 419.98999, + "close": 425.339996, + "adjClose": 425.339996, + "volume": 16272100 + }, + { + "date": "2024-05-21", + "open": 426.829987, + "high": 432.970001, + "low": 424.850006, + "close": 429.040009, + "adjClose": 429.040009, + "volume": 21453300 + }, + { + "date": "2024-05-22", + "open": 430.089996, + "high": 432.410004, + "low": 427.130005, + "close": 430.519989, + "adjClose": 430.519989, + "volume": 18073700 + }, + { + "date": "2024-05-23", + "open": 432.970001, + "high": 433.600006, + "low": 425.420013, + "close": 427.0, + "adjClose": 427.0, + "volume": 17211700 + }, + { + "date": "2024-05-24", + "open": 427.190002, + "high": 431.059998, + "low": 424.410004, + "close": 430.160004, + "adjClose": 430.160004, + "volume": 11845800 + }, + { + "date": "2024-05-28", + "open": 429.630005, + "high": 430.820007, + "low": 426.600006, + "close": 430.320007, + "adjClose": 430.320007, + "volume": 15718000 + }, + { + "date": "2024-05-29", + "open": 425.690002, + "high": 430.940002, + "low": 425.690002, + "close": 429.170013, + "adjClose": 429.170013, + "volume": 15517100 + }, + { + "date": "2024-05-30", + "open": 424.299988, + "high": 424.299988, + "low": 414.23999, + "close": 414.670013, + "adjClose": 414.670013, + "volume": 28424800 + }, + { + "date": "2024-05-31", + "open": 416.75, + "high": 416.75, + "low": 404.51001, + "close": 415.130005, + "adjClose": 415.130005, + "volume": 47995300 + }, + { + "date": "2024-06-03", + "open": 415.529999, + "high": 416.429993, + "low": 408.920013, + "close": 413.519989, + "adjClose": 413.519989, + "volume": 17484700 + }, + { + "date": "2024-06-04", + "open": 412.429993, + "high": 416.440002, + "low": 409.679993, + "close": 416.070007, + "adjClose": 416.070007, + "volume": 14348900 + }, + { + "date": "2024-06-05", + "open": 417.809998, + "high": 424.079987, + "low": 416.299988, + "close": 424.01001, + "adjClose": 424.01001, + "volume": 16988000 + }, + { + "date": "2024-06-06", + "open": 424.01001, + "high": 425.309998, + "low": 420.579987, + "close": 424.519989, + "adjClose": 424.519989, + "volume": 14861300 + }, + { + "date": "2024-06-07", + "open": 426.200012, + "high": 426.279999, + "low": 423.0, + "close": 423.850006, + "adjClose": 423.850006, + "volume": 13621700 + }, + { + "date": "2024-06-10", + "open": 424.700012, + "high": 428.079987, + "low": 423.890015, + "close": 427.869995, + "adjClose": 427.869995, + "volume": 14003000 + }, + { + "date": "2024-06-11", + "open": 425.480011, + "high": 432.820007, + "low": 425.25, + "close": 432.679993, + "adjClose": 432.679993, + "volume": 14551100 + }, + { + "date": "2024-06-12", + "open": 435.320007, + "high": 443.399994, + "low": 433.25, + "close": 441.059998, + "adjClose": 441.059998, + "volume": 22366200 + }, + { + "date": "2024-06-13", + "open": 440.850006, + "high": 443.390015, + "low": 439.369995, + "close": 441.579987, + "adjClose": 441.579987, + "volume": 15960600 + }, + { + "date": "2024-06-14", + "open": 438.279999, + "high": 443.140015, + "low": 436.720001, + "close": 442.570007, + "adjClose": 442.570007, + "volume": 13582000 + }, + { + "date": "2024-06-17", + "open": 442.589996, + "high": 450.940002, + "low": 440.720001, + "close": 448.369995, + "adjClose": 448.369995, + "volume": 20790000 + }, + { + "date": "2024-06-18", + "open": 449.709991, + "high": 450.140015, + "low": 444.890015, + "close": 446.339996, + "adjClose": 446.339996, + "volume": 17112500 + }, + { + "date": "2024-06-20", + "open": 446.299988, + "high": 446.529999, + "low": 441.269989, + "close": 445.700012, + "adjClose": 445.700012, + "volume": 19877400 + }, + { + "date": "2024-06-21", + "open": 447.380005, + "high": 450.579987, + "low": 446.51001, + "close": 449.779999, + "adjClose": 449.779999, + "volume": 34486200 + }, + { + "date": "2024-06-24", + "open": 449.799988, + "high": 452.75, + "low": 446.410004, + "close": 447.670013, + "adjClose": 447.670013, + "volume": 15913700 + }, + { + "date": "2024-06-25", + "open": 448.25, + "high": 451.420013, + "low": 446.75, + "close": 450.950012, + "adjClose": 450.950012, + "volume": 16747500 + }, + { + "date": "2024-06-26", + "open": 449.0, + "high": 453.600006, + "low": 448.190002, + "close": 452.160004, + "adjClose": 452.160004, + "volume": 16507000 + }, + { + "date": "2024-06-27", + "open": 452.179993, + "high": 456.170013, + "low": 451.769989, + "close": 452.850006, + "adjClose": 452.850006, + "volume": 14806300 + }, + { + "date": "2024-06-28", + "open": 453.070007, + "high": 455.380005, + "low": 446.410004, + "close": 446.950012, + "adjClose": 446.950012, + "volume": 28362300 + }, + { + "date": "2024-07-01", + "open": 448.660004, + "high": 457.369995, + "low": 445.660004, + "close": 456.730011, + "adjClose": 456.730011, + "volume": 17662800 + }, + { + "date": "2024-07-02", + "open": 453.200012, + "high": 459.589996, + "low": 453.109985, + "close": 459.279999, + "adjClose": 459.279999, + "volume": 13979800 + }, + { + "date": "2024-07-03", + "open": 458.190002, + "high": 461.019989, + "low": 457.880005, + "close": 460.769989, + "adjClose": 460.769989, + "volume": 9932800 + }, + { + "date": "2024-07-05", + "open": 459.609985, + "high": 468.350006, + "low": 458.970001, + "close": 467.559998, + "adjClose": 467.559998, + "volume": 16000300 + }, + { + "date": "2024-07-08", + "open": 466.549988, + "high": 467.700012, + "low": 464.459991, + "close": 466.23999, + "adjClose": 466.23999, + "volume": 12962300 + }, + { + "date": "2024-07-09", + "open": 467.0, + "high": 467.329987, + "low": 458.0, + "close": 459.540009, + "adjClose": 459.540009, + "volume": 17207200 + }, + { + "date": "2024-07-10", + "open": 461.220001, + "high": 466.459991, + "low": 458.859985, + "close": 466.25, + "adjClose": 466.25, + "volume": 18196100 + }, + { + "date": "2024-07-11", + "open": 462.980011, + "high": 464.779999, + "low": 451.549988, + "close": 454.700012, + "adjClose": 454.700012, + "volume": 23111200 + }, + { + "date": "2024-07-12", + "open": 454.329987, + "high": 456.359985, + "low": 450.649994, + "close": 453.549988, + "adjClose": 453.549988, + "volume": 16324300 + }, + { + "date": "2024-07-15", + "open": 453.299988, + "high": 457.26001, + "low": 451.429993, + "close": 453.959991, + "adjClose": 453.959991, + "volume": 14429400 + }, + { + "date": "2024-07-16", + "open": 454.220001, + "high": 454.299988, + "low": 446.660004, + "close": 449.519989, + "adjClose": 449.519989, + "volume": 17175700 + }, + { + "date": "2024-07-17", + "open": 442.589996, + "high": 444.850006, + "low": 439.179993, + "close": 443.519989, + "adjClose": 443.519989, + "volume": 21778000 + }, + { + "date": "2024-07-18", + "open": 444.339996, + "high": 444.649994, + "low": 434.399994, + "close": 440.369995, + "adjClose": 440.369995, + "volume": 20794800 + }, + { + "date": "2024-07-19", + "open": 433.100006, + "high": 441.140015, + "low": 432.0, + "close": 437.109985, + "adjClose": 437.109985, + "volume": 20940400 + }, + { + "date": "2024-07-22", + "open": 441.790009, + "high": 444.600006, + "low": 438.910004, + "close": 442.940002, + "adjClose": 442.940002, + "volume": 15784700 + } +] diff --git a/docs/data/charts/getting-started/Combining.js b/docs/data/charts/getting-started/Combining.js index 1b1b3cb9e714d..5e7c31d42b950 100644 --- a/docs/data/charts/getting-started/Combining.js +++ b/docs/data/charts/getting-started/Combining.js @@ -1,62 +1,111 @@ import * as React from 'react'; +import Typography from '@mui/material/Typography'; import { BarPlot } from '@mui/x-charts/BarChart'; -import { LinePlot } from '@mui/x-charts/LineChart'; -import { ChartContainer } from '@mui/x-charts/ChartContainer'; +import { LineHighlightPlot, LinePlot } from '@mui/x-charts/LineChart'; +import { ResponsiveChartContainer } from '@mui/x-charts/ResponsiveChartContainer'; import { ChartsXAxis } from '@mui/x-charts/ChartsXAxis'; import { ChartsYAxis } from '@mui/x-charts/ChartsYAxis'; +import { ChartsTooltip } from '@mui/x-charts/ChartsTooltip'; +import { ChartsAxisHighlight } from '@mui/x-charts/ChartsAxisHighlight'; +import { axisClasses } from '@mui/x-charts/ChartsAxis'; +import alphabetStock from '../dataset/GOOGL.json'; const series = [ { type: 'bar', - stack: '', - yAxisId: 'eco', - data: [2, 5, 3, 4, 1], + yAxisId: 'volume', + label: 'Volume', + color: 'lightgray', + data: alphabetStock.map((day) => day.volume), + highlightScope: { highlight: 'item' }, }, { - type: 'bar', - stack: '', - yAxisId: 'eco', - data: [5, 6, 2, 8, 9], + type: 'line', + yAxisId: 'price', + color: 'red', + label: 'Low', + data: alphabetStock.map((day) => day.low), + highlightScope: { highlight: 'item' }, }, { type: 'line', - yAxisId: 'pib', - color: 'red', - data: [1000, 1500, 3000, 5000, 10000], + yAxisId: 'price', + color: 'green', + label: 'High', + data: alphabetStock.map((day) => day.high), }, ]; export default function Combining() { return ( - value.toString(), - }, - ]} - yAxis={[ - { - id: 'eco', - scaleType: 'linear', - }, - { - id: 'pib', - scaleType: 'log', - }, - ]} - > - - - - - - +
+ Alphabet stocks +
+ new Date(day.date)), + scaleType: 'band', + valueFormatter: (value) => value.toLocaleDateString(), + }, + ]} + yAxis={[ + { + id: 'price', + scaleType: 'linear', + }, + { + id: 'volume', + scaleType: 'linear', + valueFormatter: (value) => `${(value / 1000000).toLocaleString()}M`, + }, + ]} + > + + + + + { + return index % 30 === 0; + }} + tickLabelStyle={{ + fontSize: 10, + }} + /> + + + + +
+
); } diff --git a/docs/data/charts/getting-started/Combining.tsx b/docs/data/charts/getting-started/Combining.tsx index cc183c177ca61..fb21e60c73a79 100644 --- a/docs/data/charts/getting-started/Combining.tsx +++ b/docs/data/charts/getting-started/Combining.tsx @@ -1,62 +1,112 @@ import * as React from 'react'; +import Typography from '@mui/material/Typography'; import { BarPlot } from '@mui/x-charts/BarChart'; -import { LinePlot } from '@mui/x-charts/LineChart'; -import { ChartContainer } from '@mui/x-charts/ChartContainer'; +import { LineHighlightPlot, LinePlot } from '@mui/x-charts/LineChart'; +import { ResponsiveChartContainer } from '@mui/x-charts/ResponsiveChartContainer'; import { AllSeriesType } from '@mui/x-charts/models'; import { ChartsXAxis } from '@mui/x-charts/ChartsXAxis'; import { ChartsYAxis } from '@mui/x-charts/ChartsYAxis'; +import { ChartsTooltip } from '@mui/x-charts/ChartsTooltip'; +import { ChartsAxisHighlight } from '@mui/x-charts/ChartsAxisHighlight'; +import { axisClasses } from '@mui/x-charts/ChartsAxis'; +import alphabetStock from '../dataset/GOOGL.json'; const series = [ { type: 'bar', - stack: '', - yAxisId: 'eco', - data: [2, 5, 3, 4, 1], + yAxisId: 'volume', + label: 'Volume', + color: 'lightgray', + data: alphabetStock.map((day) => day.volume), + highlightScope: { highlight: 'item' }, }, { - type: 'bar', - stack: '', - yAxisId: 'eco', - data: [5, 6, 2, 8, 9], + type: 'line', + yAxisId: 'price', + color: 'red', + label: 'Low', + data: alphabetStock.map((day) => day.low), + highlightScope: { highlight: 'item' }, }, { type: 'line', - yAxisId: 'pib', - color: 'red', - data: [1000, 1500, 3000, 5000, 10000], + yAxisId: 'price', + color: 'green', + label: 'High', + data: alphabetStock.map((day) => day.high), }, ] as AllSeriesType[]; export default function Combining() { return ( - value.toString(), - }, - ]} - yAxis={[ - { - id: 'eco', - scaleType: 'linear', - }, - { - id: 'pib', - scaleType: 'log', - }, - ]} - > - - - - - - +
+ Alphabet stocks +
+ new Date(day.date)), + scaleType: 'band', + valueFormatter: (value) => value.toLocaleDateString(), + }, + ]} + yAxis={[ + { + id: 'price', + scaleType: 'linear', + }, + { + id: 'volume', + scaleType: 'linear', + valueFormatter: (value) => `${(value / 1000000).toLocaleString()}M`, + }, + ]} + > + + + + + + { + return index % 30 === 0; + }} + tickLabelStyle={{ + fontSize: 10, + }} + /> + + + + +
+
); } diff --git a/docs/data/charts/getting-started/getting-started.md b/docs/data/charts/getting-started/getting-started.md index fbd74b8ecb3d7..6c6057abfc00b 100644 --- a/docs/data/charts/getting-started/getting-started.md +++ b/docs/data/charts/getting-started/getting-started.md @@ -73,18 +73,18 @@ To help folks using CommonJS, the `@mui/x-charts` package uses a vendored packag If you need some D3 functions, you can import them with `@mui/x-charts-vendor/d3-color`. -## Displaying Charts +## Displaying charts A Chart can be rendered in one of two ways: as a single component, or by composing subcomponents. -### Single Charts +### Single charts For common use cases, the single component is the recommended way. Those components' names end with "Chart", as opposed to "Plot", and only require the series prop describing the data to render. {{"demo": "SimpleCharts.js"}} -### Composed Charts +### Composed charts To combine different Charts, like Lines with Bars, you can use composition with the `ChartContainer` wrapper. @@ -118,3 +118,30 @@ Visit the [Axis page](/x/react-charts/axis/) for more details. MUIĀ X Charts follows the MaterialĀ UI styling and features all of the customization tools you'd find there, making tweaking charts as straightforward as designing buttons. Visit the [Styling page](/x/react-charts/styling/) for more details. + +## TypeScript + +In order to benefit from the [CSS overrides](/material-ui/customization/theme-components/#theme-style-overrides) and [default prop customization](/material-ui/customization/theme-components/#theme-default-props) with the theme, TypeScript users need to import the following types. +Internally, it uses module augmentation to extend the default theme structure. + +```tsx +import type {} from '@mui/x-charts/themeAugmentation'; +import type {} from '@mui/x-charts-pro/themeAugmentation'; + +const theme = createTheme({ + components: { + MuiChartsAxis: { + styleOverrides: { + tick: { + stroke: '#006BD6', + }, + }, + }, + }, +}); +``` + +:::info +You don't have to import the theme augmentation from both `@mui/x-charts` and `@mui/x-charts-pro` when using `@mui/x-charts-pro`. +Importing it from `@mui/x-charts-pro` is enough. +::: diff --git a/docs/data/charts/lines/AreaBaseline.js b/docs/data/charts/lines/AreaBaseline.js new file mode 100644 index 0000000000000..cbaa2965c4085 --- /dev/null +++ b/docs/data/charts/lines/AreaBaseline.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { LineChart } from '@mui/x-charts/LineChart'; + +export default function AreaBaseline() { + return ( + + ); +} diff --git a/docs/data/charts/lines/AreaBaseline.tsx b/docs/data/charts/lines/AreaBaseline.tsx new file mode 100644 index 0000000000000..cbaa2965c4085 --- /dev/null +++ b/docs/data/charts/lines/AreaBaseline.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { LineChart } from '@mui/x-charts/LineChart'; + +export default function AreaBaseline() { + return ( + + ); +} diff --git a/docs/data/charts/lines/AreaBaseline.tsx.preview b/docs/data/charts/lines/AreaBaseline.tsx.preview new file mode 100644 index 0000000000000..b5f56b85ada1c --- /dev/null +++ b/docs/data/charts/lines/AreaBaseline.tsx.preview @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/docs/data/charts/lines/CSSCustomization.js b/docs/data/charts/lines/CSSCustomization.js index 933eaa75f5018..5e5fe62608213 100644 --- a/docs/data/charts/lines/CSSCustomization.js +++ b/docs/data/charts/lines/CSSCustomization.js @@ -1,62 +1,11 @@ import * as React from 'react'; import { LineChart, lineElementClasses } from '@mui/x-charts/LineChart'; - -const years = [ - new Date(1990, 0, 1), - new Date(1991, 0, 1), - new Date(1992, 0, 1), - new Date(1993, 0, 1), - new Date(1994, 0, 1), - new Date(1995, 0, 1), - new Date(1996, 0, 1), - new Date(1997, 0, 1), - new Date(1998, 0, 1), - new Date(1999, 0, 1), - new Date(2000, 0, 1), - new Date(2001, 0, 1), - new Date(2002, 0, 1), - new Date(2003, 0, 1), - new Date(2004, 0, 1), - new Date(2005, 0, 1), - new Date(2006, 0, 1), - new Date(2007, 0, 1), - new Date(2008, 0, 1), - new Date(2009, 0, 1), - new Date(2010, 0, 1), - new Date(2011, 0, 1), - new Date(2012, 0, 1), - new Date(2013, 0, 1), - new Date(2014, 0, 1), - new Date(2015, 0, 1), - new Date(2016, 0, 1), - new Date(2017, 0, 1), - new Date(2018, 0, 1), -]; - -const FranceGDPperCapita = [ - 28129, 28294.264, 28619.805, 28336.16, 28907.977, 29418.863, 29736.645, 30341.807, - 31323.078, 32284.611, 33409.68, 33920.098, 34152.773, 34292.03, 35093.824, - 35495.465, 36166.16, 36845.684, 36761.793, 35534.926, 36086.727, 36691, 36571, - 36632, 36527, 36827, 37124, 37895, 38515.918, -]; - -const UKGDPperCapita = [ - 26189, 25792.014, 25790.186, 26349.342, 27277.543, 27861.215, 28472.248, 29259.764, - 30077.385, 30932.537, 31946.037, 32660.441, 33271.3, 34232.426, 34865.78, - 35623.625, 36214.07, 36816.676, 36264.79, 34402.36, 34754.473, 34971, 35185, 35618, - 36436, 36941, 37334, 37782.83, 38058.086, -]; - -const GermanyGDPperCapita = [ - 25391, 26769.96, 27385.055, 27250.701, 28140.057, 28868.945, 29349.982, 30186.945, - 31129.584, 32087.604, 33367.285, 34260.29, 34590.93, 34716.44, 35528.715, - 36205.574, 38014.137, 39752.207, 40715.434, 38962.938, 41109.582, 43189, 43320, - 43413, 43922, 44293, 44689, 45619.785, 46177.617, -]; +import { dataset } from './GDPperCapita'; export default function CSSCustomization() { return ( date.getFullYear().toString(), }, @@ -77,21 +26,21 @@ export default function CSSCustomization() { series={[ { id: 'France', - data: FranceGDPperCapita, + dataKey: 'fr', stack: 'total', area: true, showMark: false, }, { id: 'Germany', - data: GermanyGDPperCapita, + dataKey: 'dl', stack: 'total', area: true, showMark: false, }, { id: 'United Kingdom', - data: UKGDPperCapita, + dataKey: 'gb', stack: 'total', area: true, showMark: false, diff --git a/docs/data/charts/lines/CSSCustomization.tsx b/docs/data/charts/lines/CSSCustomization.tsx index 933eaa75f5018..5e5fe62608213 100644 --- a/docs/data/charts/lines/CSSCustomization.tsx +++ b/docs/data/charts/lines/CSSCustomization.tsx @@ -1,62 +1,11 @@ import * as React from 'react'; import { LineChart, lineElementClasses } from '@mui/x-charts/LineChart'; - -const years = [ - new Date(1990, 0, 1), - new Date(1991, 0, 1), - new Date(1992, 0, 1), - new Date(1993, 0, 1), - new Date(1994, 0, 1), - new Date(1995, 0, 1), - new Date(1996, 0, 1), - new Date(1997, 0, 1), - new Date(1998, 0, 1), - new Date(1999, 0, 1), - new Date(2000, 0, 1), - new Date(2001, 0, 1), - new Date(2002, 0, 1), - new Date(2003, 0, 1), - new Date(2004, 0, 1), - new Date(2005, 0, 1), - new Date(2006, 0, 1), - new Date(2007, 0, 1), - new Date(2008, 0, 1), - new Date(2009, 0, 1), - new Date(2010, 0, 1), - new Date(2011, 0, 1), - new Date(2012, 0, 1), - new Date(2013, 0, 1), - new Date(2014, 0, 1), - new Date(2015, 0, 1), - new Date(2016, 0, 1), - new Date(2017, 0, 1), - new Date(2018, 0, 1), -]; - -const FranceGDPperCapita = [ - 28129, 28294.264, 28619.805, 28336.16, 28907.977, 29418.863, 29736.645, 30341.807, - 31323.078, 32284.611, 33409.68, 33920.098, 34152.773, 34292.03, 35093.824, - 35495.465, 36166.16, 36845.684, 36761.793, 35534.926, 36086.727, 36691, 36571, - 36632, 36527, 36827, 37124, 37895, 38515.918, -]; - -const UKGDPperCapita = [ - 26189, 25792.014, 25790.186, 26349.342, 27277.543, 27861.215, 28472.248, 29259.764, - 30077.385, 30932.537, 31946.037, 32660.441, 33271.3, 34232.426, 34865.78, - 35623.625, 36214.07, 36816.676, 36264.79, 34402.36, 34754.473, 34971, 35185, 35618, - 36436, 36941, 37334, 37782.83, 38058.086, -]; - -const GermanyGDPperCapita = [ - 25391, 26769.96, 27385.055, 27250.701, 28140.057, 28868.945, 29349.982, 30186.945, - 31129.584, 32087.604, 33367.285, 34260.29, 34590.93, 34716.44, 35528.715, - 36205.574, 38014.137, 39752.207, 40715.434, 38962.938, 41109.582, 43189, 43320, - 43413, 43922, 44293, 44689, 45619.785, 46177.617, -]; +import { dataset } from './GDPperCapita'; export default function CSSCustomization() { return ( date.getFullYear().toString(), }, @@ -77,21 +26,21 @@ export default function CSSCustomization() { series={[ { id: 'France', - data: FranceGDPperCapita, + dataKey: 'fr', stack: 'total', area: true, showMark: false, }, { id: 'Germany', - data: GermanyGDPperCapita, + dataKey: 'dl', stack: 'total', area: true, showMark: false, }, { id: 'United Kingdom', - data: UKGDPperCapita, + dataKey: 'gb', stack: 'total', area: true, showMark: false, diff --git a/docs/data/charts/lines/GDPperCapita.ts b/docs/data/charts/lines/GDPperCapita.ts new file mode 100644 index 0000000000000..eb5babd81d4dc --- /dev/null +++ b/docs/data/charts/lines/GDPperCapita.ts @@ -0,0 +1,31 @@ +export const dataset = [ + { date: new Date(1990, 0, 1), fr: 28129, gb: 26189, dl: 25391 }, + { date: new Date(1991, 0, 1), fr: 28294.264, gb: 25792.014, dl: 26769.96 }, + { date: new Date(1992, 0, 1), fr: 28619.805, gb: 25790.186, dl: 27385.055 }, + { date: new Date(1993, 0, 1), fr: 28336.16, gb: 26349.342, dl: 27250.701 }, + { date: new Date(1994, 0, 1), fr: 28907.977, gb: 27277.543, dl: 28140.057 }, + { date: new Date(1995, 0, 1), fr: 29418.863, gb: 27861.215, dl: 28868.945 }, + { date: new Date(1996, 0, 1), fr: 29736.645, gb: 28472.248, dl: 29349.982 }, + { date: new Date(1997, 0, 1), fr: 30341.807, gb: 29259.764, dl: 30186.945 }, + { date: new Date(1998, 0, 1), fr: 31323.078, gb: 30077.385, dl: 31129.584 }, + { date: new Date(1999, 0, 1), fr: 32284.611, gb: 30932.537, dl: 32087.604 }, + { date: new Date(2000, 0, 1), fr: 33409.68, gb: 31946.037, dl: 33367.285 }, + { date: new Date(2001, 0, 1), fr: 33920.098, gb: 32660.441, dl: 34260.29 }, + { date: new Date(2002, 0, 1), fr: 34152.773, gb: 33271.3, dl: 34590.93 }, + { date: new Date(2003, 0, 1), fr: 34292.03, gb: 34232.426, dl: 34716.44 }, + { date: new Date(2004, 0, 1), fr: 35093.824, gb: 34865.78, dl: 35528.715 }, + { date: new Date(2005, 0, 1), fr: 35495.465, gb: 35623.625, dl: 36205.574 }, + { date: new Date(2006, 0, 1), fr: 36166.16, gb: 36214.07, dl: 38014.137 }, + { date: new Date(2007, 0, 1), fr: 36845.684, gb: 36816.676, dl: 39752.207 }, + { date: new Date(2008, 0, 1), fr: 36761.793, gb: 36264.79, dl: 40715.434 }, + { date: new Date(2009, 0, 1), fr: 35534.926, gb: 34402.36, dl: 38962.938 }, + { date: new Date(2010, 0, 1), fr: 36086.727, gb: 34754.473, dl: 41109.582 }, + { date: new Date(2011, 0, 1), fr: 36691, gb: 34971, dl: 43189 }, + { date: new Date(2012, 0, 1), fr: 36571, gb: 35185, dl: 43320 }, + { date: new Date(2013, 0, 1), fr: 36632, gb: 35618, dl: 43413 }, + { date: new Date(2014, 0, 1), fr: 36527, gb: 36436, dl: 43922 }, + { date: new Date(2015, 0, 1), fr: 36827, gb: 36941, dl: 44293 }, + { date: new Date(2016, 0, 1), fr: 37124, gb: 37334, dl: 44689 }, + { date: new Date(2017, 0, 1), fr: 37895, gb: 37782.83, dl: 45619.785 }, + { date: new Date(2018, 0, 1), fr: 38515.918, gb: 38058.086, dl: 46177.617 }, +]; diff --git a/docs/data/charts/lines/GridDemo.js b/docs/data/charts/lines/GridDemo.js index 0f5641f5b2a90..a025d5c3cbffc 100644 --- a/docs/data/charts/lines/GridDemo.js +++ b/docs/data/charts/lines/GridDemo.js @@ -1,15 +1,13 @@ import * as React from 'react'; import { LineChart } from '@mui/x-charts/LineChart'; +import { dataset } from './basicDataset'; export default function GridDemo() { return ( date.getFullYear().toString(), }, @@ -69,7 +18,7 @@ export default function StackedAreas() { { id: 'France', label: 'French GDP per capita', - data: FranceGDPperCapita, + dataKey: 'fr', stack: 'total', area: true, showMark: false, @@ -77,7 +26,7 @@ export default function StackedAreas() { { id: 'Germany', label: 'German GDP per capita', - data: GermanyGDPperCapita, + dataKey: 'dl', stack: 'total', area: true, showMark: false, @@ -85,7 +34,7 @@ export default function StackedAreas() { { id: 'United Kingdom', label: 'UK GDP per capita', - data: UKGDPperCapita, + dataKey: 'gb', stack: 'total', area: true, showMark: false, diff --git a/docs/data/charts/lines/StackedAreas.tsx b/docs/data/charts/lines/StackedAreas.tsx index 5a895f8a4b44d..79181db86caa1 100644 --- a/docs/data/charts/lines/StackedAreas.tsx +++ b/docs/data/charts/lines/StackedAreas.tsx @@ -1,66 +1,15 @@ import * as React from 'react'; import { LineChart } from '@mui/x-charts/LineChart'; - -const years = [ - new Date(1990, 0, 1), - new Date(1991, 0, 1), - new Date(1992, 0, 1), - new Date(1993, 0, 1), - new Date(1994, 0, 1), - new Date(1995, 0, 1), - new Date(1996, 0, 1), - new Date(1997, 0, 1), - new Date(1998, 0, 1), - new Date(1999, 0, 1), - new Date(2000, 0, 1), - new Date(2001, 0, 1), - new Date(2002, 0, 1), - new Date(2003, 0, 1), - new Date(2004, 0, 1), - new Date(2005, 0, 1), - new Date(2006, 0, 1), - new Date(2007, 0, 1), - new Date(2008, 0, 1), - new Date(2009, 0, 1), - new Date(2010, 0, 1), - new Date(2011, 0, 1), - new Date(2012, 0, 1), - new Date(2013, 0, 1), - new Date(2014, 0, 1), - new Date(2015, 0, 1), - new Date(2016, 0, 1), - new Date(2017, 0, 1), - new Date(2018, 0, 1), -]; - -const FranceGDPperCapita = [ - 28129, 28294.264, 28619.805, 28336.16, 28907.977, 29418.863, 29736.645, 30341.807, - 31323.078, 32284.611, 33409.68, 33920.098, 34152.773, 34292.03, 35093.824, - 35495.465, 36166.16, 36845.684, 36761.793, 35534.926, 36086.727, 36691, 36571, - 36632, 36527, 36827, 37124, 37895, 38515.918, -]; - -const UKGDPperCapita = [ - 26189, 25792.014, 25790.186, 26349.342, 27277.543, 27861.215, 28472.248, 29259.764, - 30077.385, 30932.537, 31946.037, 32660.441, 33271.3, 34232.426, 34865.78, - 35623.625, 36214.07, 36816.676, 36264.79, 34402.36, 34754.473, 34971, 35185, 35618, - 36436, 36941, 37334, 37782.83, 38058.086, -]; - -const GermanyGDPperCapita = [ - 25391, 26769.96, 27385.055, 27250.701, 28140.057, 28868.945, 29349.982, 30186.945, - 31129.584, 32087.604, 33367.285, 34260.29, 34590.93, 34716.44, 35528.715, - 36205.574, 38014.137, 39752.207, 40715.434, 38962.938, 41109.582, 43189, 43320, - 43413, 43922, 44293, 44689, 45619.785, 46177.617, -]; +import { dataset } from './GDPperCapita'; export default function StackedAreas() { return ( date.getFullYear().toString(), }, @@ -69,7 +18,7 @@ export default function StackedAreas() { { id: 'France', label: 'French GDP per capita', - data: FranceGDPperCapita, + dataKey: 'fr', stack: 'total', area: true, showMark: false, @@ -77,7 +26,7 @@ export default function StackedAreas() { { id: 'Germany', label: 'German GDP per capita', - data: GermanyGDPperCapita, + dataKey: 'dl', stack: 'total', area: true, showMark: false, @@ -85,7 +34,7 @@ export default function StackedAreas() { { id: 'United Kingdom', label: 'UK GDP per capita', - data: UKGDPperCapita, + dataKey: 'gb', stack: 'total', area: true, showMark: false, diff --git a/docs/data/charts/lines/basicDataset.ts b/docs/data/charts/lines/basicDataset.ts new file mode 100644 index 0000000000000..ef59c6d6d807e --- /dev/null +++ b/docs/data/charts/lines/basicDataset.ts @@ -0,0 +1,8 @@ +export const dataset = [ + { x: 1, y: 2 }, + { x: 2, y: 5.5 }, + { x: 3, y: 2 }, + { x: 5, y: 8.5 }, + { x: 8, y: 1.5 }, + { x: 10, y: 5 }, +]; diff --git a/docs/data/charts/lines/lines.md b/docs/data/charts/lines/lines.md index 0ad25ce2f3971..be651d4dd57a7 100644 --- a/docs/data/charts/lines/lines.md +++ b/docs/data/charts/lines/lines.md @@ -175,6 +175,20 @@ Different series could even have different interpolations. {{"demo": "InterpolationDemoNoSnap.js", "hideToolbar": true}} +### Baseline + +The area chart draws a `baseline` on the Y axis `0`. +This is useful as a base value, but customized visualizations may require a different baseline. + +To get the area filling the space above or below the line, set `baseline` to `"min"` or `"max"`. +It is also possible to provide a `number` value to fix the baseline at the desired position. + +:::warning +The `baseline` should not be used with stacked areas, as it will not work as expected. +::: + +{{"demo": "AreaBaseline.js"}} + ### Optimization To show mark elements, use `showMark` series property. diff --git a/docs/data/charts/lines/worldElectricityProduction.ts b/docs/data/charts/lines/worldElectricityProduction.ts new file mode 100644 index 0000000000000..94748a611938a --- /dev/null +++ b/docs/data/charts/lines/worldElectricityProduction.ts @@ -0,0 +1,520 @@ +export const keyToLabel: { [key: string]: string } = { + coal: 'Electricity from coal (TWh)', + gas: 'Electricity from gas (TWh)', + oil: 'Electricity from oil (TWh)', + nuclear: 'Electricity from nuclear (TWh)', + hydro: 'Electricity from hydro (TWh)', + wind: 'Electricity from wind (TWh)', + solar: 'Electricity from solar (TWh)', + bio: 'Electricity from bioenergy (TWh)', + other: 'Other renewables excluding bioenergy (TWh)', +}; + +export const colors: { [key: string]: string } = { + other: 'lightgray', + bio: 'lightgreen', + solar: 'yellow', + wind: 'lightblue', + hydro: 'blue', + nuclear: 'orange', + oil: 'darkgrey', + gas: 'gray', + coal: 'black', +}; + +export const worldElectricityProduction = [ + { + country: 'World', + year: 1985, + other: 0, + bio: 0, + solar: 0.011747475, + wind: 0.064220205, + hydro: 1979.2446, + nuclear: 1488.9216, + oil: 1110.7847, + gas: 1426.3086, + coal: 3748.3848, + }, + { + country: 'World', + year: 1986, + other: 0, + bio: 0, + solar: 0.015183838, + wind: 0.13883132, + hydro: 2006.0651, + nuclear: 1594.7357, + oil: 1168.3097, + gas: 1432.6683, + coal: 3839.0095, + }, + { + country: 'World', + year: 1987, + other: 0, + bio: 0, + solar: 0.01060303, + wind: 0.19537677, + hydro: 2033.1884, + nuclear: 1734.7332, + oil: 1183.1947, + gas: 1516.4941, + coal: 4058.0767, + }, + { + country: 'World', + year: 1988, + other: 0, + bio: 0, + solar: 0.01019596, + wind: 0.3315798, + hydro: 2098.3518, + nuclear: 1891.2493, + oil: 1256.5684, + gas: 1540.9414, + coal: 4200.6743, + }, + { + country: 'World', + year: 1989, + other: 0, + bio: 0, + solar: 0.26222324, + wind: 2.6497767, + hydro: 2087.588, + nuclear: 1945.0106, + oil: 1350.2358, + gas: 1728.5189, + coal: 4376.982, + }, + { + country: 'World', + year: 1990, + other: 0, + bio: 0, + solar: 0.38834995, + wind: 3.6324706, + hydro: 2158.854, + nuclear: 2000.596, + oil: 1364.6844, + gas: 1789.7031, + coal: 4460.2417, + }, + { + country: 'World', + year: 1991, + other: 0, + bio: 0, + solar: 0.5053229, + wind: 4.086107, + hydro: 2208.702, + nuclear: 2096.3098, + oil: 1349.9071, + gas: 1815.2444, + coal: 4557.0664, + }, + { + country: 'World', + year: 1992, + other: 0, + bio: 0, + solar: 0.4666791, + wind: 4.732812, + hydro: 2208.4592, + nuclear: 2112.223, + oil: 1328.2163, + gas: 1829.3868, + coal: 4649.9165, + }, + { + country: 'World', + year: 1993, + other: 0, + bio: 0, + solar: 0.5566775, + wind: 5.704169, + hydro: 2341.4597, + nuclear: 2184.9646, + oil: 1266.6155, + gas: 1863.8153, + coal: 4727.899, + }, + { + country: 'World', + year: 1994, + other: 0, + bio: 0, + solar: 0.5969829, + wind: 7.13173, + hydro: 2356.203, + nuclear: 2225.9788, + oil: 1302.1187, + gas: 1925.1002, + coal: 4891.904, + }, + { + country: 'World', + year: 1995, + other: 0, + bio: 0, + solar: 0.63888276, + wind: 8.272123, + hydro: 2483.6868, + nuclear: 2322.5298, + oil: 1259.9452, + gas: 2036.3821, + coal: 5038.9316, + }, + { + country: 'World', + year: 1996, + other: 0, + bio: 0, + solar: 0.69922996, + wind: 9.215601, + hydro: 2517.03, + nuclear: 2406.615, + oil: 1245.6957, + gas: 2101.594, + coal: 5279.661, + }, + { + country: 'World', + year: 1997, + other: 0, + bio: 0, + solar: 0.7496558, + wind: 12.028216, + hydro: 2561.359, + nuclear: 2390.0642, + oil: 1244.647, + gas: 2271.0615, + coal: 5395.626, + }, + { + country: 'World', + year: 1998, + other: 0, + bio: 0, + solar: 0.811852, + wind: 15.92926, + hydro: 2581.1204, + nuclear: 2431.1948, + oil: 1294.6146, + gas: 2408.5476, + coal: 5511.2935, + }, + { + country: 'World', + year: 1999, + other: 0, + bio: 0, + solar: 0.9052879, + wind: 21.226898, + hydro: 2600.65, + nuclear: 2523.7056, + oil: 1266.6599, + gas: 2600.75, + coal: 5630.859, + }, + { + country: 'World', + year: 2000, + other: 52.37, + bio: 148.65, + solar: 1.08, + wind: 31.16, + hydro: 2621.36, + nuclear: 2507.43, + oil: 1209.51, + gas: 2681.11, + coal: 5719.12, + }, + { + country: 'World', + year: 2001, + other: 52.6, + bio: 143.1, + solar: 1.35, + wind: 38.16, + hydro: 2561.04, + nuclear: 2573.71, + oil: 1197.6, + gas: 2827.65, + coal: 5801.14, + }, + { + country: 'World', + year: 2002, + other: 54.08, + bio: 156.61, + solar: 1.69, + wind: 52.04, + hydro: 2601.39, + nuclear: 2601.89, + oil: 1175.58, + gas: 3033.78, + coal: 6056.12, + }, + { + country: 'World', + year: 2003, + other: 56.07, + bio: 167.91, + solar: 2.07, + wind: 63.43, + hydro: 2602.33, + nuclear: 2577.71, + oil: 1198.2, + gas: 3165.78, + coal: 6458.61, + }, + { + country: 'World', + year: 2004, + other: 57.94, + bio: 184.54, + solar: 2.71, + wind: 85.26, + hydro: 2796.69, + nuclear: 2682.73, + oil: 1177.47, + gas: 3408.19, + coal: 6697.61, + }, + { + country: 'World', + year: 2005, + other: 58.74, + bio: 208.44, + solar: 3.78, + wind: 103.89, + hydro: 2895.17, + nuclear: 2686.95, + oil: 1186.13, + gas: 3579.99, + coal: 7040.85, + }, + { + country: 'World', + year: 2006, + other: 60.11, + bio: 220.96, + solar: 5.11, + wind: 132.79, + hydro: 3001.53, + nuclear: 2721.42, + oil: 1097.06, + gas: 3792.38, + coal: 7439.88, + }, + { + country: 'World', + year: 2007, + other: 62.87, + bio: 243.14, + solar: 6.92, + wind: 170.91, + hydro: 3046.18, + nuclear: 2666.92, + oil: 1119.39, + gas: 4109.47, + coal: 7931.82, + }, + { + country: 'World', + year: 2008, + other: 65.97, + bio: 258.44, + solar: 11.36, + wind: 220.07, + hydro: 3231.07, + nuclear: 2656.04, + oil: 1078.99, + gas: 4210.51, + coal: 7927.59, + }, + { + country: 'World', + year: 2009, + other: 68.02, + bio: 279.55, + solar: 19.19, + wind: 275.88, + hydro: 3229.55, + nuclear: 2619, + oil: 1005.12, + gas: 4247.72, + coal: 7817.32, + }, + { + country: 'World', + year: 2010, + other: 68.38, + bio: 322.22, + solar: 31.05, + wind: 345.99, + hydro: 3412.33, + nuclear: 2686.44, + oil: 1011.78, + gas: 4701.27, + coal: 8358.6, + }, + { + country: 'World', + year: 2011, + other: 69.67, + bio: 342.44, + solar: 61.85, + wind: 440.39, + hydro: 3479.25, + nuclear: 2575.35, + oil: 1103.87, + gas: 4767.24, + coal: 8814.17, + }, + { + country: 'World', + year: 2012, + other: 70.88, + bio: 370.87, + solar: 95.18, + wind: 529.11, + hydro: 3645.02, + nuclear: 2403.21, + oil: 1157.13, + gas: 5042.66, + coal: 8855.83, + }, + { + country: 'World', + year: 2013, + other: 72.58, + bio: 402.92, + solar: 131.42, + wind: 641.17, + hydro: 3776.88, + nuclear: 2418.44, + oil: 1118.61, + gas: 4939.52, + coal: 9306.75, + }, + { + country: 'World', + year: 2014, + other: 77.68, + bio: 438.47, + solar: 196.46, + wind: 716.51, + hydro: 3865.63, + nuclear: 2472.7, + oil: 1063.74, + gas: 5096.07, + coal: 9495.57, + }, + { + country: 'World', + year: 2015, + other: 81.57, + bio: 475.79, + solar: 254.87, + wind: 828.37, + hydro: 3870.04, + nuclear: 2502.45, + oil: 1068.09, + gas: 5418.55, + coal: 9160.63, + }, + { + country: 'World', + year: 2016, + other: 83.91, + bio: 483.77, + solar: 329.15, + wind: 959.65, + hydro: 3996.54, + nuclear: 2540.48, + oil: 1004.96, + gas: 5669.08, + coal: 9226.85, + }, + { + country: 'World', + year: 2017, + other: 86.39, + bio: 515.07, + solar: 444.54, + wind: 1136.41, + hydro: 4053.96, + nuclear: 2566.22, + oil: 913.07, + gas: 5791.83, + coal: 9518.91, + }, + { + country: 'World', + year: 2018, + other: 89.54, + bio: 546.21, + solar: 570.57, + wind: 1265.29, + hydro: 4174.81, + nuclear: 2619.57, + oil: 841.34, + gas: 6015.24, + coal: 9899.44, + }, + { + country: 'World', + year: 2019, + other: 91.15, + bio: 575.5, + solar: 701.19, + wind: 1419.51, + hydro: 4220.11, + nuclear: 2724.08, + oil: 776.78, + gas: 6176.34, + coal: 9680.92, + }, + { + country: 'World', + year: 2020, + other: 94.16, + bio: 602.57, + solar: 850.89, + wind: 1587.13, + hydro: 4341.1, + nuclear: 2634.69, + oil: 741, + gas: 6132.47, + coal: 9292.9, + }, + { + country: 'World', + year: 2021, + other: 95, + bio: 663.78, + solar: 1040.06, + wind: 1849.4, + hydro: 4244.57, + nuclear: 2740.78, + oil: 793.53, + gas: 6326, + coal: 10081.8, + }, + { + country: 'World', + year: 2022, + other: 99.74, + bio: 677.57, + solar: 1289.27, + wind: 2139.23, + hydro: 4326.76, + nuclear: 2610.04, + oil: 884.98, + gas: 6309.46, + coal: 10190.71, + }, +]; diff --git a/docs/data/charts/pie-demo/OnSeriesItemClick.js b/docs/data/charts/pie-demo/OnSeriesItemClick.js index fadc705a390c2..b6a09e0c31855 100644 --- a/docs/data/charts/pie-demo/OnSeriesItemClick.js +++ b/docs/data/charts/pie-demo/OnSeriesItemClick.js @@ -1,7 +1,8 @@ import * as React from 'react'; import { PieChart } from '@mui/x-charts/PieChart'; -import { Typography, Stack } from '@mui/material'; +import Typography from '@mui/material/Typography'; +import Stack from '@mui/material/Stack'; const items = [ { value: 10, label: 'Series A ( no Id )' }, diff --git a/docs/data/charts/pie-demo/OnSeriesItemClick.tsx b/docs/data/charts/pie-demo/OnSeriesItemClick.tsx index 2a3bb8c592935..297b548ee4a50 100644 --- a/docs/data/charts/pie-demo/OnSeriesItemClick.tsx +++ b/docs/data/charts/pie-demo/OnSeriesItemClick.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import { PieChart } from '@mui/x-charts/PieChart'; import { PieItemIdentifier, DefaultizedPieValueType } from '@mui/x-charts/models'; -import { Typography, Stack } from '@mui/material'; +import Typography from '@mui/material/Typography'; +import Stack from '@mui/material/Stack'; const items = [ { value: 10, label: 'Series A ( no Id )' }, diff --git a/docs/data/charts/pie/PieActiveArc.js b/docs/data/charts/pie/PieActiveArc.js index 4c967f452fc0a..df49ae3bd67f1 100644 --- a/docs/data/charts/pie/PieActiveArc.js +++ b/docs/data/charts/pie/PieActiveArc.js @@ -13,7 +13,7 @@ export default function PieActiveArc() { series={[ { data, - highlightScope: { faded: 'global', highlighted: 'item' }, + highlightScope: { fade: 'global', highlight: 'item' }, faded: { innerRadius: 30, additionalRadius: -30, color: 'gray' }, }, ]} diff --git a/docs/data/charts/pie/PieActiveArc.tsx b/docs/data/charts/pie/PieActiveArc.tsx index 4c967f452fc0a..df49ae3bd67f1 100644 --- a/docs/data/charts/pie/PieActiveArc.tsx +++ b/docs/data/charts/pie/PieActiveArc.tsx @@ -13,7 +13,7 @@ export default function PieActiveArc() { series={[ { data, - highlightScope: { faded: 'global', highlighted: 'item' }, + highlightScope: { fade: 'global', highlight: 'item' }, faded: { innerRadius: 30, additionalRadius: -30, color: 'gray' }, }, ]} diff --git a/docs/data/charts/pie/PieActiveArc.tsx.preview b/docs/data/charts/pie/PieActiveArc.tsx.preview index 23a3a41ec8384..ef820474ebef5 100644 --- a/docs/data/charts/pie/PieActiveArc.tsx.preview +++ b/docs/data/charts/pie/PieActiveArc.tsx.preview @@ -2,7 +2,7 @@ series={[ { data, - highlightScope: { faded: 'global', highlighted: 'item' }, + highlightScope: { fade: 'global', highlight: 'item' }, faded: { innerRadius: 30, additionalRadius: -30, color: 'gray' }, }, ]} diff --git a/docs/data/charts/scatter/ScatterClickNoSnap.js b/docs/data/charts/scatter/ScatterClickNoSnap.js index 546306d7c5088..a4ccaee0a4e49 100644 --- a/docs/data/charts/scatter/ScatterClickNoSnap.js +++ b/docs/data/charts/scatter/ScatterClickNoSnap.js @@ -27,7 +27,7 @@ const scatterChartsParams = { ], label: 'A', highlightScope: { - highlighted: 'item', + highlight: 'item', }, }, { @@ -46,7 +46,7 @@ const scatterChartsParams = { ], label: 'B', highlightScope: { - highlighted: 'item', + highlight: 'item', }, }, ], diff --git a/docs/data/charts/scatter/VoronoiInteraction.js b/docs/data/charts/scatter/VoronoiInteraction.js index a5b3b119144f0..450b16d5d166f 100644 --- a/docs/data/charts/scatter/VoronoiInteraction.js +++ b/docs/data/charts/scatter/VoronoiInteraction.js @@ -1,11 +1,9 @@ import * as React from 'react'; -import { - Stack, - FormControlLabel, - Checkbox, - Typography, - Slider, -} from '@mui/material'; +import Stack from '@mui/material/Stack'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import Typography from '@mui/material/Typography'; +import Slider from '@mui/material/Slider'; import { ScatterChart } from '@mui/x-charts/ScatterChart'; const data = [ diff --git a/docs/data/charts/scatter/VoronoiInteraction.tsx b/docs/data/charts/scatter/VoronoiInteraction.tsx index 0e3f37c196a14..a654f22a66de3 100644 --- a/docs/data/charts/scatter/VoronoiInteraction.tsx +++ b/docs/data/charts/scatter/VoronoiInteraction.tsx @@ -1,11 +1,9 @@ import * as React from 'react'; -import { - Stack, - FormControlLabel, - Checkbox, - Typography, - Slider, -} from '@mui/material'; +import Stack from '@mui/material/Stack'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import Typography from '@mui/material/Typography'; +import Slider from '@mui/material/Slider'; import { ScatterChart } from '@mui/x-charts/ScatterChart'; const data = [ diff --git a/docs/data/charts/styling/SxStyling.js b/docs/data/charts/styling/SxStyling.js index 158f933f9ff06..976ff5e87ba2c 100644 --- a/docs/data/charts/styling/SxStyling.js +++ b/docs/data/charts/styling/SxStyling.js @@ -30,10 +30,16 @@ export default function SxStyling() { fill: '#006BD6', }, }, - border: `1px solid rgba(${theme.palette.mode === 'dark' ? '255,255,255' : '0, 0, 0'}, 0.1)`, - backgroundImage: `linear-gradient(rgba(${theme.palette.mode === 'dark' ? '255,255,255' : '0, 0, 0'}, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(${theme.palette.mode === 'dark' ? '255,255,255' : '0, 0, 0'}, 0.1) 1px, transparent 1px)`, + border: '1px solid rgba(0, 0, 0, 0.1)', + backgroundImage: + 'linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)', backgroundSize: '35px 35px', backgroundPosition: '20px 20px, 20px 20px', + ...theme.applyStyles('dark', { + borderColor: 'rgba(255,255,255, 0.1)', + backgroundImage: + 'linear-gradient(rgba(255,255,255, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255, 0.1) 1px, transparent 1px)', + }), })} xAxis={[{ scaleType: 'band', data: labels }]} series={[ diff --git a/docs/data/charts/styling/SxStyling.tsx b/docs/data/charts/styling/SxStyling.tsx index 57758d5ab7b94..67eee1e60db8a 100644 --- a/docs/data/charts/styling/SxStyling.tsx +++ b/docs/data/charts/styling/SxStyling.tsx @@ -30,11 +30,16 @@ export default function SxStyling(): React.JSX.Element { fill: '#006BD6', }, }, - - border: `1px solid rgba(${theme.palette.mode === 'dark' ? '255,255,255' : '0, 0, 0'}, 0.1)`, - backgroundImage: `linear-gradient(rgba(${theme.palette.mode === 'dark' ? '255,255,255' : '0, 0, 0'}, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(${theme.palette.mode === 'dark' ? '255,255,255' : '0, 0, 0'}, 0.1) 1px, transparent 1px)`, + border: '1px solid rgba(0, 0, 0, 0.1)', + backgroundImage: + 'linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)', backgroundSize: '35px 35px', backgroundPosition: '20px 20px, 20px 20px', + ...theme.applyStyles('dark', { + borderColor: 'rgba(255,255,255, 0.1)', + backgroundImage: + 'linear-gradient(rgba(255,255,255, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255, 0.1) 1px, transparent 1px)', + }), })} xAxis={[{ scaleType: 'band', data: labels }]} series={[ diff --git a/docs/data/charts/tooltip/ElementHighlights.js b/docs/data/charts/tooltip/ElementHighlights.js index e0430b7918716..4ff360e2a58cc 100644 --- a/docs/data/charts/tooltip/ElementHighlights.js +++ b/docs/data/charts/tooltip/ElementHighlights.js @@ -73,13 +73,13 @@ const pieChartsParams = { data: [{ value: 5 }, { value: 10 }, { value: 15 }], label: 'Series 1', outerRadius: 80, - highlighted: { additionalRadius: 10 }, + highlight: { additionalRadius: 10 }, }, { data: [{ value: 5 }, { value: 10 }, { value: 15 }], label: 'Series 1', innerRadius: 90, - highlighted: { additionalRadius: 10 }, + highlight: { additionalRadius: 10 }, }, ], height: 400, @@ -130,7 +130,6 @@ export default function ElementHighlights() { }))} /> )} - {chartType === 'line' && ( )} - {chartType === 'scatter' && ( )} - {chartType === 'pie' && ( + ); +} + +const chartProps = { + width: 600, + height: 300, +}; diff --git a/docs/data/charts/zoom-and-pan/ZoomFilterMode.tsx b/docs/data/charts/zoom-and-pan/ZoomFilterMode.tsx new file mode 100644 index 0000000000000..577aff92be4a7 --- /dev/null +++ b/docs/data/charts/zoom-and-pan/ZoomFilterMode.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { BarChartPro } from '@mui/x-charts-pro/BarChartPro'; +import { dataset, valueFormatter } from './letterFrequency'; + +export default function ZoomFilterMode() { + return ( + + ); +} + +const chartProps = { + width: 600, + height: 300, +}; diff --git a/docs/data/charts/zoom-and-pan/ZoomFilterMode.tsx.preview b/docs/data/charts/zoom-and-pan/ZoomFilterMode.tsx.preview new file mode 100644 index 0000000000000..de5ba8f3d9206 --- /dev/null +++ b/docs/data/charts/zoom-and-pan/ZoomFilterMode.tsx.preview @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/docs/data/charts/zoom-and-pan/letterFrequency.ts b/docs/data/charts/zoom-and-pan/letterFrequency.ts new file mode 100644 index 0000000000000..80aa1fd7d92be --- /dev/null +++ b/docs/data/charts/zoom-and-pan/letterFrequency.ts @@ -0,0 +1,37 @@ +export const letterFrequency = [ + { letter: 'A', frequency: 8.2 }, + { letter: 'B', frequency: 1.5 }, + { letter: 'C', frequency: 2.8 }, + { letter: 'D', frequency: 4.3 }, + { letter: 'E', frequency: 12.7 }, + { letter: 'F', frequency: 2.2 }, + { letter: 'G', frequency: 2.0 }, + { letter: 'H', frequency: 6.1 }, + { letter: 'I', frequency: 7.0 }, + { letter: 'J', frequency: 0.15 }, + { letter: 'K', frequency: 0.77 }, + { letter: 'L', frequency: 4.0 }, + { letter: 'M', frequency: 2.4 }, + { letter: 'N', frequency: 6.7 }, + { letter: 'O', frequency: 7.5 }, + { letter: 'P', frequency: 1.9 }, + { letter: 'Q', frequency: 0.095 }, + { letter: 'R', frequency: 6.0 }, + { letter: 'S', frequency: 6.3 }, + { letter: 'T', frequency: 9.1 }, + { letter: 'U', frequency: 2.8 }, + { letter: 'V', frequency: 0.98 }, + { letter: 'W', frequency: 2.4 }, + { letter: 'X', frequency: 0.15 }, + { letter: 'Y', frequency: 2.0 }, + { letter: 'Z', frequency: 0.074 }, +]; + +export const dataset = letterFrequency.sort((a, b) => b.frequency - a.frequency); + +export function valueFormatter(value: number | null) { + if (value === null) { + return `?`; + } + return `${(value / 100).toLocaleString(undefined, { style: 'percent', maximumFractionDigits: 2 })}`; +} diff --git a/docs/data/charts/zoom-and-pan/zoom-and-pan.md b/docs/data/charts/zoom-and-pan/zoom-and-pan.md index 4c2c5082cc153..fcb8a36704977 100644 --- a/docs/data/charts/zoom-and-pan/zoom-and-pan.md +++ b/docs/data/charts/zoom-and-pan/zoom-and-pan.md @@ -55,3 +55,14 @@ While the `zoom` prop is an array of objects that define the zoom state for each - **end**: The ending percentage of the zoom range. {{"demo": "ZoomControlled.js"}} + +## Zoom filtering + +You can make the zoom of an axis affect one or more axes extremums by setting the `zoom.filterMode` prop on the axis config. + +- If `zoom.filterMode` is set to `"discard"` the data points outside the visible range of this axis are filtered out and the other axes will modify their zoom range to fit the visible ones. +- If `zoom.filterMode` is set to `"keep"` (default) the data points outside the visible range are kept. Then, other axes will not be impacted. + +See how the secondary axis adapts to the visible part of the primary axis in the following example. + +{{"demo": "ZoomFilterMode.js"}} diff --git a/docs/data/data-grid-component-api-pages.ts b/docs/data/data-grid-component-api-pages.ts index b6e11dc6c69b4..5aed416321e15 100644 --- a/docs/data/data-grid-component-api-pages.ts +++ b/docs/data/data-grid-component-api-pages.ts @@ -1,4 +1,4 @@ -import type { MuiPage } from '@mui/monorepo/docs/src/MuiPage'; +import type { MuiPage } from 'docs/src/MuiPage'; const apiPages: MuiPage[] = [ { diff --git a/docs/data/data-grid/cell-selection/CellSelectionRangeStyling.js b/docs/data/data-grid/cell-selection/CellSelectionRangeStyling.js index dfa70d34ebe07..c629a45f3397b 100644 --- a/docs/data/data-grid/cell-selection/CellSelectionRangeStyling.js +++ b/docs/data/data-grid/cell-selection/CellSelectionRangeStyling.js @@ -4,18 +4,20 @@ import { DataGridPremium, gridClasses } from '@mui/x-data-grid-premium'; import { useDemoData } from '@mui/x-data-grid-generator'; const StyledDataGridPremium = styled(DataGridPremium)(({ theme }) => { - const borderColor = - theme.palette.mode === 'light' - ? lighten(alpha(theme.palette.divider, 1), 0.88) - : darken(alpha(theme.palette.divider, 1), 0.68); + const lightBorderColor = lighten(alpha(theme.palette.divider, 1), 0.88); + const darkBorderColor = darken(alpha(theme.palette.divider, 1), 0.68); const selectedCellBorder = alpha(theme.palette.primary.main, 0.5); return { [`& .${gridClasses.cell}`]: { border: `1px solid transparent`, - borderRight: `1px solid ${borderColor}`, - borderBottom: `1px solid ${borderColor}`, + borderRight: `1px solid ${lightBorderColor}`, + borderBottom: `1px solid ${lightBorderColor}`, + ...theme.applyStyles('dark', { + borderRightColor: `${darkBorderColor}`, + borderBottomColor: `${darkBorderColor}`, + }), }, [`& .${gridClasses.cell}.Mui-selected`]: { borderColor: alpha(theme.palette.primary.main, 0.1), diff --git a/docs/data/data-grid/cell-selection/CellSelectionRangeStyling.tsx b/docs/data/data-grid/cell-selection/CellSelectionRangeStyling.tsx index dfa70d34ebe07..c629a45f3397b 100644 --- a/docs/data/data-grid/cell-selection/CellSelectionRangeStyling.tsx +++ b/docs/data/data-grid/cell-selection/CellSelectionRangeStyling.tsx @@ -4,18 +4,20 @@ import { DataGridPremium, gridClasses } from '@mui/x-data-grid-premium'; import { useDemoData } from '@mui/x-data-grid-generator'; const StyledDataGridPremium = styled(DataGridPremium)(({ theme }) => { - const borderColor = - theme.palette.mode === 'light' - ? lighten(alpha(theme.palette.divider, 1), 0.88) - : darken(alpha(theme.palette.divider, 1), 0.68); + const lightBorderColor = lighten(alpha(theme.palette.divider, 1), 0.88); + const darkBorderColor = darken(alpha(theme.palette.divider, 1), 0.68); const selectedCellBorder = alpha(theme.palette.primary.main, 0.5); return { [`& .${gridClasses.cell}`]: { border: `1px solid transparent`, - borderRight: `1px solid ${borderColor}`, - borderBottom: `1px solid ${borderColor}`, + borderRight: `1px solid ${lightBorderColor}`, + borderBottom: `1px solid ${lightBorderColor}`, + ...theme.applyStyles('dark', { + borderRightColor: `${darkBorderColor}`, + borderBottomColor: `${darkBorderColor}`, + }), }, [`& .${gridClasses.cell}.Mui-selected`]: { borderColor: alpha(theme.palette.primary.main, 0.1), diff --git a/docs/data/data-grid/column-pinning/ColumnPinningDynamicRowHeight.js b/docs/data/data-grid/column-pinning/ColumnPinningDynamicRowHeight.js index 0790f9f5c50e0..30ef6c800feeb 100644 --- a/docs/data/data-grid/column-pinning/ColumnPinningDynamicRowHeight.js +++ b/docs/data/data-grid/column-pinning/ColumnPinningDynamicRowHeight.js @@ -51,7 +51,6 @@ export default function ColumnPinningDynamicRowHeight() { )} - diff --git a/docs/data/data-grid/demo/FullFeaturedDemo.js b/docs/data/data-grid/demo/FullFeaturedDemo.js index 3fc0fb9d27edc..07c9e2689d5f3 100644 --- a/docs/data/data-grid/demo/FullFeaturedDemo.js +++ b/docs/data/data-grid/demo/FullFeaturedDemo.js @@ -15,9 +15,8 @@ import MenuItem from '@mui/material/MenuItem'; import Select from '@mui/material/Select'; const AntDesignStyledDataGridPro = styled(DataGridPro)(({ theme }) => ({ - border: `1px solid ${theme.palette.mode === 'light' ? '#f0f0f0' : '#303030'}`, - color: - theme.palette.mode === 'light' ? 'rgba(0,0,0,.85)' : 'rgba(255,255,255,0.85)', + border: '1px solid #303030', + color: 'rgba(255,255,255,0.85)', fontFamily: [ '-apple-system', 'BlinkMacSystemFont', @@ -33,24 +32,28 @@ const AntDesignStyledDataGridPro = styled(DataGridPro)(({ theme }) => ({ WebkitFontSmoothing: 'auto', letterSpacing: 'normal', '& .MuiDataGrid-columnsContainer': { - backgroundColor: theme.palette.mode === 'light' ? '#fafafa' : '#1d1d1d', + backgroundColor: '#1d1d1d', + ...theme.applyStyles('light', { + backgroundColor: '#fafafa', + }), }, '& .MuiDataGrid-iconSeparator': { display: 'none', }, '& .MuiDataGrid-columnHeader, .MuiDataGrid-cell': { - borderRight: `1px solid ${ - theme.palette.mode === 'light' ? '#f0f0f0' : '#303030' - }`, + borderRight: '1px solid #303030', + ...theme.applyStyles('light', { + borderRightColor: '#f0f0f0', + }), }, '& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell': { - borderBottom: `1px solid ${ - theme.palette.mode === 'light' ? '#f0f0f0' : '#303030' - }`, + borderBottom: '1px solid #303030', + ...theme.applyStyles('light', { + borderBottomColor: '#f0f0f0', + }), }, '& .MuiDataGrid-cell': { - color: - theme.palette.mode === 'light' ? 'rgba(0,0,0,.85)' : 'rgba(255,255,255,0.85)', + color: 'rgba(255,255,255,0.85)', fontFamily: [ '-apple-system', 'BlinkMacSystemFont', @@ -66,26 +69,31 @@ const AntDesignStyledDataGridPro = styled(DataGridPro)(({ theme }) => ({ WebkitFontSmoothing: 'auto', letterSpacing: 'normal', '& .MuiDataGrid-columnsContainer': { - backgroundColor: theme.palette.mode === 'light' ? '#fafafa' : '#1d1d1d', + backgroundColor: '#1d1d1d', + ...theme.applyStyles('light', { + backgroundColor: '#fafafa', + }), }, '& .MuiDataGrid-iconSeparator': { display: 'none', }, '& .MuiDataGrid-colCell, .MuiDataGrid-cell': { - borderRight: `1px solid ${ - theme.palette.mode === 'light' ? '#f0f0f0' : '#303030' - }`, + borderRight: '1px solid #303030', + ...theme.applyStyles('light', { + borderRightColor: '#f0f0f0', + }), }, '& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell': { - borderBottom: `1px solid ${ - theme.palette.mode === 'light' ? '#f0f0f0' : '#303030' - }`, + borderBottom: '1px solid #303030', + ...theme.applyStyles('light', { + borderBottomColor: '#f0f0f0', + }), }, '& .MuiDataGrid-cell': { - color: - theme.palette.mode === 'light' - ? 'rgba(0,0,0,.85)' - : 'rgba(255,255,255,0.65)', + color: 'rgba(255,255,255,0.65)', + ...theme.applyStyles('light', { + color: 'rgba(0,0,0,.85)', + }), }, '& .MuiPaginationItem-root': { borderRadius: 0, @@ -94,10 +102,11 @@ const AntDesignStyledDataGridPro = styled(DataGridPro)(({ theme }) => ({ width: 16, height: 16, backgroundColor: 'transparent', - border: `1px solid ${ - theme.palette.mode === 'light' ? '#d9d9d9' : 'rgb(67, 67, 67)' - }`, + border: '1px solid rgb(67, 67, 67)', borderRadius: 2, + ...theme.applyStyles('light', { + borderColor: '#d9d9d9', + }), }, '& .MuiCheckbox-root svg path': { display: 'none', @@ -129,7 +138,14 @@ const AntDesignStyledDataGridPro = styled(DataGridPro)(({ theme }) => ({ top: '39%', border: 0, }, + ...theme.applyStyles('light', { + color: 'rgba(0,0,0,.85)', + }), }, + ...theme.applyStyles('light', { + borderColor: '#f0f0f0', + color: 'rgba(0,0,0,.85)', + }), })); const StyledBox = styled('div')(({ theme }) => ({ diff --git a/docs/data/data-grid/demo/FullFeaturedDemo.tsx b/docs/data/data-grid/demo/FullFeaturedDemo.tsx index 2928393ef60ec..2112942cab764 100644 --- a/docs/data/data-grid/demo/FullFeaturedDemo.tsx +++ b/docs/data/data-grid/demo/FullFeaturedDemo.tsx @@ -15,9 +15,8 @@ import MenuItem from '@mui/material/MenuItem'; import Select, { SelectProps } from '@mui/material/Select'; const AntDesignStyledDataGridPro = styled(DataGridPro)(({ theme }) => ({ - border: `1px solid ${theme.palette.mode === 'light' ? '#f0f0f0' : '#303030'}`, - color: - theme.palette.mode === 'light' ? 'rgba(0,0,0,.85)' : 'rgba(255,255,255,0.85)', + border: '1px solid #303030', + color: 'rgba(255,255,255,0.85)', fontFamily: [ '-apple-system', 'BlinkMacSystemFont', @@ -33,24 +32,28 @@ const AntDesignStyledDataGridPro = styled(DataGridPro)(({ theme }) => ({ WebkitFontSmoothing: 'auto', letterSpacing: 'normal', '& .MuiDataGrid-columnsContainer': { - backgroundColor: theme.palette.mode === 'light' ? '#fafafa' : '#1d1d1d', + backgroundColor: '#1d1d1d', + ...theme.applyStyles('light', { + backgroundColor: '#fafafa', + }), }, '& .MuiDataGrid-iconSeparator': { display: 'none', }, '& .MuiDataGrid-columnHeader, .MuiDataGrid-cell': { - borderRight: `1px solid ${ - theme.palette.mode === 'light' ? '#f0f0f0' : '#303030' - }`, + borderRight: '1px solid #303030', + ...theme.applyStyles('light', { + borderRightColor: '#f0f0f0', + }), }, '& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell': { - borderBottom: `1px solid ${ - theme.palette.mode === 'light' ? '#f0f0f0' : '#303030' - }`, + borderBottom: '1px solid #303030', + ...theme.applyStyles('light', { + borderBottomColor: '#f0f0f0', + }), }, '& .MuiDataGrid-cell': { - color: - theme.palette.mode === 'light' ? 'rgba(0,0,0,.85)' : 'rgba(255,255,255,0.85)', + color: 'rgba(255,255,255,0.85)', fontFamily: [ '-apple-system', 'BlinkMacSystemFont', @@ -66,26 +69,31 @@ const AntDesignStyledDataGridPro = styled(DataGridPro)(({ theme }) => ({ WebkitFontSmoothing: 'auto', letterSpacing: 'normal', '& .MuiDataGrid-columnsContainer': { - backgroundColor: theme.palette.mode === 'light' ? '#fafafa' : '#1d1d1d', + backgroundColor: '#1d1d1d', + ...theme.applyStyles('light', { + backgroundColor: '#fafafa', + }), }, '& .MuiDataGrid-iconSeparator': { display: 'none', }, '& .MuiDataGrid-colCell, .MuiDataGrid-cell': { - borderRight: `1px solid ${ - theme.palette.mode === 'light' ? '#f0f0f0' : '#303030' - }`, + borderRight: '1px solid #303030', + ...theme.applyStyles('light', { + borderRightColor: '#f0f0f0', + }), }, '& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell': { - borderBottom: `1px solid ${ - theme.palette.mode === 'light' ? '#f0f0f0' : '#303030' - }`, + borderBottom: '1px solid #303030', + ...theme.applyStyles('light', { + borderBottomColor: '#f0f0f0', + }), }, '& .MuiDataGrid-cell': { - color: - theme.palette.mode === 'light' - ? 'rgba(0,0,0,.85)' - : 'rgba(255,255,255,0.65)', + color: 'rgba(255,255,255,0.65)', + ...theme.applyStyles('light', { + color: 'rgba(0,0,0,.85)', + }), }, '& .MuiPaginationItem-root': { borderRadius: 0, @@ -94,10 +102,11 @@ const AntDesignStyledDataGridPro = styled(DataGridPro)(({ theme }) => ({ width: 16, height: 16, backgroundColor: 'transparent', - border: `1px solid ${ - theme.palette.mode === 'light' ? '#d9d9d9' : 'rgb(67, 67, 67)' - }`, + border: '1px solid rgb(67, 67, 67)', borderRadius: 2, + ...theme.applyStyles('light', { + borderColor: '#d9d9d9', + }), }, '& .MuiCheckbox-root svg path': { display: 'none', @@ -129,7 +138,14 @@ const AntDesignStyledDataGridPro = styled(DataGridPro)(({ theme }) => ({ top: '39%', border: 0, }, + ...theme.applyStyles('light', { + color: 'rgba(0,0,0,.85)', + }), }, + ...theme.applyStyles('light', { + borderColor: '#f0f0f0', + color: 'rgba(0,0,0,.85)', + }), })); const StyledBox = styled('div')(({ theme }) => ({ diff --git a/docs/data/data-grid/editing/IsCellEditableGrid.js b/docs/data/data-grid/editing/IsCellEditableGrid.js index aebf4beab2815..1bf0165409870 100644 --- a/docs/data/data-grid/editing/IsCellEditableGrid.js +++ b/docs/data/data-grid/editing/IsCellEditableGrid.js @@ -10,14 +10,16 @@ import { export default function IsCellEditableGrid() { return ( ({ height: 400, width: '100%', '& .MuiDataGrid-cell--editable': { - bgcolor: (theme) => - theme.palette.mode === 'dark' ? '#376331' : 'rgb(217 243 190)', + bgcolor: 'rgb(217 243 190)', + ...theme.applyStyles('dark', { + bgcolor: '#376331', + }), }, - }} + })} > ({ height: 400, width: '100%', '& .MuiDataGrid-cell--editable': { - bgcolor: (theme) => - theme.palette.mode === 'dark' ? '#376331' : 'rgb(217 243 190)', + bgcolor: 'rgb(217 243 190)', + ...theme.applyStyles('dark', { + bgcolor: '#376331', + }), }, - }} + })} > ({ height: 400, width: '100%', '& .MuiDataGrid-cell--editable': { - backgroundColor: theme.palette.mode === 'dark' ? '#376331' : 'rgb(217 243 190)', + backgroundColor: 'rgb(217 243 190)', '& .MuiInputBase-root': { height: '100%', }, + ...theme.applyStyles('dark', { + backgroundColor: '#376331', + }), }, '& .Mui-error': { - backgroundColor: `rgb(126,10,15, ${theme.palette.mode === 'dark' ? 0 : 0.1})`, - color: theme.palette.mode === 'dark' ? '#ff4343' : '#750f0f', + backgroundColor: 'rgb(126,10,15, 0.1)', + color: '#750f0f', + ...theme.applyStyles('dark', { + backgroundColor: 'rgb(126,10,15, 0)', + color: '#ff4343', + }), }, })); diff --git a/docs/data/data-grid/editing/ValidateServerNameGrid.tsx b/docs/data/data-grid/editing/ValidateServerNameGrid.tsx index 5b460c759370f..0355112675217 100644 --- a/docs/data/data-grid/editing/ValidateServerNameGrid.tsx +++ b/docs/data/data-grid/editing/ValidateServerNameGrid.tsx @@ -14,14 +14,21 @@ const StyledBox = styled('div')(({ theme }) => ({ height: 400, width: '100%', '& .MuiDataGrid-cell--editable': { - backgroundColor: theme.palette.mode === 'dark' ? '#376331' : 'rgb(217 243 190)', + backgroundColor: 'rgb(217 243 190)', '& .MuiInputBase-root': { height: '100%', }, + ...theme.applyStyles('dark', { + backgroundColor: '#376331', + }), }, '& .Mui-error': { - backgroundColor: `rgb(126,10,15, ${theme.palette.mode === 'dark' ? 0 : 0.1})`, - color: theme.palette.mode === 'dark' ? '#ff4343' : '#750f0f', + backgroundColor: 'rgb(126,10,15, 0.1)', + color: '#750f0f', + ...theme.applyStyles('dark', { + backgroundColor: 'rgb(126,10,15, 0)', + color: '#ff4343', + }), }, })); diff --git a/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.js b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.js new file mode 100644 index 0000000000000..290e5ce9e6084 --- /dev/null +++ b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.js @@ -0,0 +1,85 @@ +import * as React from 'react'; +import { DataGrid, GridToolbar } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin']; + +const createFilterModelStore = () => { + let listeners = []; + const lsKey = 'gridFilterModel'; + const emptyModel = 'null'; + + return { + subscribe: (callback) => { + listeners.push(callback); + return () => { + listeners = listeners.filter((listener) => listener !== callback); + }; + }, + getSnapshot: () => { + try { + return localStorage.getItem(lsKey) || emptyModel; + } catch (error) { + return emptyModel; + } + }, + getServerSnapshot: () => { + return emptyModel; + }, + update: (filterModel) => { + localStorage.setItem(lsKey, JSON.stringify(filterModel)); + listeners.forEach((listener) => listener()); + }, + }; +}; + +const usePersistedFilterModel = () => { + const [filterModelStore] = React.useState(createFilterModelStore); + + const filterModelString = React.useSyncExternalStore( + filterModelStore.subscribe, + filterModelStore.getSnapshot, + filterModelStore.getServerSnapshot, + ); + + const filterModel = React.useMemo(() => { + try { + return JSON.parse(filterModelString) || undefined; + } catch (error) { + return undefined; + } + }, [filterModelString]); + + return React.useMemo( + () => [filterModel, filterModelStore.update], + [filterModel, filterModelStore.update], + ); +}; + +export default function FilteringLocalStorage() { + const { data } = useDemoData({ + dataSet: 'Employee', + visibleFields: VISIBLE_FIELDS, + rowLength: 100, + }); + + const [filterModel, setFilterModel] = usePersistedFilterModel(); + + const onFilterModelChange = React.useCallback( + (newFilterModel) => { + setFilterModel(newFilterModel); + }, + [setFilterModel], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx new file mode 100644 index 0000000000000..f0d4089961706 --- /dev/null +++ b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx @@ -0,0 +1,94 @@ +import * as React from 'react'; +import { + DataGrid, + DataGridProps, + GridFilterModel, + GridToolbar, +} from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin']; + +const createFilterModelStore = () => { + let listeners: Array<() => void> = []; + const lsKey = 'gridFilterModel'; + const emptyModel = 'null'; + + return { + subscribe: (callback: () => void) => { + listeners.push(callback); + return () => { + listeners = listeners.filter((listener) => listener !== callback); + }; + }, + getSnapshot: () => { + try { + return localStorage.getItem(lsKey) || emptyModel; + } catch (error) { + return emptyModel; + } + }, + + getServerSnapshot: () => { + return emptyModel; + }, + + update: (filterModel: GridFilterModel) => { + localStorage.setItem(lsKey, JSON.stringify(filterModel)); + listeners.forEach((listener) => listener()); + }, + }; +}; + +const usePersistedFilterModel = () => { + const [filterModelStore] = React.useState(createFilterModelStore); + + const filterModelString = React.useSyncExternalStore( + filterModelStore.subscribe, + filterModelStore.getSnapshot, + filterModelStore.getServerSnapshot, + ); + + const filterModel = React.useMemo(() => { + try { + return (JSON.parse(filterModelString) as GridFilterModel) || undefined; + } catch (error) { + return undefined; + } + }, [filterModelString]); + + return React.useMemo( + () => [filterModel, filterModelStore.update] as const, + [filterModel, filterModelStore.update], + ); +}; + +export default function FilteringLocalStorage() { + const { data } = useDemoData({ + dataSet: 'Employee', + visibleFields: VISIBLE_FIELDS, + rowLength: 100, + }); + + const [filterModel, setFilterModel] = usePersistedFilterModel(); + + const onFilterModelChange = React.useCallback< + NonNullable + >( + (newFilterModel) => { + setFilterModel(newFilterModel); + }, + [setFilterModel], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx.preview b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx.preview new file mode 100644 index 0000000000000..cdca9c3dde546 --- /dev/null +++ b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx.preview @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/filtering-recipes/filtering-recipes.md b/docs/data/data-grid/filtering-recipes/filtering-recipes.md index fa5322aa7f224..9265e2dd36f9a 100644 --- a/docs/data/data-grid/filtering-recipes/filtering-recipes.md +++ b/docs/data/data-grid/filtering-recipes/filtering-recipes.md @@ -6,6 +6,14 @@ title: Data Grid - Filtering customization recipes

Advanced filtering customization recipes.

+## Persisting filters in local storage + +You can persist the filters in the local storage to keep the filters applied after the page is reloaded. + +In the demo below, the [`React.useSyncExternalStore` hook](https://react.dev/reference/react/useSyncExternalStore) is used to synchronize the filters with the local storage. + +{{"demo": "FilteringLocalStorage.js", "bg": "inline", "defaultCodeOpen": false}} + ## Quick filter outside of the grid The [Quick Filter](/x/react-data-grid/filtering/quick-filter/) component is typically used in the Data Grid's Toolbar component slot. diff --git a/docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.js b/docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.js index 59520ac9e4153..294ebacdbea2c 100644 --- a/docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.js +++ b/docs/data/data-grid/filtering/CustomHeaderFilterDataGridPro.js @@ -62,11 +62,20 @@ function CustomHeaderFilter(props) { return ( + - + ); } diff --git a/docs/data/data-grid/joy-ui/GridJoyUISlots.tsx b/docs/data/data-grid/joy-ui/GridJoyUISlots.tsx index 8bcd13834ceb5..6b1817921dccd 100644 --- a/docs/data/data-grid/joy-ui/GridJoyUISlots.tsx +++ b/docs/data/data-grid/joy-ui/GridJoyUISlots.tsx @@ -3,8 +3,8 @@ import Box from '@mui/material/Box'; import { DataGrid, GridToolbar, GridActionsCellItem } from '@mui/x-data-grid'; import { unstable_joySlots } from '@mui/x-data-grid/joy'; import { - experimental_extendTheme as materialExtendTheme, - Experimental_CssVarsProvider as MaterialCssVarsProvider, + createTheme, + ThemeProvider as MaterialThemeProvider, THEME_ID as MATERIAL_THEME_ID, } from '@mui/material/styles'; import { CssVarsProvider as JoyCssVarsProvider } from '@mui/joy/styles'; @@ -19,7 +19,7 @@ import { } from '@mui/x-data-grid-generator'; import type {} from '@mui/material/themeCssVarsAugmentation'; -const materialTheme = materialExtendTheme({ +const materialTheme = createTheme({ components: { MuiSvgIcon: { styleOverrides: { @@ -106,7 +106,7 @@ export default function GridJoyUISlots() { }, []); return ( - + - + ); } diff --git a/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.js b/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.js index b52f36d8eb203..9b93efc6eaaf3 100644 --- a/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.js +++ b/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.js @@ -10,10 +10,16 @@ const StyledGridOverlay = styled('div')(({ theme }) => ({ justifyContent: 'center', height: '100%', '& .no-rows-primary': { - fill: theme.palette.mode === 'light' ? '#AEB8C2' : '#3D4751', + fill: '#3D4751', + ...theme.applyStyles('light', { + fill: '#AEB8C2', + }), }, '& .no-rows-secondary': { - fill: theme.palette.mode === 'light' ? '#E8EAED' : '#1D2126', + fill: '#1D2126', + ...theme.applyStyles('light', { + fill: '#E8EAED', + }), }, })); diff --git a/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.tsx b/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.tsx index b52f36d8eb203..9b93efc6eaaf3 100644 --- a/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.tsx +++ b/docs/data/data-grid/layout/AutoHeightOverlayNoSnap.tsx @@ -10,10 +10,16 @@ const StyledGridOverlay = styled('div')(({ theme }) => ({ justifyContent: 'center', height: '100%', '& .no-rows-primary': { - fill: theme.palette.mode === 'light' ? '#AEB8C2' : '#3D4751', + fill: '#3D4751', + ...theme.applyStyles('light', { + fill: '#AEB8C2', + }), }, '& .no-rows-secondary': { - fill: theme.palette.mode === 'light' ? '#E8EAED' : '#1D2126', + fill: '#1D2126', + ...theme.applyStyles('light', { + fill: '#E8EAED', + }), }, })); diff --git a/docs/data/data-grid/localization/data.json b/docs/data/data-grid/localization/data.json index e1a780c91537a..f8ddeba538511 100644 --- a/docs/data/data-grid/localization/data.json +++ b/docs/data/data-grid/localization/data.json @@ -35,7 +35,7 @@ "languageTag": "zh-CN", "importName": "zhCN", "localeName": "Chinese (Simplified)", - "missingKeysCount": 4, + "missingKeysCount": 0, "totalKeysCount": 118, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/zhCN.ts" }, @@ -59,7 +59,7 @@ "languageTag": "cs-CZ", "importName": "csCZ", "localeName": "Czech", - "missingKeysCount": 4, + "missingKeysCount": 0, "totalKeysCount": 118, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/csCZ.ts" }, @@ -91,7 +91,7 @@ "languageTag": "fr-FR", "importName": "frFR", "localeName": "French", - "missingKeysCount": 1, + "missingKeysCount": 0, "totalKeysCount": 118, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/frFR.ts" }, @@ -115,7 +115,7 @@ "languageTag": "he-IL", "importName": "heIL", "localeName": "Hebrew", - "missingKeysCount": 4, + "missingKeysCount": 0, "totalKeysCount": 118, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/heIL.ts" }, @@ -275,7 +275,7 @@ "languageTag": "vi-VN", "importName": "viVN", "localeName": "Vietnamese", - "missingKeysCount": 4, + "missingKeysCount": 0, "totalKeysCount": 118, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/viVN.ts" } diff --git a/docs/data/data-grid/master-detail/CustomizeDetailPanelToggle.js b/docs/data/data-grid/master-detail/CustomizeDetailPanelToggle.js index 0f9a8b1b1f648..77d28626f827b 100644 --- a/docs/data/data-grid/master-detail/CustomizeDetailPanelToggle.js +++ b/docs/data/data-grid/master-detail/CustomizeDetailPanelToggle.js @@ -59,13 +59,12 @@ function CustomDetailPanelToggle(props) { aria-label={isExpanded ? 'Close' : 'Open'} > ({ transform: `rotateZ(${isExpanded ? 180 : 0}deg)`, - transition: (theme) => - theme.transitions.create('transform', { - duration: theme.transitions.duration.shortest, - }), - }} + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.shortest, + }), + })} fontSize="inherit" /> diff --git a/docs/data/data-grid/master-detail/CustomizeDetailPanelToggle.tsx b/docs/data/data-grid/master-detail/CustomizeDetailPanelToggle.tsx index 47a78a7af50a3..aea595aa168d2 100644 --- a/docs/data/data-grid/master-detail/CustomizeDetailPanelToggle.tsx +++ b/docs/data/data-grid/master-detail/CustomizeDetailPanelToggle.tsx @@ -63,13 +63,12 @@ function CustomDetailPanelToggle(props: Pick ({ transform: `rotateZ(${isExpanded ? 180 : 0}deg)`, - transition: (theme) => - theme.transitions.create('transform', { - duration: theme.transitions.duration.shortest, - }), - }} + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.shortest, + }), + })} fontSize="inherit" /> diff --git a/docs/data/data-grid/overlays/LoadingOverlayCustom.js b/docs/data/data-grid/overlays/LoadingOverlayCustom.js index a936b4296f194..a2b1edbb06ca6 100644 --- a/docs/data/data-grid/overlays/LoadingOverlayCustom.js +++ b/docs/data/data-grid/overlays/LoadingOverlayCustom.js @@ -12,10 +12,10 @@ const StyledGridOverlay = styled('div')(({ theme }) => ({ alignItems: 'center', justifyContent: 'center', height: '100%', - backgroundColor: - theme.palette.mode === 'light' - ? 'rgba(255, 255, 255, 0.9)' - : 'rgba(18, 18, 18, 0.9)', + backgroundColor: 'rgba(18, 18, 18, 0.9)', + ...theme.applyStyles('light', { + backgroundColor: 'rgba(255, 255, 255, 0.9)', + }), })); function CircularProgressWithLabel(props) { diff --git a/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx b/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx index 59209a50e686a..1b22495961470 100644 --- a/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx +++ b/docs/data/data-grid/overlays/LoadingOverlayCustom.tsx @@ -14,10 +14,10 @@ const StyledGridOverlay = styled('div')(({ theme }) => ({ alignItems: 'center', justifyContent: 'center', height: '100%', - backgroundColor: - theme.palette.mode === 'light' - ? 'rgba(255, 255, 255, 0.9)' - : 'rgba(18, 18, 18, 0.9)', + backgroundColor: 'rgba(18, 18, 18, 0.9)', + ...theme.applyStyles('light', { + backgroundColor: 'rgba(255, 255, 255, 0.9)', + }), })); function CircularProgressWithLabel( diff --git a/docs/data/data-grid/overlays/NoResultsOverlayCustom.js b/docs/data/data-grid/overlays/NoResultsOverlayCustom.js index 15320b97081c0..dc89588011fda 100644 --- a/docs/data/data-grid/overlays/NoResultsOverlayCustom.js +++ b/docs/data/data-grid/overlays/NoResultsOverlayCustom.js @@ -11,10 +11,16 @@ const StyledGridOverlay = styled('div')(({ theme }) => ({ justifyContent: 'center', height: '100%', '& .no-results-primary': { - fill: theme.palette.mode === 'light' ? '#AEB8C2' : '#3D4751', + fill: '#3D4751', + ...theme.applyStyles('light', { + fill: '#AEB8C2', + }), }, '& .no-results-secondary': { - fill: theme.palette.mode === 'light' ? '#E8EAED' : '#1D2126', + fill: '#1D2126', + ...theme.applyStyles('light', { + fill: '#E8EAED', + }), }, })); diff --git a/docs/data/data-grid/overlays/NoResultsOverlayCustom.tsx b/docs/data/data-grid/overlays/NoResultsOverlayCustom.tsx index 15320b97081c0..dc89588011fda 100644 --- a/docs/data/data-grid/overlays/NoResultsOverlayCustom.tsx +++ b/docs/data/data-grid/overlays/NoResultsOverlayCustom.tsx @@ -11,10 +11,16 @@ const StyledGridOverlay = styled('div')(({ theme }) => ({ justifyContent: 'center', height: '100%', '& .no-results-primary': { - fill: theme.palette.mode === 'light' ? '#AEB8C2' : '#3D4751', + fill: '#3D4751', + ...theme.applyStyles('light', { + fill: '#AEB8C2', + }), }, '& .no-results-secondary': { - fill: theme.palette.mode === 'light' ? '#E8EAED' : '#1D2126', + fill: '#1D2126', + ...theme.applyStyles('light', { + fill: '#E8EAED', + }), }, })); diff --git a/docs/data/data-grid/overlays/NoRowsOverlayCustom.js b/docs/data/data-grid/overlays/NoRowsOverlayCustom.js index bfe5090c04662..606ccc43562cb 100644 --- a/docs/data/data-grid/overlays/NoRowsOverlayCustom.js +++ b/docs/data/data-grid/overlays/NoRowsOverlayCustom.js @@ -11,10 +11,16 @@ const StyledGridOverlay = styled('div')(({ theme }) => ({ justifyContent: 'center', height: '100%', '& .no-rows-primary': { - fill: theme.palette.mode === 'light' ? '#AEB8C2' : '#3D4751', + fill: '#3D4751', + ...theme.applyStyles('light', { + fill: '#AEB8C2', + }), }, '& .no-rows-secondary': { - fill: theme.palette.mode === 'light' ? '#E8EAED' : '#1D2126', + fill: '#1D2126', + ...theme.applyStyles('light', { + fill: '#E8EAED', + }), }, })); diff --git a/docs/data/data-grid/overlays/NoRowsOverlayCustom.tsx b/docs/data/data-grid/overlays/NoRowsOverlayCustom.tsx index bfe5090c04662..606ccc43562cb 100644 --- a/docs/data/data-grid/overlays/NoRowsOverlayCustom.tsx +++ b/docs/data/data-grid/overlays/NoRowsOverlayCustom.tsx @@ -11,10 +11,16 @@ const StyledGridOverlay = styled('div')(({ theme }) => ({ justifyContent: 'center', height: '100%', '& .no-rows-primary': { - fill: theme.palette.mode === 'light' ? '#AEB8C2' : '#3D4751', + fill: '#3D4751', + ...theme.applyStyles('light', { + fill: '#AEB8C2', + }), }, '& .no-rows-secondary': { - fill: theme.palette.mode === 'light' ? '#E8EAED' : '#1D2126', + fill: '#1D2126', + ...theme.applyStyles('light', { + fill: '#E8EAED', + }), }, })); diff --git a/docs/data/data-grid/overview/overview.md b/docs/data/data-grid/overview/overview.md index cb859d0d35c93..fcc710c8290e4 100644 --- a/docs/data/data-grid/overview/overview.md +++ b/docs/data/data-grid/overview/overview.md @@ -109,7 +109,7 @@ Planned features include: - [Pivoting](/x/react-data-grid/pivoting/) - [Charts integration](/x/react-charts/) -You can find more details on, the [feature comparison](/x/react-data-grid/getting-started/#feature-comparison), our living quarterly [roadmap](https://github.com/mui/mui-x/projects/1) as well as on the open [GitHub issues](https://github.com/mui/mui-x/issues?q=is%3Aopen+label%3A%22component%3A+DataGrid%22+label%3Aenhancement). +You can find more details on, the [feature comparison](/x/react-data-grid/getting-started/#feature-comparison), our living quarterly [roadmap](https://github.com/orgs/mui/projects/35) as well as on the open [GitHub issues](https://github.com/mui/mui-x/issues?q=is%3Aopen+label%3A%22component%3A+DataGrid%22+label%3Aenhancement). ## Resources diff --git a/docs/data/data-grid/pagination/CursorPaginationGrid.js b/docs/data/data-grid/pagination/CursorPaginationGrid.js index 04caad836070a..1815b05771007 100644 --- a/docs/data/data-grid/pagination/CursorPaginationGrid.js +++ b/docs/data/data-grid/pagination/CursorPaginationGrid.js @@ -110,7 +110,7 @@ export default function CursorPaginationGrid() { aria-labelledby="demo-cursor-pagination-buttons-group-label" name="cursor-pagination-buttons-group" value={rowCountType} - onChange={(e) => setRowCountType(e.target.value)} + onChange={(event) => setRowCountType(event.target.value)} > } label="Known" /> } label="Unknown" /> diff --git a/docs/data/data-grid/pagination/CursorPaginationGrid.tsx b/docs/data/data-grid/pagination/CursorPaginationGrid.tsx index d54e3e806c7d1..113e1b7945c45 100644 --- a/docs/data/data-grid/pagination/CursorPaginationGrid.tsx +++ b/docs/data/data-grid/pagination/CursorPaginationGrid.tsx @@ -117,7 +117,7 @@ export default function CursorPaginationGrid() { aria-labelledby="demo-cursor-pagination-buttons-group-label" name="cursor-pagination-buttons-group" value={rowCountType} - onChange={(e) => setRowCountType(e.target.value as RowCountType)} + onChange={(event) => setRowCountType(event.target.value as RowCountType)} > } label="Known" /> } label="Unknown" /> diff --git a/docs/data/data-grid/performance/GridVisualization.js b/docs/data/data-grid/performance/GridVisualization.js index 012448e2fd472..97862557cf5d3 100644 --- a/docs/data/data-grid/performance/GridVisualization.js +++ b/docs/data/data-grid/performance/GridVisualization.js @@ -44,14 +44,13 @@ export default function GridVisualization() { return ( ({ height: 400, width: '100%', '&&& .updated': { - transition: (theme) => - theme.transitions.create(['background-color', 'outline'], { - duration: theme.transitions.duration.standard, - }), + transition: theme.transitions.create(['background-color', 'outline'], { + duration: theme.transitions.duration.standard, + }), }, '&&& .updating': { backgroundColor: 'rgb(92 199 68 / 20%)', @@ -59,7 +58,7 @@ export default function GridVisualization() { outlineOffset: '-1px', transition: 'none', }, - }} + })} > ({ height: 400, width: '100%', '&&& .updated': { - transition: (theme) => - theme.transitions.create(['background-color', 'outline'], { - duration: theme.transitions.duration.standard, - }), + transition: theme.transitions.create(['background-color', 'outline'], { + duration: theme.transitions.duration.standard, + }), }, '&&& .updating': { backgroundColor: 'rgb(92 199 68 / 20%)', @@ -59,7 +58,7 @@ export default function GridVisualization() { outlineOffset: '-1px', transition: 'none', }, - }} + })} > ({ }, }, '& .Mui-error': { - backgroundColor: `rgb(126,10,15, ${theme.palette.mode === 'dark' ? 0 : 0.1})`, + backgroundColor: 'rgb(126,10,15, 0.1)', color: theme.palette.error.main, + ...theme.applyStyles('dark', { + backgroundColor: 'rgb(126,10,15, 0)', + }), }, })); diff --git a/docs/data/data-grid/recipes-editing/ConditionalValidationGrid.tsx b/docs/data/data-grid/recipes-editing/ConditionalValidationGrid.tsx index 74d0ddf5b4133..98f9265551144 100644 --- a/docs/data/data-grid/recipes-editing/ConditionalValidationGrid.tsx +++ b/docs/data/data-grid/recipes-editing/ConditionalValidationGrid.tsx @@ -14,8 +14,11 @@ const StyledBox = styled('div')(({ theme }) => ({ }, }, '& .Mui-error': { - backgroundColor: `rgb(126,10,15, ${theme.palette.mode === 'dark' ? 0 : 0.1})`, + backgroundColor: 'rgb(126,10,15, 0.1)', color: theme.palette.error.main, + ...theme.applyStyles('dark', { + backgroundColor: 'rgb(126,10,15, 0)', + }), }, })); diff --git a/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.js b/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.js index 837ea2546b6bb..ea82154282ba8 100644 --- a/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.js +++ b/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.js @@ -8,7 +8,7 @@ import { gridFilteredDescendantRowCountSelector, } from '@mui/x-data-grid-premium'; import { useMovieData } from '@mui/x-data-grid-generator'; -import { Box } from '@mui/material'; +import Box from '@mui/material/Box'; function CustomFooterRowCount(props) { const { visibleRowCount: topLevelRowCount } = props; diff --git a/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.tsx b/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.tsx index 514aa8358b3cc..c5b6e5e0d7459 100644 --- a/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.tsx +++ b/docs/data/data-grid/recipes-row-grouping/RowGroupingChildRowCount.tsx @@ -9,7 +9,7 @@ import { gridFilteredDescendantRowCountSelector, } from '@mui/x-data-grid-premium'; import { useMovieData } from '@mui/x-data-grid-generator'; -import { Box } from '@mui/material'; +import Box from '@mui/material/Box'; function CustomFooterRowCount(props: GridRowCountProps) { const { visibleRowCount: topLevelRowCount } = props; diff --git a/docs/data/data-grid/row-grouping/RowGroupingAriaV8.js b/docs/data/data-grid/row-grouping/RowGroupingAriaV8.js new file mode 100644 index 0000000000000..6689443db0822 --- /dev/null +++ b/docs/data/data-grid/row-grouping/RowGroupingAriaV8.js @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMovieData } from '@mui/x-data-grid-generator'; + +export default function RowGroupingAriaV8() { + const data = useMovieData(); + const apiRef = useGridApiRef(); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company'], + }, + }, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/row-grouping/RowGroupingAriaV8.tsx b/docs/data/data-grid/row-grouping/RowGroupingAriaV8.tsx new file mode 100644 index 0000000000000..6689443db0822 --- /dev/null +++ b/docs/data/data-grid/row-grouping/RowGroupingAriaV8.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMovieData } from '@mui/x-data-grid-generator'; + +export default function RowGroupingAriaV8() { + const data = useMovieData(); + const apiRef = useGridApiRef(); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company'], + }, + }, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/row-grouping/RowGroupingAriaV8.tsx.preview b/docs/data/data-grid/row-grouping/RowGroupingAriaV8.tsx.preview new file mode 100644 index 0000000000000..303e3b3ef2367 --- /dev/null +++ b/docs/data/data-grid/row-grouping/RowGroupingAriaV8.tsx.preview @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/row-grouping/RowGroupingFullExample.js b/docs/data/data-grid/row-grouping/RowGroupingFullExample.js index 8c84089146301..d1a08e3ff0624 100644 --- a/docs/data/data-grid/row-grouping/RowGroupingFullExample.js +++ b/docs/data/data-grid/row-grouping/RowGroupingFullExample.js @@ -39,6 +39,7 @@ export default function RowGroupingFullExample() { groupingColDef={{ leafField: 'traderEmail', }} + experimentalFeatures={{ ariaV8: true }} /> ); diff --git a/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx b/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx index 8c84089146301..d1a08e3ff0624 100644 --- a/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx +++ b/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx @@ -39,6 +39,7 @@ export default function RowGroupingFullExample() { groupingColDef={{ leafField: 'traderEmail', }} + experimentalFeatures={{ ariaV8: true }} /> ); diff --git a/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx.preview b/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx.preview index b20dbc70dc3ad..35a7dd3ccbcd0 100644 --- a/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx.preview +++ b/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx.preview @@ -7,4 +7,5 @@ groupingColDef={{ leafField: 'traderEmail', }} + experimentalFeatures={{ ariaV8: true }} /> \ No newline at end of file diff --git a/docs/data/data-grid/row-grouping/row-grouping.md b/docs/data/data-grid/row-grouping/row-grouping.md index 08ef4281d6cfd..8b387aa8d42b2 100644 --- a/docs/data/data-grid/row-grouping/row-grouping.md +++ b/docs/data/data-grid/row-grouping/row-grouping.md @@ -344,6 +344,22 @@ Don't hesitate to leave a comment on the same issue to influence what gets built With this panel, your users will be able to control which columns are used for grouping just by dragging them inside the panel. +## Accessibility changes in v8 + +The Data Grid v8 with row grouping feature will improve the accessibility and will be more aligned with the WAI-ARIA authoring practices. + +You can start using the new accessibility features by enabling `ariaV8` experimental feature flag: + +```tsx + +``` + +:::warning +The value of `ariaV8` should be constant and not change during the lifetime of the Data Grid. +::: + +{{"demo": "RowGroupingAriaV8.js", "bg": "inline", "defaultCodeOpen": false}} + ## Full example {{"demo": "RowGroupingFullExample.js", "bg": "inline", "defaultCodeOpen": false}} diff --git a/docs/data/data-grid/row-height/ExpandableCells.js b/docs/data/data-grid/row-height/ExpandableCells.js index 1a1c4d10b31de..45b22e8734c01 100644 --- a/docs/data/data-grid/row-height/ExpandableCells.js +++ b/docs/data/data-grid/row-height/ExpandableCells.js @@ -34,7 +34,7 @@ function ExpandableCell({ value }) { setExpanded(!expanded)} > {expanded ? 'view less' : 'view more'} diff --git a/docs/data/data-grid/row-height/ExpandableCells.tsx b/docs/data/data-grid/row-height/ExpandableCells.tsx index 4ac5b11fa122d..7f5f0874d6d44 100644 --- a/docs/data/data-grid/row-height/ExpandableCells.tsx +++ b/docs/data/data-grid/row-height/ExpandableCells.tsx @@ -39,7 +39,7 @@ function ExpandableCell({ value }: GridRenderCellParams) { setExpanded(!expanded)} > {expanded ? 'view less' : 'view more'} diff --git a/docs/data/data-grid/row-selection/CheckboxSelectionIndeterminateGrid.js b/docs/data/data-grid/row-selection/CheckboxSelectionIndeterminateGrid.js new file mode 100644 index 0000000000000..88493e2bf3a76 --- /dev/null +++ b/docs/data/data-grid/row-selection/CheckboxSelectionIndeterminateGrid.js @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { DataGrid } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function CheckboxSelectionIndeterminateGrid() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 10, + maxColumns: 5, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/row-selection/CheckboxSelectionIndeterminateGrid.tsx b/docs/data/data-grid/row-selection/CheckboxSelectionIndeterminateGrid.tsx new file mode 100644 index 0000000000000..88493e2bf3a76 --- /dev/null +++ b/docs/data/data-grid/row-selection/CheckboxSelectionIndeterminateGrid.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { DataGrid } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function CheckboxSelectionIndeterminateGrid() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 10, + maxColumns: 5, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/row-selection/CheckboxSelectionIndeterminateGrid.tsx.preview b/docs/data/data-grid/row-selection/CheckboxSelectionIndeterminateGrid.tsx.preview new file mode 100644 index 0000000000000..9091ca3b75cce --- /dev/null +++ b/docs/data/data-grid/row-selection/CheckboxSelectionIndeterminateGrid.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/row-selection/row-selection.md b/docs/data/data-grid/row-selection/row-selection.md index 6b4e2f63da464..70f9f43b3f90c 100644 --- a/docs/data/data-grid/row-selection/row-selection.md +++ b/docs/data/data-grid/row-selection/row-selection.md @@ -64,6 +64,13 @@ Always set the `checkboxSelection` prop to `true` even when providing a custom c Otherwise, the data grid might remove your column. ::: +### Customize indeterminate checkbox behavior + +The parent checkboxes (like "Select All" checkbox) when clicked in an indeterminate state will deselect the selected rows. +You can customize this behavior by using the [`indeterminateCheckboxAction` prop](/x/api/data-grid/data-grid/#data-grid-prop-indeterminateCheckboxAction). + +{{"demo": "CheckboxSelectionIndeterminateGrid.js", "bg": "inline"}} + ### Visible rows selection [](/x/introduction/licensing/#pro-plan 'Pro plan') By default, when you click the "Select All" checkbox, all rows in the data grid are selected. diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js index 3c0dbeb76f863..31bfbc5b61b7e 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.js @@ -12,11 +12,9 @@ function ServerSideDataGrid() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx index 514b77b23bf9f..498c1e0674299 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGrid.tsx @@ -12,11 +12,9 @@ function ServerSideDataGrid() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js index e059e7b98b002..43c742a5b90a1 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.js @@ -17,11 +17,9 @@ export default function ServerSideDataGridNoCache() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx index ca8a9fe14814f..b62606d8985f0 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridNoCache.tsx @@ -17,11 +17,9 @@ export default function ServerSideDataGridNoCache() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js index 615cf4072a62c..33d5403393f86 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.js @@ -14,11 +14,9 @@ function ServerSideDataGridTTL() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx index 3f8f3525e78c5..f33906bf70b6f 100644 --- a/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideDataGridTTL.tsx @@ -18,11 +18,9 @@ function ServerSideDataGridTTL() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js index 1bf394ea930fe..75fd9a5422acf 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.js @@ -10,13 +10,6 @@ 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, @@ -28,8 +21,11 @@ const StyledDiv = styled('div')(({ theme: t }) => ({ alignItems: 'center', justifyContent: 'center', borderRadius: '4px', - border: `1px solid ${getBorderColor(t)}`, + border: `1px solid ${lighten(alpha(t.palette.divider, 1), 0.88)}`, backgroundColor: t.palette.background.default, + ...t.applyStyles('dark', { + borderColor: darken(alpha(t.palette.divider, 1), 0.68), + }), })); function ErrorOverlay({ error }) { @@ -54,11 +50,9 @@ export default function ServerSideErrorHandling() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, @@ -100,7 +94,7 @@ export default function ServerSideErrorHandling() { control={ setShouldRequestsFail(e.target.checked)} + onChange={(event) => setShouldRequestsFail(event.target.checked)} /> } label="Make the requests fail" diff --git a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx index a030a8bf6e502..2ea04e50c57f1 100644 --- a/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideErrorHandling.tsx @@ -9,20 +9,13 @@ import { 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 { 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: 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, @@ -34,8 +27,11 @@ const StyledDiv = styled('div')(({ theme: t }) => ({ alignItems: 'center', justifyContent: 'center', borderRadius: '4px', - border: `1px solid ${getBorderColor(t)}`, + border: `1px solid ${lighten(alpha(t.palette.divider, 1), 0.88)}`, backgroundColor: t.palette.background.default, + ...t.applyStyles('dark', { + borderColor: darken(alpha(t.palette.divider, 1), 0.68), + }), })); function ErrorOverlay({ error }: { error: string }) { @@ -60,11 +56,9 @@ export default function ServerSideErrorHandling() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, @@ -106,7 +100,7 @@ export default function ServerSideErrorHandling() { control={ setShouldRequestsFail(e.target.checked)} + onChange={(event) => setShouldRequestsFail(event.target.checked)} /> } label="Make the requests fail" diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.js b/docs/data/data-grid/server-side-data/ServerSideTreeData.js index ed0d0bc8e093c..fdba621ef3914 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.js @@ -32,12 +32,10 @@ export default function ServerSideTreeData() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx index 86a2e47fd1ef0..2d0d1cfdea39c 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeData.tsx @@ -38,12 +38,10 @@ export default function ServerSideTreeData() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js index 920a1b2d5609e..9b5d3bfcd7100 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.js @@ -51,12 +51,10 @@ export default function ServerSideTreeDataCustomCache() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx index a3f2961d549fb..fccc8862aef68 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataCustomCache.tsx @@ -59,12 +59,10 @@ export default function ServerSideTreeDataCustomCache() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js index 44da5bf0cab3e..1066e54daf35b 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.js @@ -31,12 +31,10 @@ export default function ServerSideTreeDataErrorHandling() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, @@ -80,7 +78,7 @@ export default function ServerSideTreeDataErrorHandling() { control={ setShouldRequestsFail(e.target.checked)} + onChange={(event) => setShouldRequestsFail(event.target.checked)} /> } label="Make the requests fail" @@ -118,13 +116,6 @@ export default function ServerSideTreeDataErrorHandling() { ); } -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, @@ -136,8 +127,11 @@ const StyledDiv = styled('div')(({ theme: t }) => ({ alignItems: 'center', justifyContent: 'center', borderRadius: '4px', - border: `1px solid ${getBorderColor(t)}`, + border: `1px solid ${lighten(alpha(t.palette.divider, 1), 0.88)}`, backgroundColor: t.palette.background.default, + ...t.applyStyles('dark', { + borderColor: darken(alpha(t.palette.divider, 1), 0.68), + }), })); function ErrorOverlay({ error }) { diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx index fdd4deeb1771b..c62baec1a6a73 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataErrorHandling.tsx @@ -9,7 +9,7 @@ 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 { alpha, styled, darken, lighten } from '@mui/material/styles'; import { useMockServer } from '@mui/x-data-grid-generator'; const pageSizeOptions = [5, 10, 50]; @@ -36,12 +36,10 @@ export default function ServerSideTreeDataErrorHandling() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, @@ -85,7 +83,7 @@ export default function ServerSideTreeDataErrorHandling() { control={ setShouldRequestsFail(e.target.checked)} + onChange={(event) => setShouldRequestsFail(event.target.checked)} /> } label="Make the requests fail" @@ -123,13 +121,6 @@ export default function ServerSideTreeDataErrorHandling() { ); } -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, @@ -141,8 +132,11 @@ const StyledDiv = styled('div')(({ theme: t }) => ({ alignItems: 'center', justifyContent: 'center', borderRadius: '4px', - border: `1px solid ${getBorderColor(t)}`, + border: `1px solid ${lighten(alpha(t.palette.divider, 1), 0.88)}`, backgroundColor: t.palette.background.default, + ...t.applyStyles('dark', { + borderColor: darken(alpha(t.palette.divider, 1), 0.68), + }), })); function ErrorOverlay({ error }: { error: string }) { diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js index 9b61ceacdcf8c..ec4471d6664d6 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.js @@ -19,12 +19,10 @@ export default function ServerSideTreeDataGroupExpansion() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx index 9eb7fdd75ba44..f52c73fe470ae 100644 --- a/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx +++ b/docs/data/data-grid/server-side-data/ServerSideTreeDataGroupExpansion.tsx @@ -27,12 +27,10 @@ export default function ServerSideTreeDataGroupExpansion() { () => ({ 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)), + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), }); const getRowsResponse = await fetchRows( `https://mui.com/x/api/data-grid?${urlParams.toString()}`, diff --git a/docs/data/data-grid/server-side-data/index.md b/docs/data/data-grid/server-side-data/index.md index 106b1737b7c39..a53d755dd72db 100644 --- a/docs/data/data-grid/server-side-data/index.md +++ b/docs/data/data-grid/server-side-data/index.md @@ -87,7 +87,7 @@ Let's take a look at the minimal `GridDataSource` interface configuration. ```tsx interface GridDataSource { /** - * This method will be called when the grid needs to fetch some rows + * 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] */ diff --git a/docs/data/data-grid/style/AntDesignGrid.js b/docs/data/data-grid/style/AntDesignGrid.js index 37efb118bd289..7bb38f9533e28 100644 --- a/docs/data/data-grid/style/AntDesignGrid.js +++ b/docs/data/data-grid/style/AntDesignGrid.js @@ -17,10 +17,11 @@ function customCheckbox(theme) { width: 16, height: 16, backgroundColor: 'transparent', - border: `1px solid ${ - theme.palette.mode === 'light' ? '#d9d9d9' : 'rgb(67, 67, 67)' - }`, + border: '1px solid #d9d9d9', borderRadius: 2, + ...theme.applyStyles('dark', { + borderColor: 'rgb(67, 67, 67)', + }), }, '& .MuiCheckbox-root svg path': { display: 'none', @@ -57,8 +58,7 @@ function customCheckbox(theme) { const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ border: 0, - color: - theme.palette.mode === 'light' ? 'rgba(0,0,0,.85)' : 'rgba(255,255,255,0.85)', + color: 'rgba(255,255,255,0.85)', fontFamily: [ '-apple-system', 'BlinkMacSystemFont', @@ -74,29 +74,39 @@ const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ WebkitFontSmoothing: 'auto', letterSpacing: 'normal', '& .MuiDataGrid-columnsContainer': { - backgroundColor: theme.palette.mode === 'light' ? '#fafafa' : '#1d1d1d', + backgroundColor: '#1d1d1d', + ...theme.applyStyles('light', { + backgroundColor: '#fafafa', + }), }, '& .MuiDataGrid-iconSeparator': { display: 'none', }, '& .MuiDataGrid-columnHeader, .MuiDataGrid-cell': { - borderRight: `1px solid ${ - theme.palette.mode === 'light' ? '#f0f0f0' : '#303030' - }`, + borderRight: '1px solid #303030', + ...theme.applyStyles('light', { + borderRightColor: '#f0f0f0', + }), }, '& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell': { - borderBottom: `1px solid ${ - theme.palette.mode === 'light' ? '#f0f0f0' : '#303030' - }`, + borderBottom: '1px solid #303030', + ...theme.applyStyles('light', { + borderBottomColor: '#f0f0f0', + }), }, '& .MuiDataGrid-cell': { - color: - theme.palette.mode === 'light' ? 'rgba(0,0,0,.85)' : 'rgba(255,255,255,0.65)', + color: 'rgba(255,255,255,0.65)', + ...theme.applyStyles('light', { + color: 'rgba(0,0,0,.85)', + }), }, '& .MuiPaginationItem-root': { borderRadius: 0, }, ...customCheckbox(theme), + ...theme.applyStyles('light', { + color: 'rgba(0,0,0,.85)', + }), })); function CustomPagination() { diff --git a/docs/data/data-grid/style/AntDesignGrid.tsx b/docs/data/data-grid/style/AntDesignGrid.tsx index d4f18367c18bc..257018a2471dd 100644 --- a/docs/data/data-grid/style/AntDesignGrid.tsx +++ b/docs/data/data-grid/style/AntDesignGrid.tsx @@ -17,10 +17,11 @@ function customCheckbox(theme: Theme) { width: 16, height: 16, backgroundColor: 'transparent', - border: `1px solid ${ - theme.palette.mode === 'light' ? '#d9d9d9' : 'rgb(67, 67, 67)' - }`, + border: '1px solid #d9d9d9', borderRadius: 2, + ...theme.applyStyles('dark', { + borderColor: 'rgb(67, 67, 67)', + }), }, '& .MuiCheckbox-root svg path': { display: 'none', @@ -57,8 +58,7 @@ function customCheckbox(theme: Theme) { const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ border: 0, - color: - theme.palette.mode === 'light' ? 'rgba(0,0,0,.85)' : 'rgba(255,255,255,0.85)', + color: 'rgba(255,255,255,0.85)', fontFamily: [ '-apple-system', 'BlinkMacSystemFont', @@ -74,29 +74,39 @@ const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ WebkitFontSmoothing: 'auto', letterSpacing: 'normal', '& .MuiDataGrid-columnsContainer': { - backgroundColor: theme.palette.mode === 'light' ? '#fafafa' : '#1d1d1d', + backgroundColor: '#1d1d1d', + ...theme.applyStyles('light', { + backgroundColor: '#fafafa', + }), }, '& .MuiDataGrid-iconSeparator': { display: 'none', }, '& .MuiDataGrid-columnHeader, .MuiDataGrid-cell': { - borderRight: `1px solid ${ - theme.palette.mode === 'light' ? '#f0f0f0' : '#303030' - }`, + borderRight: '1px solid #303030', + ...theme.applyStyles('light', { + borderRightColor: '#f0f0f0', + }), }, '& .MuiDataGrid-columnsContainer, .MuiDataGrid-cell': { - borderBottom: `1px solid ${ - theme.palette.mode === 'light' ? '#f0f0f0' : '#303030' - }`, + borderBottom: '1px solid #303030', + ...theme.applyStyles('light', { + borderBottomColor: '#f0f0f0', + }), }, '& .MuiDataGrid-cell': { - color: - theme.palette.mode === 'light' ? 'rgba(0,0,0,.85)' : 'rgba(255,255,255,0.65)', + color: 'rgba(255,255,255,0.65)', + ...theme.applyStyles('light', { + color: 'rgba(0,0,0,.85)', + }), }, '& .MuiPaginationItem-root': { borderRadius: 0, }, ...customCheckbox(theme), + ...theme.applyStyles('light', { + color: 'rgba(0,0,0,.85)', + }), })); function CustomPagination() { diff --git a/docs/data/data-grid/style/StylingRowsGrid.js b/docs/data/data-grid/style/StylingRowsGrid.js index 56606a53676f2..6aee2cc5a9792 100644 --- a/docs/data/data-grid/style/StylingRowsGrid.js +++ b/docs/data/data-grid/style/StylingRowsGrid.js @@ -4,109 +4,59 @@ import { DataGrid } from '@mui/x-data-grid'; import { useDemoData } from '@mui/x-data-grid-generator'; import { darken, lighten, styled } from '@mui/material/styles'; -const getBackgroundColor = (color, mode) => - mode === 'dark' ? darken(color, 0.7) : lighten(color, 0.7); - -const getHoverBackgroundColor = (color, mode) => - mode === 'dark' ? darken(color, 0.6) : lighten(color, 0.6); - -const getSelectedBackgroundColor = (color, mode) => - mode === 'dark' ? darken(color, 0.5) : lighten(color, 0.5); - -const getSelectedHoverBackgroundColor = (color, mode) => - mode === 'dark' ? darken(color, 0.4) : lighten(color, 0.4); +const getBackgroundColor = (color, theme, coefficient) => ({ + backgroundColor: darken(color, coefficient), + ...theme.applyStyles('light', { + backgroundColor: lighten(color, coefficient), + }), +}); const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ '& .super-app-theme--Open': { - backgroundColor: getBackgroundColor(theme.palette.info.main, theme.palette.mode), + ...getBackgroundColor(theme.palette.info.main, theme, 0.7), '&:hover': { - backgroundColor: getHoverBackgroundColor( - theme.palette.info.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.info.main, theme, 0.6), }, '&.Mui-selected': { - backgroundColor: getSelectedBackgroundColor( - theme.palette.info.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.info.main, theme, 0.5), '&:hover': { - backgroundColor: getSelectedHoverBackgroundColor( - theme.palette.info.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.info.main, theme, 0.4), }, }, }, '& .super-app-theme--Filled': { - backgroundColor: getBackgroundColor( - theme.palette.success.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.success.main, theme, 0.7), '&:hover': { - backgroundColor: getHoverBackgroundColor( - theme.palette.success.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.success.main, theme, 0.6), }, '&.Mui-selected': { - backgroundColor: getSelectedBackgroundColor( - theme.palette.success.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.success.main, theme, 0.5), '&:hover': { - backgroundColor: getSelectedHoverBackgroundColor( - theme.palette.success.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.success.main, theme, 0.4), }, }, }, '& .super-app-theme--PartiallyFilled': { - backgroundColor: getBackgroundColor( - theme.palette.warning.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.warning.main, theme, 0.7), '&:hover': { - backgroundColor: getHoverBackgroundColor( - theme.palette.warning.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.warning.main, theme, 0.6), }, '&.Mui-selected': { - backgroundColor: getSelectedBackgroundColor( - theme.palette.warning.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.warning.main, theme, 0.5), '&:hover': { - backgroundColor: getSelectedHoverBackgroundColor( - theme.palette.warning.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.warning.main, theme, 0.4), }, }, }, '& .super-app-theme--Rejected': { - backgroundColor: getBackgroundColor( - theme.palette.error.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.error.main, theme, 0.7), '&:hover': { - backgroundColor: getHoverBackgroundColor( - theme.palette.error.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.error.main, theme, 0.6), }, '&.Mui-selected': { - backgroundColor: getSelectedBackgroundColor( - theme.palette.error.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.error.main, theme, 0.5), '&:hover': { - backgroundColor: getSelectedHoverBackgroundColor( - theme.palette.error.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.error.main, theme, 0.4), }, }, }, diff --git a/docs/data/data-grid/style/StylingRowsGrid.tsx b/docs/data/data-grid/style/StylingRowsGrid.tsx index 4d88308d90bc7..de1d8b44e7fdc 100644 --- a/docs/data/data-grid/style/StylingRowsGrid.tsx +++ b/docs/data/data-grid/style/StylingRowsGrid.tsx @@ -2,111 +2,61 @@ 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 { darken, lighten, styled } from '@mui/material/styles'; +import { darken, lighten, styled, Theme } from '@mui/material/styles'; -const getBackgroundColor = (color: string, mode: string) => - mode === 'dark' ? darken(color, 0.7) : lighten(color, 0.7); - -const getHoverBackgroundColor = (color: string, mode: string) => - mode === 'dark' ? darken(color, 0.6) : lighten(color, 0.6); - -const getSelectedBackgroundColor = (color: string, mode: string) => - mode === 'dark' ? darken(color, 0.5) : lighten(color, 0.5); - -const getSelectedHoverBackgroundColor = (color: string, mode: string) => - mode === 'dark' ? darken(color, 0.4) : lighten(color, 0.4); +const getBackgroundColor = (color: string, theme: Theme, coefficient: number) => ({ + backgroundColor: darken(color, coefficient), + ...theme.applyStyles('light', { + backgroundColor: lighten(color, coefficient), + }), +}); const StyledDataGrid = styled(DataGrid)(({ theme }) => ({ '& .super-app-theme--Open': { - backgroundColor: getBackgroundColor(theme.palette.info.main, theme.palette.mode), + ...getBackgroundColor(theme.palette.info.main, theme, 0.7), '&:hover': { - backgroundColor: getHoverBackgroundColor( - theme.palette.info.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.info.main, theme, 0.6), }, '&.Mui-selected': { - backgroundColor: getSelectedBackgroundColor( - theme.palette.info.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.info.main, theme, 0.5), '&:hover': { - backgroundColor: getSelectedHoverBackgroundColor( - theme.palette.info.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.info.main, theme, 0.4), }, }, }, '& .super-app-theme--Filled': { - backgroundColor: getBackgroundColor( - theme.palette.success.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.success.main, theme, 0.7), '&:hover': { - backgroundColor: getHoverBackgroundColor( - theme.palette.success.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.success.main, theme, 0.6), }, '&.Mui-selected': { - backgroundColor: getSelectedBackgroundColor( - theme.palette.success.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.success.main, theme, 0.5), '&:hover': { - backgroundColor: getSelectedHoverBackgroundColor( - theme.palette.success.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.success.main, theme, 0.4), }, }, }, '& .super-app-theme--PartiallyFilled': { - backgroundColor: getBackgroundColor( - theme.palette.warning.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.warning.main, theme, 0.7), '&:hover': { - backgroundColor: getHoverBackgroundColor( - theme.palette.warning.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.warning.main, theme, 0.6), }, '&.Mui-selected': { - backgroundColor: getSelectedBackgroundColor( - theme.palette.warning.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.warning.main, theme, 0.5), '&:hover': { - backgroundColor: getSelectedHoverBackgroundColor( - theme.palette.warning.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.warning.main, theme, 0.4), }, }, }, '& .super-app-theme--Rejected': { - backgroundColor: getBackgroundColor( - theme.palette.error.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.error.main, theme, 0.7), '&:hover': { - backgroundColor: getHoverBackgroundColor( - theme.palette.error.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.error.main, theme, 0.6), }, '&.Mui-selected': { - backgroundColor: getSelectedBackgroundColor( - theme.palette.error.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.error.main, theme, 0.5), '&:hover': { - backgroundColor: getSelectedHoverBackgroundColor( - theme.palette.error.main, - theme.palette.mode, - ), + ...getBackgroundColor(theme.palette.error.main, theme, 0.4), }, }, }, diff --git a/docs/data/data-grid/virtualization/virtualization.md b/docs/data/data-grid/virtualization/virtualization.md index bc14f817113f9..7d1f048db3837 100644 --- a/docs/data/data-grid/virtualization/virtualization.md +++ b/docs/data/data-grid/virtualization/virtualization.md @@ -14,6 +14,10 @@ Row virtualization is the insertion and removal of rows as the data grid scrolls The grid renders some additional rows above and below the visible rows. You can use `rowBufferPx` prop to hint to the Data Grid the area to render, but this value may not be respected in certain situations, for example during high-speed scrolling. Row virtualization is limited to 100 rows in the `DataGrid` component. +:::warning +Row virtualization does not work with the `autoHeight` prop enabled. +::: + ## Column virtualization Column virtualization is the insertion and removal of columns as the data grid scrolls horizontally. diff --git a/docs/data/date-pickers-component-api-pages.ts b/docs/data/date-pickers-component-api-pages.ts index 739009bd7113f..f7d4b339ac4fb 100644 --- a/docs/data/date-pickers-component-api-pages.ts +++ b/docs/data/date-pickers-component-api-pages.ts @@ -1,4 +1,4 @@ -import type { MuiPage } from '@mui/monorepo/docs/src/MuiPage'; +import type { MuiPage } from 'docs/src/MuiPage'; const apiPages: MuiPage[] = [ { diff --git a/docs/data/date-pickers/adapters-locale/adapters-locale.md b/docs/data/date-pickers/adapters-locale/adapters-locale.md index 26b4113d0f7b2..bf62a4b35c072 100644 --- a/docs/data/date-pickers/adapters-locale/adapters-locale.md +++ b/docs/data/date-pickers/adapters-locale/adapters-locale.md @@ -12,7 +12,7 @@ packageName: '@mui/x-date-pickers' ## Getting started -The default locale of MUIĀ X is English (United States). If you want to use other localesā€”follow the instructions below. +The default locale of MUIĀ X is English (United States). If you want to use other locales, follow the instructions below. :::warning This page focuses on date format localization. diff --git a/docs/data/date-pickers/base-concepts/CustomSlots.js b/docs/data/date-pickers/base-concepts/CustomSlots.js index 5a6d1197147ad..46b7619f3fd32 100644 --- a/docs/data/date-pickers/base-concepts/CustomSlots.js +++ b/docs/data/date-pickers/base-concepts/CustomSlots.js @@ -13,10 +13,10 @@ const StyledButton = styled(IconButton)(({ theme }) => ({ })); const StyledDay = styled(PickersDay)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, - color: - theme.palette.mode === 'light' - ? theme.palette.secondary.dark - : theme.palette.secondary.light, + color: theme.palette.secondary.light, + ...theme.applyStyles('light', { + color: theme.palette.secondary.dark, + }), })); export default function CustomSlots() { diff --git a/docs/data/date-pickers/base-concepts/CustomSlots.tsx b/docs/data/date-pickers/base-concepts/CustomSlots.tsx index 5a6d1197147ad..46b7619f3fd32 100644 --- a/docs/data/date-pickers/base-concepts/CustomSlots.tsx +++ b/docs/data/date-pickers/base-concepts/CustomSlots.tsx @@ -13,10 +13,10 @@ const StyledButton = styled(IconButton)(({ theme }) => ({ })); const StyledDay = styled(PickersDay)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, - color: - theme.palette.mode === 'light' - ? theme.palette.secondary.dark - : theme.palette.secondary.light, + color: theme.palette.secondary.light, + ...theme.applyStyles('light', { + color: theme.palette.secondary.dark, + }), })); export default function CustomSlots() { diff --git a/docs/data/date-pickers/base-concepts/base-concepts.md b/docs/data/date-pickers/base-concepts/base-concepts.md index cf175fda55da9..636aa7f934cae 100644 --- a/docs/data/date-pickers/base-concepts/base-concepts.md +++ b/docs/data/date-pickers/base-concepts/base-concepts.md @@ -29,6 +29,51 @@ import { DatePicker } from '@mui/x-date-pickers'; import { DatePicker } from '@mui/x-date-pickers-pro'; ``` +## Date library + +The Date and Time Pickers are focused on UI/UX and, like most other picker components available, require a third-party library to format, parse, and mutate dates. + +MUI's components let you choose which library you prefer for this purpose. +This gives you the flexibility to implement any date library you may already be using in your application, without adding an extra one to your bundle. + +To achieve this, both `@mui/x-date-pickers` and `@mui/x-date-pickers-pro` export a set of **adapters** that expose the date manipulation libraries under a unified API. + +### Available libraries + +The Date and Time Pickers currently support the following date libraries: + +- [Day.js](https://day.js.org/) +- [date-fns](https://date-fns.org/) +- [Luxon](https://moment.github.io/luxon/#/) +- [Moment.js](https://momentjs.com/) + +:::info +If you are using a non-Gregorian calendar (such as Jalali or Hijri), please refer to the [Support for other calendar systems](/x/react-date-pickers/calendar-systems/) page. +::: + +### Recommended library + +If you are already using one of the libraries listed above in your application, then you can keep using it with the Date and Time Pickers as well. +This will avoid bundling two libraries. + +If you don't have your own requirements or don't manipulate dates outside of MUIĀ X components, then the recommendation is to use `dayjs` because it has the smallest impact on your application's bundle size. + +Here is the weight added to your gzipped bundle size by each of these libraries when used inside the Date and Time Pickers: + +| Library | Gzipped size | +| :---------------- | -----------: | +| `dayjs@1.11.5` | 6.77 kB | +| `date-fns@2.29.3` | 19.39 kB | +| `luxon@3.0.4` | 23.26 kB | +| `moment@2.29.4` | 20.78 kB | + +:::info +The results above were obtained in October 2022 with the latest version of each library. +The bundling of the JavaScript modules was done by a Create React App, and no locale was loaded for any of the libraries. + +The results may vary in your application depending on the version of each library, the locale, and the bundler used. +::: + ## Other components ### Choose interaction style diff --git a/docs/data/date-pickers/custom-components/ArrowSwitcherComponent.js b/docs/data/date-pickers/custom-components/ArrowSwitcherComponent.js index 6a2824cb8a432..039cfac29bda3 100644 --- a/docs/data/date-pickers/custom-components/ArrowSwitcherComponent.js +++ b/docs/data/date-pickers/custom-components/ArrowSwitcherComponent.js @@ -43,7 +43,6 @@ export default function ArrowSwitcherComponent() { {currentComponent === 'date' && ( )} - {currentComponent === 'time' && ( )} - {currentComponent === 'dateRange' && ( )} - {currentComponent === 'time' && ( )} - {currentComponent === 'dateRange' && ( @@ -37,7 +45,15 @@ function CustomCalendarHeader(props) { {month.format('MMMM YYYY')} diff --git a/docs/data/date-pickers/custom-components/CalendarHeaderComponentRange.tsx b/docs/data/date-pickers/custom-components/CalendarHeaderComponentRange.tsx index a7cda0e96c95e..e150031d943e0 100644 --- a/docs/data/date-pickers/custom-components/CalendarHeaderComponentRange.tsx +++ b/docs/data/date-pickers/custom-components/CalendarHeaderComponentRange.tsx @@ -30,7 +30,15 @@ function CustomCalendarHeader(props: PickersRangeCalendarHeaderProps) { @@ -38,7 +46,15 @@ function CustomCalendarHeader(props: PickersRangeCalendarHeaderProps) { {month.format('MMMM YYYY')} diff --git a/docs/data/date-pickers/custom-components/custom-components.md b/docs/data/date-pickers/custom-components/custom-components.md index 8eaf3a83d4fa5..65034ecb88828 100644 --- a/docs/data/date-pickers/custom-components/custom-components.md +++ b/docs/data/date-pickers/custom-components/custom-components.md @@ -285,7 +285,7 @@ You can pass a custom component to replace the month button, as shown below: ## Arrow switcher -The following slots let you customize how to render the buttons and icons for an arrow switcher componentā€”the component +The following slots let you customize how to render the buttons and icons for an arrow switcher: the component used to navigate to the "Previous" and "Next" steps of the picker: `PreviousIconButton`, `NextIconButton`, `LeftArrowIcon`, `RightArrowIcon`. ### Component props diff --git a/docs/data/date-pickers/custom-field/BrowserV6Field.js b/docs/data/date-pickers/custom-field/BrowserV6Field.js index f3a8f6cc98d17..63be3f56e8846 100644 --- a/docs/data/date-pickers/custom-field/BrowserV6Field.js +++ b/docs/data/date-pickers/custom-field/BrowserV6Field.js @@ -29,7 +29,16 @@ const BrowserField = React.forwardRef((props, ref) => { return ( diff --git a/docs/data/date-pickers/custom-field/BrowserV6Field.tsx b/docs/data/date-pickers/custom-field/BrowserV6Field.tsx index b7b4237ad8804..49138cd42863e 100644 --- a/docs/data/date-pickers/custom-field/BrowserV6Field.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV6Field.tsx @@ -59,7 +59,16 @@ const BrowserField = React.forwardRef( return ( diff --git a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.js b/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.js index 61c9dd0857d13..5421fd0d4348c 100644 --- a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.js +++ b/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.js @@ -30,7 +30,14 @@ const BrowserField = React.forwardRef((props, ref) => { return ( diff --git a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx index 0c993265f4413..b52029d14cde9 100644 --- a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx @@ -63,7 +63,14 @@ const BrowserField = React.forwardRef( return ( diff --git a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.js b/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.js index dc34821c612c0..311029bbc6fa8 100644 --- a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.js +++ b/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.js @@ -33,7 +33,13 @@ const BrowserField = React.forwardRef((props, ref) => { return ( @@ -57,7 +63,7 @@ const BrowserSingleInputDateRangeField = React.forwardRef((props, ref) => { textFieldProps.InputProps = { ...textFieldProps.InputProps, endAdornment: ( - + diff --git a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx index 29d27a1c63d7e..6db84b3f6c575 100644 --- a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx @@ -69,7 +69,13 @@ const BrowserField = React.forwardRef( return ( @@ -112,7 +118,7 @@ const BrowserSingleInputDateRangeField = React.forwardRef( textFieldProps.InputProps = { ...textFieldProps.InputProps, endAdornment: ( - + diff --git a/docs/data/date-pickers/custom-field/BrowserV7Field.js b/docs/data/date-pickers/custom-field/BrowserV7Field.js index 2454d803bf30c..78497c2f81a5a 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7Field.js +++ b/docs/data/date-pickers/custom-field/BrowserV7Field.js @@ -13,6 +13,9 @@ import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-p const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({ display: 'flex', alignItems: 'center', + '& .MuiInputAdornment-root': { + height: 'auto', + }, }); const BrowserFieldContent = styled('div', { name: 'BrowserField', slot: 'Content' })( diff --git a/docs/data/date-pickers/custom-field/BrowserV7Field.tsx b/docs/data/date-pickers/custom-field/BrowserV7Field.tsx index a84e535d4340a..202c6dd74e0a9 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7Field.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV7Field.tsx @@ -21,6 +21,9 @@ import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-p const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({ display: 'flex', alignItems: 'center', + '& .MuiInputAdornment-root': { + height: 'auto', + }, }); const BrowserFieldContent = styled('div', { name: 'BrowserField', slot: 'Content' })( diff --git a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js index 57cc103e1c69b..26c10f367f978 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js +++ b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js @@ -16,6 +16,9 @@ import { Unstable_PickersSectionList as PickersSectionList } from '@mui/x-date-p const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({ display: 'flex', alignItems: 'center', + '& .MuiInputAdornment-root': { + height: 'auto', + }, }); const BrowserFieldContent = styled('div', { name: 'BrowserField', slot: 'Content' })( diff --git a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx index dd04c5aae06ef..5c925effbaab3 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx @@ -30,6 +30,9 @@ import { BaseSingleInputFieldProps } from '@mui/x-date-pickers/models'; const BrowserFieldRoot = styled('div', { name: 'BrowserField', slot: 'Root' })({ display: 'flex', alignItems: 'center', + '& .MuiInputAdornment-root': { + height: 'auto', + }, }); const BrowserFieldContent = styled('div', { name: 'BrowserField', slot: 'Content' })( diff --git a/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalid.js b/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalid.js index 45751705f59dd..67ba149ec674f 100644 --- a/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalid.js +++ b/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalid.js @@ -13,7 +13,18 @@ function CustomInputAdornment(props) { {children} diff --git a/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalid.tsx b/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalid.tsx index 9fda0784aff81..c55a05726acc7 100644 --- a/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalid.tsx +++ b/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalid.tsx @@ -14,7 +14,18 @@ function CustomInputAdornment(props: InputAdornmentProps & { hasError?: boolean {children} diff --git a/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalidRange.js b/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalidRange.js index e6b3ea5113735..8012efda6585f 100644 --- a/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalidRange.js +++ b/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalidRange.js @@ -11,7 +11,18 @@ function CustomInputAdornment(props) { const { hasError, children, sx, ...other } = props; return ( - + {children} ); diff --git a/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalidRange.tsx b/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalidRange.tsx index 5e49622b3c029..28bf9e2a238e2 100644 --- a/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalidRange.tsx +++ b/docs/data/date-pickers/custom-opening-button/AddWarningIconWhenInvalidRange.tsx @@ -12,7 +12,18 @@ function CustomInputAdornment(props: InputAdornmentProps & { hasError?: boolean const { hasError, children, sx, ...other } = props; return ( - + {children} ); diff --git a/docs/data/date-pickers/date-calendar/WeekPicker.js b/docs/data/date-pickers/date-calendar/WeekPicker.js index 2154ea4918ccc..02efb22ffc513 100644 --- a/docs/data/date-pickers/date-calendar/WeekPicker.js +++ b/docs/data/date-pickers/date-calendar/WeekPicker.js @@ -21,10 +21,16 @@ const CustomPickersDay = styled(PickersDay, { }, }), ...(isHovered && { - backgroundColor: theme.palette.primary[theme.palette.mode], + backgroundColor: theme.palette.primary.light, '&:hover, &:focus': { - backgroundColor: theme.palette.primary[theme.palette.mode], + backgroundColor: theme.palette.primary.light, }, + ...theme.applyStyles('dark', { + backgroundColor: theme.palette.primary.dark, + '&:hover, &:focus': { + backgroundColor: theme.palette.primary.dark, + }, + }), }), ...(day.day() === 0 && { borderTopLeftRadius: '50%', diff --git a/docs/data/date-pickers/date-calendar/WeekPicker.tsx b/docs/data/date-pickers/date-calendar/WeekPicker.tsx index 88f7fb03e3f6e..7e46c36ba8761 100644 --- a/docs/data/date-pickers/date-calendar/WeekPicker.tsx +++ b/docs/data/date-pickers/date-calendar/WeekPicker.tsx @@ -26,10 +26,16 @@ const CustomPickersDay = styled(PickersDay, { }, }), ...(isHovered && { - backgroundColor: theme.palette.primary[theme.palette.mode], + backgroundColor: theme.palette.primary.light, '&:hover, &:focus': { - backgroundColor: theme.palette.primary[theme.palette.mode], + backgroundColor: theme.palette.primary.light, }, + ...theme.applyStyles('dark', { + backgroundColor: theme.palette.primary.dark, + '&:hover, &:focus': { + backgroundColor: theme.palette.primary.dark, + }, + }), }), ...(day.day() === 0 && { borderTopLeftRadius: '50%', diff --git a/docs/data/date-pickers/date-range-calendar/CustomDateRangePickerDay.js b/docs/data/date-pickers/date-range-calendar/CustomDateRangePickerDay.js index 2bc8af2bb52c5..6f1dbf791830c 100644 --- a/docs/data/date-pickers/date-range-calendar/CustomDateRangePickerDay.js +++ b/docs/data/date-pickers/date-range-calendar/CustomDateRangePickerDay.js @@ -7,34 +7,36 @@ import { AdapterDayjs } from '@mui/x-date-pickers-pro/AdapterDayjs'; import { DateRangeCalendar } from '@mui/x-date-pickers-pro/DateRangeCalendar'; import { DateRangePickerDay as MuiDateRangePickerDay } from '@mui/x-date-pickers-pro/DateRangePickerDay'; -const DateRangePickerDay = styled(MuiDateRangePickerDay)( - ({ - theme, - isHighlighting, - isStartOfHighlighting, - isEndOfHighlighting, - outsideCurrentMonth, - }) => ({ - ...(!outsideCurrentMonth && - isHighlighting && { +const DateRangePickerDay = styled(MuiDateRangePickerDay)(({ theme }) => ({ + variants: [ + { + props: ({ isHighlighting, outsideCurrentMonth }) => + !outsideCurrentMonth && isHighlighting, + style: { borderRadius: 0, backgroundColor: theme.palette.primary.main, color: theme.palette.common.white, '&:hover, &:focus': { backgroundColor: theme.palette.primary.dark, }, - }), - ...(isStartOfHighlighting && { - borderTopLeftRadius: '50%', - borderBottomLeftRadius: '50%', - }), - ...(isEndOfHighlighting && { - borderTopRightRadius: '50%', - borderBottomRightRadius: '50%', - }), - }), -); - + }, + }, + { + props: ({ isStartOfHighlighting }) => isStartOfHighlighting, + style: { + borderTopLeftRadius: '50%', + borderBottomLeftRadius: '50%', + }, + }, + { + props: ({ isEndOfHighlighting }) => isEndOfHighlighting, + style: { + borderTopRightRadius: '50%', + borderBottomRightRadius: '50%', + }, + }, + ], +})); export default function CustomDateRangePickerDay() { return ( diff --git a/docs/data/date-pickers/date-range-calendar/CustomDateRangePickerDay.tsx b/docs/data/date-pickers/date-range-calendar/CustomDateRangePickerDay.tsx index f42faf053b0a3..caf9d98b2c8f2 100644 --- a/docs/data/date-pickers/date-range-calendar/CustomDateRangePickerDay.tsx +++ b/docs/data/date-pickers/date-range-calendar/CustomDateRangePickerDay.tsx @@ -10,34 +10,36 @@ import { DateRangePickerDayProps, } from '@mui/x-date-pickers-pro/DateRangePickerDay'; -const DateRangePickerDay = styled(MuiDateRangePickerDay)( - ({ - theme, - isHighlighting, - isStartOfHighlighting, - isEndOfHighlighting, - outsideCurrentMonth, - }) => ({ - ...(!outsideCurrentMonth && - isHighlighting && { +const DateRangePickerDay = styled(MuiDateRangePickerDay)(({ theme }) => ({ + variants: [ + { + props: ({ isHighlighting, outsideCurrentMonth }) => + !outsideCurrentMonth && isHighlighting, + style: { borderRadius: 0, backgroundColor: theme.palette.primary.main, color: theme.palette.common.white, '&:hover, &:focus': { backgroundColor: theme.palette.primary.dark, }, - }), - ...(isStartOfHighlighting && { - borderTopLeftRadius: '50%', - borderBottomLeftRadius: '50%', - }), - ...(isEndOfHighlighting && { - borderTopRightRadius: '50%', - borderBottomRightRadius: '50%', - }), - }), -) as React.ComponentType>; - + }, + }, + { + props: ({ isStartOfHighlighting }) => isStartOfHighlighting, + style: { + borderTopLeftRadius: '50%', + borderBottomLeftRadius: '50%', + }, + }, + { + props: ({ isEndOfHighlighting }) => isEndOfHighlighting, + style: { + borderTopRightRadius: '50%', + borderBottomRightRadius: '50%', + }, + }, + ], +})) as React.ComponentType>; export default function CustomDateRangePickerDay() { return ( diff --git a/docs/data/date-pickers/getting-started/getting-started.md b/docs/data/date-pickers/getting-started/getting-started.md index 7572576f22f64..4c04e87bd78c8 100644 --- a/docs/data/date-pickers/getting-started/getting-started.md +++ b/docs/data/date-pickers/getting-started/getting-started.md @@ -24,7 +24,7 @@ Using your favorite package manager, install: :::info If you need more information about the date library supported by the Date and Time Pickers, -take a look at the [dedicated section](/x/react-date-pickers/#date-library) +take a look at the [dedicated section](/x/react-date-pickers/base-concepts/#date-library) ::: The Date and Time Pickers package has a peer dependency on `@mui/material`. @@ -86,7 +86,7 @@ This component receives your chosen [date library's adapter](https://mui.com/x/r Each demonstration in the documentation has its own `LocalizationProvider` wrapper. This is **not** a pattern to reproduce. -The reason is to keep examples atomic and functionalā€”especially when running in a CodeSandbox. +The reason is to keep examples atomic and functional, especially when running in a CodeSandbox. The general recommendation is to declare the `LocalizationProvider` once, wrapping your entire application. Then, you don't need to repeat the boilerplate code for every Date and Time Picker in your application. diff --git a/docs/data/date-pickers/lifecycle/lifecycle.md b/docs/data/date-pickers/lifecycle/lifecycle.md index d118eb3858179..b5ee50f1d8281 100644 --- a/docs/data/date-pickers/lifecycle/lifecycle.md +++ b/docs/data/date-pickers/lifecycle/lifecycle.md @@ -141,7 +141,7 @@ You can find more information [in the dedicated doc section](/x/react-date-picke The `onChange` callback is called whenever the current value changes. -If you don't want to listen to the intermediary steps, consider using the [`onAccept` prop](/x/react-date-pickers/lifecycle/#lifecycle-on-pickers-onaccept) instead. +If you don't want to listen to the intermediary steps, consider using the [`onAccept` prop](/x/react-date-pickers/lifecycle/#lifecycle-on-pickers-quot-onaccept-quot) instead. ```tsx setValue(value)} /> @@ -392,6 +392,6 @@ In such a case, the recommended UI is to add a button for validating the form. If for some reason, you need to send the data to the server without having the user pressing a validation button, you can debounce the `onChange` as follows. The following demo shows how to extend the Date Field component by adding an `onAccept` prop, which is a debounced version of `onChange`. -You can find more information about the `onAccept` prop [in the dedicated doc section](/x/react-date-pickers/lifecycle/#lifecycle-on-pickers-onaccept). +You can find more information about the `onAccept` prop [in the dedicated doc section](/x/react-date-pickers/lifecycle/#lifecycle-on-pickers-quot-onaccept-quot). {{"demo": "ServerInteraction.js"}} diff --git a/docs/data/date-pickers/localization/data.json b/docs/data/date-pickers/localization/data.json index 6c2afaec1d785..da12d1f4ffe42 100644 --- a/docs/data/date-pickers/localization/data.json +++ b/docs/data/date-pickers/localization/data.json @@ -27,7 +27,7 @@ "languageTag": "zh-HK", "importName": "zhHK", "localeName": "Chinese (Hong Kong)", - "missingKeysCount": 1, + "missingKeysCount": 0, "totalKeysCount": 50, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/zhHK.ts" }, @@ -99,7 +99,7 @@ "languageTag": "he-IL", "importName": "heIL", "localeName": "Hebrew", - "missingKeysCount": 1, + "missingKeysCount": 0, "totalKeysCount": 50, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/heIL.ts" }, @@ -267,7 +267,7 @@ "languageTag": "vi-VN", "importName": "viVN", "localeName": "Vietnamese", - "missingKeysCount": 14, + "missingKeysCount": 0, "totalKeysCount": 50, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/viVN.ts" } diff --git a/docs/data/date-pickers/overview/CommonlyUsedComponents.js b/docs/data/date-pickers/overview/CommonlyUsedComponents.js deleted file mode 100644 index b1ddcc788f64d..0000000000000 --- a/docs/data/date-pickers/overview/CommonlyUsedComponents.js +++ /dev/null @@ -1,112 +0,0 @@ -import * as React from 'react'; -import { styled } from '@mui/material/styles'; -import Tooltip from '@mui/material/Tooltip'; -import Stack from '@mui/material/Stack'; -import { DemoContainer, DemoItem } from '@mui/x-date-pickers/internals/demo'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { TimePicker } from '@mui/x-date-pickers/TimePicker'; -import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; -import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; -import { DateTimeRangePicker } from '@mui/x-date-pickers-pro/DateTimeRangePicker'; - -const ProSpan = styled('span')({ - display: 'inline-block', - height: '1em', - width: '1em', - verticalAlign: 'middle', - marginLeft: '0.3em', - marginBottom: '0.08em', - backgroundSize: 'contain', - backgroundRepeat: 'no-repeat', - backgroundImage: 'url(https://mui.com/static/x/pro.svg)', -}); - -function Label({ componentName, valueType, isProOnly }) { - const content = ( - - {componentName} for {valueType} editing - - ); - - if (isProOnly) { - return ( - - - - - - - {content} - - ); - } - - return content; -} - -export default function CommonlyUsedComponents() { - return ( - - - }> - - - }> - - - } - > - - - - } - component="DateRangePicker" - > - - - - } - component="DateTimeRangePicker" - > - - - - - ); -} diff --git a/docs/data/date-pickers/overview/CommonlyUsedComponents.tsx b/docs/data/date-pickers/overview/CommonlyUsedComponents.tsx deleted file mode 100644 index 81fe975bc4773..0000000000000 --- a/docs/data/date-pickers/overview/CommonlyUsedComponents.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import * as React from 'react'; -import { styled } from '@mui/material/styles'; -import Tooltip from '@mui/material/Tooltip'; -import Stack from '@mui/material/Stack'; -import { DemoContainer, DemoItem } from '@mui/x-date-pickers/internals/demo'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { TimePicker } from '@mui/x-date-pickers/TimePicker'; -import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; -import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; -import { DateTimeRangePicker } from '@mui/x-date-pickers-pro/DateTimeRangePicker'; - -const ProSpan = styled('span')({ - display: 'inline-block', - height: '1em', - width: '1em', - verticalAlign: 'middle', - marginLeft: '0.3em', - marginBottom: '0.08em', - backgroundSize: 'contain', - backgroundRepeat: 'no-repeat', - backgroundImage: 'url(https://mui.com/static/x/pro.svg)', -}); - -function Label({ - componentName, - valueType, - isProOnly, -}: { - componentName: string; - valueType: string; - isProOnly?: boolean; -}) { - const content = ( - - {componentName} for {valueType} editing - - ); - - if (isProOnly) { - return ( - - - - - - - {content} - - ); - } - - return content; -} - -export default function CommonlyUsedComponents() { - return ( - - - }> - - - }> - - - } - > - - - - } - component="DateRangePicker" - > - - - - } - component="DateTimeRangePicker" - > - - - - - ); -} diff --git a/docs/data/date-pickers/overview/overview.md b/docs/data/date-pickers/overview/overview.md index 8c73477e1a0a3..f3cd40be4bc75 100644 --- a/docs/data/date-pickers/overview/overview.md +++ b/docs/data/date-pickers/overview/overview.md @@ -7,68 +7,16 @@ materialDesign: https://m2.material.io/components/date-pickers waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/ --- -# MUIĀ X Date and Time Pickers - -

These react date picker and time picker components let users select date or time values.

- -{{"component": "@mui/docs/ComponentLinkHeader"}} - -## Overview - -{{"demo": "CommonlyUsedComponents.js"}} - -## Community or Pro plan? - -The Date and Time Pickers are available in two packages: - -- `@mui/x-date-pickers`, which is MIT licensed (free forever) and contains all the components to edit a date and/or a time. -- `@mui/x-date-pickers-pro`, which is [commercially licensed](/x/introduction/licensing/#pro-plan) and contains additional components to edit date and/or time ranges. - -## Date library - -The Date and Time Pickers are focused on UI/UX and, like most other picker components available, require a third-party library to format, parse, and mutate dates. - -MUI's components let you choose which library you prefer for this purpose. -This gives you the flexibility to implement any date library you may already be using in your application, without adding an extra one to your bundle. +{{"component": "modules/components/overview/XLogo.tsx"}} -To achieve this, both `@mui/x-date-pickers` and `@mui/x-date-pickers-pro` export a set of **adapters** that expose the date manipulation libraries under a unified API. - -### Available libraries - -The Date and Time Pickers currently support the following date libraries: - -- [Day.js](https://day.js.org/) -- [date-fns](https://date-fns.org/) -- [Luxon](https://moment.github.io/luxon/#/) -- [Moment.js](https://momentjs.com/) - -:::info -If you are using a non-Gregorian calendar (such as Jalali or Hijri), please refer to the [Support for other calendar systems](/x/react-date-pickers/calendar-systems/) page. -::: - -### Recommended library - -If you are already using one of the libraries listed above in your application, then you can keep using it with the Date and Time Pickers as well. -This will avoid bundling two libraries. - -If you don't have your own requirements or don't manipulate dates outside of MUIĀ X components, then the recommendation is to use `dayjs` because it has the smallest impact on your application's bundle size. - -Here is the weight added to your gzipped bundle size by each of these libraries when used inside the Date and Time Pickers: - -| Library | Gzipped size | -| :---------------- | -----------: | -| `dayjs@1.11.5` | 6.77 kB | -| `date-fns@2.29.3` | 19.39 kB | -| `luxon@3.0.4` | 23.26 kB | -| `moment@2.29.4` | 20.78 kB | - -:::info -The results above were obtained in October 2022 with the latest version of each library. -The bundling of the JavaScript modules was done by a Create React App, and no locale was loaded for any of the libraries. +# MUIĀ X Date and Time Pickers -The results may vary in your application depending on the version of each library, the locale, and the bundler used. -::: +

A collection of React UI components for selecting dates, times, and ranges.

-## What's next? +{{"component": "modules/components/overview/MainDemo.tsx"}} -Continue to the [next page](/x/react-date-pickers/getting-started/) and learn how to prepare your application for the Date and Time Pickers. +{{"component": "modules/components/overview/FeatureHighlight.tsx"}} +{{"component": "modules/components/overview/CommunityOrPro.tsx"}} +{{"component": "modules/components/overview/Keyboard.tsx"}} +{{"component": "modules/components/overview/Internationalization.tsx"}} +{{"component": "modules/components/overview/DateLibraries.tsx"}} diff --git a/docs/data/date-pickers/timezone/timezone.md b/docs/data/date-pickers/timezone/timezone.md index a095bfc9a124b..5e1eb1fe02f7e 100644 --- a/docs/data/date-pickers/timezone/timezone.md +++ b/docs/data/date-pickers/timezone/timezone.md @@ -11,7 +11,7 @@ packageName: '@mui/x-date-pickers'

Date and Time Pickers support UTC and timezones.

:::warning -UTC and timezone support is an ongoing topic. +UTC and timezones support is an ongoing effort. Only `AdapterDayjs`, `AdapterLuxon` and `AdapterMoment` are currently compatible with UTC dates and timezones. ::: @@ -184,7 +184,7 @@ const date2 = DateTime.fromISO('2022-04-17T15:30', { zone: 'UTC' }); const date3 = DateTime.fromSQL('2022-04-17 15:30:00', { zone: 'UTC' }); ``` -Please check out the documentation of the [UTC and timezone on Luxon](https://moment.github.io/luxon/#/zones) for more details. +Please check out the documentation of the [UTC and timezones on Luxon](https://moment.github.io/luxon/#/zones) for more details. ::: You can then pass your UTC date to your picker: @@ -234,7 +234,7 @@ const date1 = DateTime.fromISO('2022-04-17T15:30', { zone: 'America/New_York' }) const date2 = DateTime.fromSQL('2022-04-17 15:30:00', { zone: 'America/New_York' }); ``` -Please check out the documentation of the [UTC and timezone on Luxon](https://moment.github.io/luxon/#/zones) for more details. +Please check out the documentation of the [UTC and timezones on Luxon](https://moment.github.io/luxon/#/zones) for more details. ::: You can then pass your date in the wanted timezone to your picker: @@ -262,7 +262,7 @@ function App() { {{"demo": "LuxonTimezone.js", "defaultCodeOpen": false}} :::info -Please check out the documentation of the [UTC and timezone on Luxon](https://moment.github.io/luxon/#/zones) for more details on how to manipulate the timezones. +Please check out the documentation of the [UTC and timezones on Luxon](https://moment.github.io/luxon/#/zones) for more details on how to manipulate the timezones. ::: ## Usage with Moment diff --git a/docs/data/date-pickers/validation/validation.md b/docs/data/date-pickers/validation/validation.md index 2f9bb23631e63..f1cead013c477 100644 --- a/docs/data/date-pickers/validation/validation.md +++ b/docs/data/date-pickers/validation/validation.md @@ -19,7 +19,7 @@ But the same props are available on: - all the other variants of this picker; - For exampleā€”the validation props showcased with `DatePicker` are also available on: + For example, the validation props showcased with `DatePicker` are also available on: - `DesktopDatePicker` - `MobileDatePicker` @@ -27,21 +27,21 @@ But the same props are available on: - the field used by this picker; - For exampleā€”the validation props showcased with `DatePicker` are also available on `DateField`. + For example, the validation props showcased with `DatePicker` are also available on `DateField`. - the view components; - For exampleā€”the validation props showcased with `TimePicker` are also available on `TimeClock` and `DigitalClock`. + For example, the validation props showcased with `TimePicker` are also available on `TimeClock` and `DigitalClock`. ::: ## Invalid values feedback -On the fieldā€”it enables its error state. +On the field, it enables its error state. {{"demo": "ValidationBehaviorInput.js", "defaultCodeOpen": false}} -On the calendar and clock viewsā€”the invalid values are displayed as disabled to prevent their selection. +On the calendar and clock views, the invalid values are displayed as disabled to prevent their selection. {{"demo": "ValidationBehaviorView.js", "defaultCodeOpen": false}} @@ -52,20 +52,20 @@ All pickers support the past and future validation. The `disablePast` prop prevents the selection all values before today for date pickers and the selection of all values before the current time for time pickers. For date time pickers, it will combine both. -- On the `day` viewā€”all the days before today won't be selectable. -- On the `month` and `year` viewsā€”all the values ending before today won't be selectable. -- On the `hours` and `minutes` viewsā€”all the values ending before the current time won't be selectable. -- On the `seconds` viewā€”all the values before the current second won't be selectable. +- On the `day` view, all the days before today won't be selectable. +- On the `month` and `year` views, all the values ending before today won't be selectable. +- On the `hours` and `minutes` views, all the values ending before the current time won't be selectable. +- On the `seconds` view, all the values before the current second won't be selectable. {{"demo": "DateValidationDisablePast.js", "defaultCodeOpen": false}} The `disableFuture` prop prevents the selection all values after today for date pickers and the selection of all values after the current time for time pickers. For date time pickers, it will combine both. -- On the `day` viewā€”all the days after today won't be selectable. -- On the `month` and `year` viewsā€”all the values beginning after today won't be selectable. -- On the `hours` and `minutes` viewsā€”all the values beginning after the current time won't be selectable. -- On the `seconds` viewā€”all the values after the current second won't be selectable. +- On the `day` view, all the days after today won't be selectable. +- On the `month` and `year` views, all the values beginning after today won't be selectable. +- On the `hours` and `minutes` views, all the values beginning after the current time won't be selectable. +- On the `seconds` view, all the values after the current second won't be selectable. {{"demo": "DateValidationDisableFuture.js", "defaultCodeOpen": false}} @@ -82,8 +82,8 @@ All the props described below are available on all the components supporting dat The `minDate` prop prevents the selection of all values before `props.minDate`. -- On the `day` viewā€”all the days before the `minDate` won't be selectable. -- On the `month` and `year` viewsā€”all the values ending before the `minDate` won't be selectable. +- On the `day` view, all the days before the `minDate` won't be selectable. +- On the `month` and `year` views, all the values ending before the `minDate` won't be selectable. {{"demo": "DateValidationMinDate.js", "defaultCodeOpen": false}} @@ -93,8 +93,8 @@ The default value of `minDate` is `1900-01-01`. The `maxDate` prop prevents the selection of all values after `props.maxDate`. -- On the `day` viewā€”all the days after the `maxDate` won't be selectable. -- On the `month` and `year` viewsā€”all the values starting after the `maxDate` won't be selectable. +- On the `day` view, all the days after the `maxDate` won't be selectable. +- On the `month` and `year` views, all the values starting after the `maxDate` won't be selectable. {{"demo": "DateValidationMaxDate.js", "defaultCodeOpen": false}} @@ -106,13 +106,13 @@ The default value of `maxDate` is `2099-12-31`. The `shouldDisableDate` prop prevents the selection of all dates for which it returns `true`. -In the example belowā€”the weekends are not selectable: +In the example below, the weekends are not selectable: {{"demo": "DateValidationShouldDisableDate.js", "defaultCodeOpen": false}} :::warning `shouldDisableDate` only prevents the selection of disabled dates on the `day` view. -For performance reasonsā€”when rendering the `month` viewā€”we are not calling the callback for every day of each month to see which one should be disabled (same for the `year` view). +For performance reasons, when rendering the `month` view, we are not calling the callback for every day of each month to see which one should be disabled (same for the `year` view). If you know that all days of some months are disabled, you can provide the [`shouldDisableMonth`](#disable-specific-months) prop to disable them in the `month` view. Same with the [`shouldDisableYear`](#disable-specific-years) prop for the `year` view. @@ -124,9 +124,9 @@ Please note that `shouldDisableDate` will execute on every date rendered in the #### Disable specific dates in range components [](/x/introduction/licensing/#pro-plan) -For components supporting date range edition (`DateRangePicker`, `DateTimeRangePicker`)ā€”the `shouldDisableDate` prop receives a second argument to differentiate the start and the end date. +For components supporting date range edition (`DateRangePicker`, `DateTimeRangePicker`), the `shouldDisableDate` prop receives a second argument to differentiate the start and the end date. -In the example belowā€”the start date cannot be in the weekend but the end date can. +In the example below, the start date cannot be in the weekend but the end date can. {{"demo": "DateRangeValidationShouldDisableDate.js", "defaultCodeOpen": false}} @@ -192,7 +192,7 @@ shouldDisableTime={(value, view) => } ``` -In the example belowā€”the last quarter of each hour is not selectable. +In the example below, the last quarter of each hour is not selectable. {{"demo": "TimeValidationShouldDisableTime.js", "defaultCodeOpen": false}} @@ -234,7 +234,7 @@ For now, you cannot use `maxDateTime` and `maxTime` together. ## Show the error -To render the current errorā€”you can subscribe to the `onError` callback which is called every time the error changes. +To render the current error, you can subscribe to the `onError` callback which is called every time the error changes. You can then use the `helperText` prop of the `TextField` to pass your error message to your input as shown below. Try to type a date that is inside the first quarter of 2022ā€”the error will go away. diff --git a/docs/data/introduction/licensing/licensing.md b/docs/data/introduction/licensing/licensing.md index 14142fdb118cc..d5aa641adefa1 100644 --- a/docs/data/introduction/licensing/licensing.md +++ b/docs/data/introduction/licensing/licensing.md @@ -106,21 +106,21 @@ You can also use it for the development of code not intended for production (for You don't need to contact us to use these components for the above cases. You will need to purchase a commercial license in order to remove the watermarks and console warnings. -## How many developer seats do I need? +## How many developer licenses do I need? -The number of seats purchased on your license must correspond to the number of concurrent developers contributing changes to the front-end code of the project that uses MUIĀ X Pro or Premium. +The number of licenses purchased must correspond to the number of concurrent developers contributing changes to the front-end code of the project that uses MUIĀ X Pro or Premium. - **Example 1.** Company 'A' is developing an application named 'AppA'. The app needs to render 10K rows of data in a table and allow users to group, filter, and sort. The dev team adds MUIĀ X Pro to the project to satisfy this requirement. Five front-end and ten back-end developers are working on 'AppA'. Only one developer is tasked with maintaining the Data Grid, but there are five total developers who work on the front-end. - Company 'A' must purchase five seats. + Company 'A' must purchase five licenses. - **Example 2.** A UI development team at Company 'B' creates its own UI library for internal development that includes MUIĀ X Pro components. The teams working on 'AppY' and 'AppZ' both adopt this new library. 'AppY' has five front-end developers, and 'AppZ' has three; additionally, there are two front-end developers on the company's UI development team. - Company 'B' must purchase ten seats. + Company 'B' must purchase ten licenses. This is [the relevant clause in the EULA.](https://mui.com/legal/mui-x-eula/#required-quantity-of-licenses) diff --git a/docs/data/introduction/roadmap/roadmap.md b/docs/data/introduction/roadmap/roadmap.md index 166c0c5ed07ed..7aa56833ef053 100644 --- a/docs/data/introduction/roadmap/roadmap.md +++ b/docs/data/introduction/roadmap/roadmap.md @@ -4,7 +4,7 @@ ## MUIĀ X roadmap -To learn more about our plans for MUIĀ X, visit the [public roadmap](https://github.com/mui/mui-x/projects/1). +To learn more about our plans for MUIĀ X, visit the [public roadmap](https://github.com/orgs/mui/projects/35). :::warning We operate in a dynamic environment,so things are subject to change. diff --git a/docs/data/introduction/support/support.md b/docs/data/introduction/support/support.md index 2977168121811..3f65903d7dfbf 100644 --- a/docs/data/introduction/support/support.md +++ b/docs/data/introduction/support/support.md @@ -94,13 +94,12 @@ This includes issues introduced by external sources, like browser upgrades or ch ### Supported versions -- MUIĀ X v7: āœ… Stable major (Continuous support). -- MUIĀ X v6: āš ļø Long-term support (Guaranteed Support for security issues and regressions). -- MUIĀ X v5: šŸ…§ No longer supported. -- MUIĀ X v4: šŸ…§ No longer supported. -- MUIĀ X v3: šŸ…§ Never existed. -- MUIĀ X v2: šŸ…§ Never existed. -- MUIĀ X v1: šŸ…§ Never existed. +| MUIĀ X version | Release | Supported | +| ------------: | :--------- | :------------------------------------------------------------------ | +| ^7.0.0 | 2024-03-23 | āœ… Stable major (Continuous support) | +| ^6.0.0 | 2023-03-03 | āš ļø Long-term support (Support for security issues and regressions). | +| ^5.0.0 | 2021-11-23 | āŒ | +| ^4.0.0 | 2021-09-28 | āŒ | ## Community diff --git a/docs/data/pages.ts b/docs/data/pages.ts index 62ed986c8ca8c..dfa4c550422ee 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -1,4 +1,4 @@ -import type { MuiPage } from '@mui/monorepo/docs/src/MuiPage'; +import type { MuiPage } from 'docs/src/MuiPage'; import dataGridComponentApi from './data-grid-component-api-pages'; import pickersComponentApi from './date-pickers-component-api-pages'; import chartsComponentApi from './charts-component-api-pages'; @@ -354,7 +354,7 @@ const pages: MuiPage[] = [ }, { pathname: '/x/react-date-pickers/timezone', - title: 'UTC and timezone', + title: 'UTC and timezones', }, { pathname: '/x/react-date-pickers/calendar-systems' }, ], @@ -510,6 +510,7 @@ const pages: MuiPage[] = [ { pathname: '/x/react-tree-view/rich-tree-view/expansion' }, { pathname: '/x/react-tree-view/rich-tree-view/customization' }, { pathname: '/x/react-tree-view/rich-tree-view/focus' }, + { pathname: '/x/react-tree-view/rich-tree-view/editing' }, { pathname: '/x/react-tree-view/rich-tree-view/ordering', plan: 'pro' }, ], }, diff --git a/docs/data/tree-view-component-api-pages.ts b/docs/data/tree-view-component-api-pages.ts index 61b41b8740452..c46698f0ed3dd 100644 --- a/docs/data/tree-view-component-api-pages.ts +++ b/docs/data/tree-view-component-api-pages.ts @@ -1,4 +1,4 @@ -import type { MuiPage } from '@mui/monorepo/docs/src/MuiPage'; +import type { MuiPage } from 'docs/src/MuiPage'; const apiPages: MuiPage[] = [ { diff --git a/docs/data/tree-view/rich-tree-view/customization/CustomStyling.js b/docs/data/tree-view/rich-tree-view/customization/CustomStyling.js index 9f943fa5f5545..eedcc6b8fe7b3 100644 --- a/docs/data/tree-view/rich-tree-view/customization/CustomStyling.js +++ b/docs/data/tree-view/rich-tree-view/customization/CustomStyling.js @@ -35,10 +35,7 @@ const MUI_X_PRODUCTS = [ ]; const CustomTreeItem = styled(TreeItem)(({ theme }) => ({ - color: - theme.palette.mode === 'light' - ? theme.palette.grey[800] - : theme.palette.grey[200], + color: theme.palette.grey[200], [`& .${treeItemClasses.content}`]: { borderRadius: theme.spacing(0.5), padding: theme.spacing(0.5, 1), @@ -50,18 +47,23 @@ const CustomTreeItem = styled(TreeItem)(({ theme }) => ({ }, [`& .${treeItemClasses.iconContainer}`]: { borderRadius: '50%', - backgroundColor: - theme.palette.mode === 'light' - ? alpha(theme.palette.primary.main, 0.25) - : theme.palette.primary.dark, - color: theme.palette.mode === 'dark' && theme.palette.primary.contrastText, + backgroundColor: theme.palette.primary.dark, padding: theme.spacing(0, 1.2), + ...theme.applyStyles('light', { + backgroundColor: alpha(theme.palette.primary.main, 0.25), + }), + ...theme.applyStyles('dark', { + color: theme.palette.primary.contrastText, + }), }, [`& .${treeItemClasses.groupTransition}`]: { marginLeft: 15, paddingLeft: 18, borderLeft: `1px dashed ${alpha(theme.palette.text.primary, 0.4)}`, }, + ...theme.applyStyles('light', { + color: theme.palette.grey[800], + }), })); export default function CustomStyling() { diff --git a/docs/data/tree-view/rich-tree-view/customization/CustomStyling.tsx b/docs/data/tree-view/rich-tree-view/customization/CustomStyling.tsx index dd1a123cb753c..de8fc4bd6f5cf 100644 --- a/docs/data/tree-view/rich-tree-view/customization/CustomStyling.tsx +++ b/docs/data/tree-view/rich-tree-view/customization/CustomStyling.tsx @@ -36,11 +36,7 @@ const MUI_X_PRODUCTS: TreeViewBaseItem[] = [ ]; const CustomTreeItem = styled(TreeItem)(({ theme }) => ({ - color: - theme.palette.mode === 'light' - ? theme.palette.grey[800] - : theme.palette.grey[200], - + color: theme.palette.grey[200], [`& .${treeItemClasses.content}`]: { borderRadius: theme.spacing(0.5), padding: theme.spacing(0.5, 1), @@ -52,18 +48,23 @@ const CustomTreeItem = styled(TreeItem)(({ theme }) => ({ }, [`& .${treeItemClasses.iconContainer}`]: { borderRadius: '50%', - backgroundColor: - theme.palette.mode === 'light' - ? alpha(theme.palette.primary.main, 0.25) - : theme.palette.primary.dark, - color: theme.palette.mode === 'dark' && theme.palette.primary.contrastText, + backgroundColor: theme.palette.primary.dark, padding: theme.spacing(0, 1.2), + ...theme.applyStyles('light', { + backgroundColor: alpha(theme.palette.primary.main, 0.25), + }), + ...theme.applyStyles('dark', { + color: theme.palette.primary.contrastText, + }), }, [`& .${treeItemClasses.groupTransition}`]: { marginLeft: 15, paddingLeft: 18, borderLeft: `1px dashed ${alpha(theme.palette.text.primary, 0.4)}`, }, + ...theme.applyStyles('light', { + color: theme.palette.grey[800], + }), })); export default function CustomStyling() { diff --git a/docs/data/tree-view/rich-tree-view/customization/FileExplorer.js b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.js index a02a1e86667bd..45d30698d9307 100644 --- a/docs/data/tree-view/rich-tree-view/customization/FileExplorer.js +++ b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.js @@ -80,14 +80,14 @@ function DotIcon() { } const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ - color: - theme.palette.mode === 'light' - ? theme.palette.grey[800] - : theme.palette.grey[400], + color: theme.palette.grey[400], position: 'relative', [`& .${treeItemClasses.groupTransition}`]: { marginLeft: theme.spacing(3.5), }, + ...theme.applyStyles('light', { + color: theme.palette.grey[800], + }), })); const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ @@ -100,10 +100,10 @@ const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ fontWeight: 500, [`&.Mui-expanded `]: { '&:not(.Mui-focused, .Mui-selected, .Mui-selected.Mui-focused) .labelIcon': { - color: - theme.palette.mode === 'light' - ? theme.palette.primary.main - : theme.palette.primary.dark, + color: theme.palette.primary.dark, + ...theme.applyStyles('light', { + color: theme.palette.primary.main, + }), }, '&::before': { content: '""', @@ -113,22 +113,25 @@ const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ top: '44px', height: 'calc(100% - 48px)', width: '1.5px', - backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.grey[300] - : theme.palette.grey[700], + backgroundColor: theme.palette.grey[700], + ...theme.applyStyles('light', { + backgroundColor: theme.palette.grey[300], + }), }, }, '&:hover': { backgroundColor: alpha(theme.palette.primary.main, 0.1), - color: theme.palette.mode === 'light' ? theme.palette.primary.main : 'white', + color: 'white', + ...theme.applyStyles('light', { + color: theme.palette.primary.main, + }), }, [`&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused`]: { - backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.primary.main - : theme.palette.primary.dark, + backgroundColor: theme.palette.primary.dark, color: theme.palette.primary.contrastText, + ...theme.applyStyles('light', { + backgroundColor: theme.palette.primary.main, + }), }, })); diff --git a/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx index b57e0a3fb7204..70cb94cbb040b 100644 --- a/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx +++ b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx @@ -98,14 +98,14 @@ declare module 'react' { } const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ - color: - theme.palette.mode === 'light' - ? theme.palette.grey[800] - : theme.palette.grey[400], + color: theme.palette.grey[400], position: 'relative', [`& .${treeItemClasses.groupTransition}`]: { marginLeft: theme.spacing(3.5), }, + ...theme.applyStyles('light', { + color: theme.palette.grey[800], + }), })) as unknown as typeof TreeItem2Root; const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ @@ -118,10 +118,10 @@ const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ fontWeight: 500, [`&.Mui-expanded `]: { '&:not(.Mui-focused, .Mui-selected, .Mui-selected.Mui-focused) .labelIcon': { - color: - theme.palette.mode === 'light' - ? theme.palette.primary.main - : theme.palette.primary.dark, + color: theme.palette.primary.dark, + ...theme.applyStyles('light', { + color: theme.palette.primary.main, + }), }, '&::before': { content: '""', @@ -131,22 +131,25 @@ const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ top: '44px', height: 'calc(100% - 48px)', width: '1.5px', - backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.grey[300] - : theme.palette.grey[700], + backgroundColor: theme.palette.grey[700], + ...theme.applyStyles('light', { + backgroundColor: theme.palette.grey[300], + }), }, }, '&:hover': { backgroundColor: alpha(theme.palette.primary.main, 0.1), - color: theme.palette.mode === 'light' ? theme.palette.primary.main : 'white', + color: 'white', + ...theme.applyStyles('light', { + color: theme.palette.primary.main, + }), }, [`&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused`]: { - backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.primary.main - : theme.palette.primary.dark, + backgroundColor: theme.palette.primary.dark, color: theme.palette.primary.contrastText, + ...theme.applyStyles('light', { + backgroundColor: theme.palette.primary.main, + }), }, })); diff --git a/docs/data/tree-view/rich-tree-view/editing/ApiMethodUpdateItemLabel.js b/docs/data/tree-view/rich-tree-view/editing/ApiMethodUpdateItemLabel.js new file mode 100644 index 0000000000000..6d8aff8043d64 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/ApiMethodUpdateItemLabel.js @@ -0,0 +1,35 @@ +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'; +import { MUI_X_PRODUCTS } from './products'; + +export default function ApiMethodUpdateItemLabel() { + const [isLabelUpdated, setIsLabelUpdated] = React.useState(false); + const apiRef = useTreeViewApiRef(); + + const handleUpdateLabel = () => { + if (isLabelUpdated) { + apiRef.current.updateItemLabel('grid', 'Data Grid'); + setIsLabelUpdated(false); + } else { + apiRef.current.updateItemLabel('grid', 'New Label'); + setIsLabelUpdated(true); + } + }; + + return ( + + + + + + + + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/ApiMethodUpdateItemLabel.tsx b/docs/data/tree-view/rich-tree-view/editing/ApiMethodUpdateItemLabel.tsx new file mode 100644 index 0000000000000..69a4fb59308fa --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/ApiMethodUpdateItemLabel.tsx @@ -0,0 +1,35 @@ +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'; +import { MUI_X_PRODUCTS } from './products'; + +export default function ApiMethodUpdateItemLabel() { + const [isLabelUpdated, setIsLabelUpdated] = React.useState(false); + const apiRef = useTreeViewApiRef(); + + const handleUpdateLabel = () => { + if (isLabelUpdated) { + apiRef.current!.updateItemLabel('grid', 'Data Grid'); + setIsLabelUpdated(false); + } else { + apiRef.current!.updateItemLabel('grid', 'New Label'); + setIsLabelUpdated(true); + } + }; + + return ( + + + + + + + + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/ApiMethodUpdateItemLabel.tsx.preview b/docs/data/tree-view/rich-tree-view/editing/ApiMethodUpdateItemLabel.tsx.preview new file mode 100644 index 0000000000000..e10c7e1b68f48 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/ApiMethodUpdateItemLabel.tsx.preview @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.js b/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.js new file mode 100644 index 0000000000000..4c52e4c94ec83 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.js @@ -0,0 +1,44 @@ +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'; + +import { MUI_X_PRODUCTS } from './products'; + +const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { + const { interactions } = useTreeItem2Utils({ + itemId: props.itemId, + children: props.children, + }); + + const handleInputBlur = (event) => { + interactions.handleCancelItemLabelEditing(event); + }; + + return ( + + ); +}); + +export default function CustomBehavior() { + return ( + + + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.tsx b/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.tsx new file mode 100644 index 0000000000000..b8b44e50cca64 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.tsx @@ -0,0 +1,47 @@ +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, TreeItem2Props } from '@mui/x-tree-view/TreeItem2'; +import { UseTreeItem2LabelInputSlotOwnProps } from '@mui/x-tree-view/useTreeItem2'; +import { MUI_X_PRODUCTS } from './products'; + +const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( + props: TreeItem2Props, + ref: React.Ref, +) { + const { interactions } = useTreeItem2Utils({ + itemId: props.itemId, + children: props.children, + }); + + const handleInputBlur: UseTreeItem2LabelInputSlotOwnProps['onBlur'] = (event) => { + interactions.handleCancelItemLabelEditing(event); + }; + + return ( + + ); +}); + +export default function CustomBehavior() { + return ( + + + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.tsx.preview b/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.tsx.preview new file mode 100644 index 0000000000000..c8745d2d8df84 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.tsx.preview @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/docs/data/tree-view/rich-tree-view/editing/CustomLabelInput.js b/docs/data/tree-view/rich-tree-view/editing/CustomLabelInput.js new file mode 100644 index 0000000000000..85ac9e0ba6b15 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/CustomLabelInput.js @@ -0,0 +1,185 @@ +import * as React from 'react'; +import { styled } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import CheckIcon from '@mui/icons-material/Check'; +import IconButton from '@mui/material/IconButton'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; +import { TreeItem2, TreeItem2Label } from '@mui/x-tree-view/TreeItem2'; +import { unstable_useTreeItem2 as useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; + +const StyledLabelInput = styled('input')(({ theme }) => ({ + ...theme.typography.body1, + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + border: 'none', + padding: '0 2px', + boxSizing: 'border-box', + width: 100, + '&:focus': { + outline: `1px solid ${theme.palette.primary.main}`, + }, +})); + +export const ITEMS = [ + { + id: '1', + firstName: 'Jane', + lastName: 'Doe', + editable: true, + children: [ + { id: '1.1', firstName: 'Elena', lastName: 'Kim', editable: true }, + { id: '1.2', firstName: 'Noah', lastName: 'Rodriguez', editable: true }, + { id: '1.3', firstName: 'Maya', lastName: 'Patel', editable: true }, + ], + }, + { + id: '2', + firstName: 'Liam', + lastName: 'Clarke', + editable: true, + children: [ + { + id: '2.1', + firstName: 'Ethan', + lastName: 'Lee', + editable: true, + }, + { id: '2.2', firstName: 'Ava', lastName: 'Jones', editable: true }, + ], + }, +]; + +function Label({ children, ...other }) { + return ( + + {children} + + ); +} + +const LabelInput = React.forwardRef(function LabelInput( + { item, handleCancelItemLabelEditing, handleSaveItemLabel, ...props }, + ref, +) { + const [initialNameValue, setInitialNameValue] = React.useState({ + firstName: item.firstName, + lastName: item.lastName, + }); + const [nameValue, setNameValue] = React.useState({ + firstName: item.firstName, + lastName: item.lastName, + }); + + const handleFirstNameChange = (event) => { + setNameValue((prev) => ({ ...prev, firstName: event.target.value })); + }; + const handleLastNameChange = (event) => { + setNameValue((prev) => ({ ...prev, lastName: event.target.value })); + }; + + const reset = () => { + setNameValue(initialNameValue); + }; + const save = () => { + setInitialNameValue(nameValue); + }; + + return ( + + + + { + handleSaveItemLabel(event, `${nameValue.firstName} ${nameValue.lastName}`); + save(); + }} + > + + + { + handleCancelItemLabelEditing(event); + reset(); + }} + > + + + + ); +}); + +const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { + const { interactions } = useTreeItem2Utils({ + itemId: props.itemId, + children: props.children, + }); + const { publicAPI } = useTreeItem2(props); + + const handleInputBlur = (event) => { + event.defaultMuiPrevented = true; + }; + + const handleInputKeyDown = (event) => { + event.defaultMuiPrevented = true; + }; + + return ( + + ); +}); + +export default function CustomLabelInput() { + return ( + + `${item.firstName} ${item.lastName}`} + /> + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/CustomLabelInput.tsx b/docs/data/tree-view/rich-tree-view/editing/CustomLabelInput.tsx new file mode 100644 index 0000000000000..961a87baff103 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/CustomLabelInput.tsx @@ -0,0 +1,217 @@ +import * as React from 'react'; +import { styled } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import CheckIcon from '@mui/icons-material/Check'; +import IconButton from '@mui/material/IconButton'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; +import { + TreeItem2, + TreeItem2Label, + TreeItem2Props, +} from '@mui/x-tree-view/TreeItem2'; +import { + UseTreeItem2LabelInputSlotOwnProps, + UseTreeItem2LabelSlotOwnProps, + unstable_useTreeItem2 as useTreeItem2, +} from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; +import { TreeViewBaseItem } from '@mui/x-tree-view/models'; + +const StyledLabelInput = styled('input')(({ theme }) => ({ + ...theme.typography.body1, + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + border: 'none', + padding: '0 2px', + boxSizing: 'border-box', + width: 100, + '&:focus': { + outline: `1px solid ${theme.palette.primary.main}`, + }, +})); + +type ExtendedTreeItemProps = { + editable?: boolean; + id: string; + firstName: string; + lastName: string; +}; + +export const ITEMS: TreeViewBaseItem[] = [ + { + id: '1', + firstName: 'Jane', + lastName: 'Doe', + editable: true, + children: [ + { id: '1.1', firstName: 'Elena', lastName: 'Kim', editable: true }, + { id: '1.2', firstName: 'Noah', lastName: 'Rodriguez', editable: true }, + { id: '1.3', firstName: 'Maya', lastName: 'Patel', editable: true }, + ], + }, + { + id: '2', + firstName: 'Liam', + lastName: 'Clarke', + editable: true, + children: [ + { + id: '2.1', + firstName: 'Ethan', + lastName: 'Lee', + editable: true, + }, + { id: '2.2', firstName: 'Ava', lastName: 'Jones', editable: true }, + ], + }, +]; + +function Label({ children, ...other }: UseTreeItem2LabelSlotOwnProps) { + return ( + + {children} + + ); +} + +interface CustomLabelInputProps extends UseTreeItem2LabelInputSlotOwnProps { + handleCancelItemLabelEditing: (event: React.SyntheticEvent) => void; + handleSaveItemLabel: (event: React.SyntheticEvent, label: string) => void; + item: TreeViewBaseItem; +} + +const LabelInput = React.forwardRef(function LabelInput( + { + item, + handleCancelItemLabelEditing, + handleSaveItemLabel, + ...props + }: Omit, + ref: React.Ref, +) { + const [initialNameValue, setInitialNameValue] = React.useState({ + firstName: item.firstName, + lastName: item.lastName, + }); + const [nameValue, setNameValue] = React.useState({ + firstName: item.firstName, + lastName: item.lastName, + }); + + const handleFirstNameChange = (event: React.ChangeEvent) => { + setNameValue((prev) => ({ ...prev, firstName: event.target.value })); + }; + const handleLastNameChange = (event: React.ChangeEvent) => { + setNameValue((prev) => ({ ...prev, lastName: event.target.value })); + }; + + const reset = () => { + setNameValue(initialNameValue); + }; + const save = () => { + setInitialNameValue(nameValue); + }; + + return ( + + + + { + handleSaveItemLabel(event, `${nameValue.firstName} ${nameValue.lastName}`); + save(); + }} + > + + + { + handleCancelItemLabelEditing(event); + reset(); + }} + > + + + + ); +}); + +const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( + props: TreeItem2Props, + ref: React.Ref, +) { + const { interactions } = useTreeItem2Utils({ + itemId: props.itemId, + children: props.children, + }); + const { publicAPI } = useTreeItem2(props); + + const handleInputBlur: UseTreeItem2LabelInputSlotOwnProps['onBlur'] = (event) => { + event.defaultMuiPrevented = true; + }; + + const handleInputKeyDown: UseTreeItem2LabelInputSlotOwnProps['onKeyDown'] = ( + event, + ) => { + event.defaultMuiPrevented = true; + }; + + return ( + + ); +}); + +export default function CustomLabelInput() { + return ( + + `${item.firstName} ${item.lastName}`} + /> + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/CustomLabelInput.tsx.preview b/docs/data/tree-view/rich-tree-view/editing/CustomLabelInput.tsx.preview new file mode 100644 index 0000000000000..9712852c253a2 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/CustomLabelInput.tsx.preview @@ -0,0 +1,8 @@ + `${item.firstName} ${item.lastName}`} +/> \ No newline at end of file diff --git a/docs/data/tree-view/rich-tree-view/editing/EditLeaves.js b/docs/data/tree-view/rich-tree-view/editing/EditLeaves.js new file mode 100644 index 0000000000000..041b884099746 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/EditLeaves.js @@ -0,0 +1,54 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { useTreeViewApiRef } from '@mui/x-tree-view/hooks'; + +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 EditLeaves() { + const apiRef = useTreeViewApiRef(); + return ( + + + apiRef.current.getItemOrderedChildrenIds(item.id).length === 0 + } + defaultExpandedItems={['grid', 'pickers']} + /> + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/EditLeaves.tsx b/docs/data/tree-view/rich-tree-view/editing/EditLeaves.tsx new file mode 100644 index 0000000000000..6d1a2ef9997e7 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/EditLeaves.tsx @@ -0,0 +1,61 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { useTreeViewApiRef } from '@mui/x-tree-view/hooks'; +import { TreeViewBaseItem } from '@mui/x-tree-view/models'; + +type ExtendedTreeItemProps = { + editable?: boolean; + id: string; + label: string; +}; + +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 EditLeaves() { + const apiRef = useTreeViewApiRef(); + return ( + + + apiRef.current!.getItemOrderedChildrenIds(item.id).length === 0 + } + defaultExpandedItems={['grid', 'pickers']} + /> + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/EditLeaves.tsx.preview b/docs/data/tree-view/rich-tree-view/editing/EditLeaves.tsx.preview new file mode 100644 index 0000000000000..e03aa45b1144f --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/EditLeaves.tsx.preview @@ -0,0 +1,9 @@ + + apiRef.current!.getItemOrderedChildrenIds(item.id).length === 0 + } + defaultExpandedItems={['grid', 'pickers']} +/> \ No newline at end of file diff --git a/docs/data/tree-view/rich-tree-view/editing/EditWithIcons.js b/docs/data/tree-view/rich-tree-view/editing/EditWithIcons.js new file mode 100644 index 0000000000000..e9dd9dc73ab08 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/EditWithIcons.js @@ -0,0 +1,117 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton'; +import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; +import CheckIcon from '@mui/icons-material/Check'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; +import { TreeItem2, TreeItem2Label } from '@mui/x-tree-view/TreeItem2'; +import { TreeItem2LabelInput } from '@mui/x-tree-view/TreeItem2LabelInput'; + +import { MUI_X_PRODUCTS } from './products'; + +function CustomLabel({ editing, editable, children, toggleItemEditing, ...other }) { + return ( + + {children} + {editable && ( + + + + )} + + ); +} + +function CustomLabelInput(props) { + const { handleCancelItemLabelEditing, handleSaveItemLabel, value, ...other } = + props; + + return ( + + + { + handleSaveItemLabel(event, value); + }} + > + + + + + + + ); +} + +const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { + const { interactions, status } = useTreeItem2Utils({ + itemId: props.itemId, + children: props.children, + }); + + const handleContentDoubleClick = (event) => { + event.defaultMuiPrevented = true; + }; + + const handleInputBlur = (event) => { + event.defaultMuiPrevented = true; + }; + + const handleInputKeyDown = (event) => { + event.defaultMuiPrevented = true; + }; + + return ( + + ); +}); + +export default function EditWithIcons() { + return ( + + + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/EditWithIcons.tsx b/docs/data/tree-view/rich-tree-view/editing/EditWithIcons.tsx new file mode 100644 index 0000000000000..2a4f2521d7bbb --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/EditWithIcons.tsx @@ -0,0 +1,149 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton'; +import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; +import CloseRoundedIcon from '@mui/icons-material/CloseRounded'; +import CheckIcon from '@mui/icons-material/Check'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; +import { + TreeItem2, + TreeItem2Label, + TreeItem2Props, +} from '@mui/x-tree-view/TreeItem2'; +import { TreeItem2LabelInput } from '@mui/x-tree-view/TreeItem2LabelInput'; +import { + UseTreeItem2LabelInputSlotOwnProps, + UseTreeItem2LabelSlotOwnProps, +} from '@mui/x-tree-view/useTreeItem2'; +import { MUI_X_PRODUCTS } from './products'; + +interface CustomLabelProps extends UseTreeItem2LabelSlotOwnProps { + editable: boolean; + editing: boolean; + toggleItemEditing: () => void; +} + +function CustomLabel({ + editing, + editable, + children, + toggleItemEditing, + ...other +}: CustomLabelProps) { + return ( + + {children} + {editable && ( + + + + )} + + ); +} + +interface CustomLabelInputProps extends UseTreeItem2LabelInputSlotOwnProps { + handleCancelItemLabelEditing: (event: React.SyntheticEvent) => void; + handleSaveItemLabel: (event: React.SyntheticEvent, label: string) => void; + value: string; +} + +function CustomLabelInput(props: Omit) { + const { handleCancelItemLabelEditing, handleSaveItemLabel, value, ...other } = + props; + + return ( + + + { + handleSaveItemLabel(event, value); + }} + > + + + + + + + ); +} + +const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( + props: TreeItem2Props, + ref: React.Ref, +) { + const { interactions, status } = useTreeItem2Utils({ + itemId: props.itemId, + children: props.children, + }); + + const handleContentDoubleClick: UseTreeItem2LabelSlotOwnProps['onDoubleClick'] = ( + event, + ) => { + event.defaultMuiPrevented = true; + }; + + const handleInputBlur: UseTreeItem2LabelInputSlotOwnProps['onBlur'] = (event) => { + event.defaultMuiPrevented = true; + }; + + const handleInputKeyDown: UseTreeItem2LabelInputSlotOwnProps['onKeyDown'] = ( + event, + ) => { + event.defaultMuiPrevented = true; + }; + + return ( + + ); +}); + +export default function EditWithIcons() { + return ( + + + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/EditWithIcons.tsx.preview b/docs/data/tree-view/rich-tree-view/editing/EditWithIcons.tsx.preview new file mode 100644 index 0000000000000..761e52103ecdd --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/EditWithIcons.tsx.preview @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/docs/data/tree-view/rich-tree-view/editing/EditingCallback.js b/docs/data/tree-view/rich-tree-view/editing/EditingCallback.js new file mode 100644 index 0000000000000..31c2ac48bad75 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/EditingCallback.js @@ -0,0 +1,32 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { MUI_X_PRODUCTS } from './products'; + +export default function EditingCallback() { + const [lastEditedItem, setLastEditedItem] = React.useState(null); + + return ( + + {lastEditedItem ? ( + + The label of item with id {lastEditedItem.itemId} has been edited + to {lastEditedItem.label} + + ) : ( + No item has been edited yet + )} + + setLastEditedItem({ itemId, label })} + /> + + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/EditingCallback.tsx b/docs/data/tree-view/rich-tree-view/editing/EditingCallback.tsx new file mode 100644 index 0000000000000..772ae6e8c55db --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/EditingCallback.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { MUI_X_PRODUCTS } from './products'; + +export default function EditingCallback() { + const [lastEditedItem, setLastEditedItem] = React.useState<{ + itemId: string; + label: string; + } | null>(null); + + return ( + + {lastEditedItem ? ( + + The label of item with id {lastEditedItem!.itemId} has been edited + to {lastEditedItem!.label} + + ) : ( + No item has been edited yet + )} + + setLastEditedItem({ itemId, label })} + /> + + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/LabelEditingAllItems.js b/docs/data/tree-view/rich-tree-view/editing/LabelEditingAllItems.js new file mode 100644 index 0000000000000..e4a7020a9fb5b --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/LabelEditingAllItems.js @@ -0,0 +1,17 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { MUI_X_PRODUCTS } from './products'; + +export default function LabelEditingAllItems() { + return ( + + + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/LabelEditingAllItems.tsx b/docs/data/tree-view/rich-tree-view/editing/LabelEditingAllItems.tsx new file mode 100644 index 0000000000000..e4a7020a9fb5b --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/LabelEditingAllItems.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { MUI_X_PRODUCTS } from './products'; + +export default function LabelEditingAllItems() { + return ( + + + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/LabelEditingAllItems.tsx.preview b/docs/data/tree-view/rich-tree-view/editing/LabelEditingAllItems.tsx.preview new file mode 100644 index 0000000000000..f6140084082a5 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/LabelEditingAllItems.tsx.preview @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/docs/data/tree-view/rich-tree-view/editing/LabelEditingSomeItems.js b/docs/data/tree-view/rich-tree-view/editing/LabelEditingSomeItems.js new file mode 100644 index 0000000000000..a60407f22e05b --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/LabelEditingSomeItems.js @@ -0,0 +1,17 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { MUI_X_PRODUCTS } from './editableProducts'; + +export default function LabelEditingSomeItems() { + return ( + + Boolean(item?.editable)} + experimentalFeatures={{ labelEditing: true }} + defaultExpandedItems={['grid', 'pickers']} + /> + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/LabelEditingSomeItems.tsx b/docs/data/tree-view/rich-tree-view/editing/LabelEditingSomeItems.tsx new file mode 100644 index 0000000000000..a60407f22e05b --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/LabelEditingSomeItems.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { MUI_X_PRODUCTS } from './editableProducts'; + +export default function LabelEditingSomeItems() { + return ( + + Boolean(item?.editable)} + experimentalFeatures={{ labelEditing: true }} + defaultExpandedItems={['grid', 'pickers']} + /> + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/LabelEditingSomeItems.tsx.preview b/docs/data/tree-view/rich-tree-view/editing/LabelEditingSomeItems.tsx.preview new file mode 100644 index 0000000000000..bdd2489ac890b --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/LabelEditingSomeItems.tsx.preview @@ -0,0 +1,6 @@ + Boolean(item?.editable)} + experimentalFeatures={{ labelEditing: true }} + defaultExpandedItems={['grid', 'pickers']} +/> \ No newline at end of file diff --git a/docs/data/tree-view/rich-tree-view/editing/Validation.js b/docs/data/tree-view/rich-tree-view/editing/Validation.js new file mode 100644 index 0000000000000..a851bf3090b56 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/Validation.js @@ -0,0 +1,108 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Tooltip from '@mui/material/Tooltip'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; +import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; + +import { TreeItem2LabelInput } from '@mui/x-tree-view/TreeItem2LabelInput'; +import { MUI_X_PRODUCTS } from './products'; + +const ERRORS = { + REQUIRED: 'The label cannot be empty', + INVALID: 'The label cannot contain digits', +}; + +function CustomLabelInput(props) { + const { error, ...other } = props; + + return ( + + + {error ? ( + + + + ) : ( + + + + )} + + ); +} + +const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { + const [error, setError] = React.useState(null); + const { interactions } = useTreeItem2Utils({ + itemId: props.itemId, + children: props.children, + }); + const validateLabel = (label) => { + if (!label) { + setError('REQUIRED'); + } else if (/\d/.test(label)) { + setError('INVALID'); + } else { + setError(null); + } + }; + + const handleInputBlur = (event) => { + if (error) { + event.defaultMuiPrevented = true; + } + }; + + const handleInputKeyDown = (event) => { + event.defaultMuiPrevented = true; + const target = event.target; + + if (event.key === 'Enter' && target.value) { + if (error) { + return; + } + setError(null); + interactions.handleSaveItemLabel(event, target.value); + } else if (event.key === 'Escape') { + setError(null); + interactions.handleCancelItemLabelEditing(event); + } + }; + + const handleInputChange = (event) => { + validateLabel(event.target.value); + }; + + return ( + + ); +}); + +export default function Validation() { + return ( + + + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/Validation.tsx b/docs/data/tree-view/rich-tree-view/editing/Validation.tsx new file mode 100644 index 0000000000000..450a0b3711dcb --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/Validation.tsx @@ -0,0 +1,117 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Tooltip from '@mui/material/Tooltip'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; +import { TreeItem2, TreeItem2Props } from '@mui/x-tree-view/TreeItem2'; +import { UseTreeItem2LabelInputSlotOwnProps } from '@mui/x-tree-view/useTreeItem2'; +import { TreeItem2LabelInput } from '@mui/x-tree-view/TreeItem2LabelInput'; +import { MUI_X_PRODUCTS } from './products'; + +const ERRORS = { + REQUIRED: 'The label cannot be empty', + INVALID: 'The label cannot contain digits', +}; + +interface CustomLabelInputProps extends UseTreeItem2LabelInputSlotOwnProps { + error: null | keyof typeof ERRORS; +} + +function CustomLabelInput(props: Omit) { + const { error, ...other } = props; + + return ( + + + {error ? ( + + + + ) : ( + + + + )} + + ); +} + +const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( + props: TreeItem2Props, + ref: React.Ref, +) { + const [error, setError] = React.useState(null); + const { interactions } = useTreeItem2Utils({ + itemId: props.itemId, + children: props.children, + }); + const validateLabel = (label: string) => { + if (!label) { + setError('REQUIRED'); + } else if (/\d/.test(label)) { + setError('INVALID'); + } else { + setError(null); + } + }; + + const handleInputBlur: UseTreeItem2LabelInputSlotOwnProps['onBlur'] = (event) => { + if (error) { + event.defaultMuiPrevented = true; + } + }; + + const handleInputKeyDown: UseTreeItem2LabelInputSlotOwnProps['onKeyDown'] = ( + event, + ) => { + event.defaultMuiPrevented = true; + const target = event.target as HTMLInputElement; + + if (event.key === 'Enter' && target.value) { + if (error) { + return; + } + setError(null); + interactions.handleSaveItemLabel(event, target.value); + } else if (event.key === 'Escape') { + setError(null); + interactions.handleCancelItemLabelEditing(event); + } + }; + + const handleInputChange = (event: React.ChangeEvent) => { + validateLabel(event.target.value); + }; + + return ( + + ); +}); + +export default function Validation() { + return ( + + + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/editing/Validation.tsx.preview b/docs/data/tree-view/rich-tree-view/editing/Validation.tsx.preview new file mode 100644 index 0000000000000..6657b0a9a6a8e --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/Validation.tsx.preview @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/docs/data/tree-view/rich-tree-view/editing/editableProducts.ts b/docs/data/tree-view/rich-tree-view/editing/editableProducts.ts new file mode 100644 index 0000000000000..ca7945031fb53 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/editableProducts.ts @@ -0,0 +1,43 @@ +import { TreeViewBaseItem } from '@mui/x-tree-view/models'; + +type Editable = { + editable?: boolean; + id: string; + label: string; +}; + +export const MUI_X_PRODUCTS: TreeViewBaseItem[] = [ + { + id: 'grid', + label: 'Data Grid', + + children: [ + { id: 'grid-community', label: '@mui/x-data-grid editable', editable: true }, + { id: 'grid-pro', label: '@mui/x-data-grid-pro editable', editable: true }, + { 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' }], + }, +]; diff --git a/docs/data/tree-view/rich-tree-view/editing/editing.md b/docs/data/tree-view/rich-tree-view/editing/editing.md new file mode 100644 index 0000000000000..73e923bb7cffa --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/editing.md @@ -0,0 +1,99 @@ +--- +productId: x-tree-view +title: Rich Tree View - Editing +githubLabel: 'component: tree view' +packageName: '@mui/x-tree-view' +waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ +packageName: '@mui/x-tree-view' +--- + +# Rich Tree View - Label editing + +

Learn how to edit the label of Tree View items.

+ +## Enable label editing + +You can use the `isItemEditable` prop to enable editing. +If set to `true`, this prop will enable label editing on all items: + +{{"demo": "LabelEditingAllItems.js"}} + +:::success +If an item is editable, the editing state can be toggled by double clicking on it, or by pressing Enter on the keyboard when the item is in focus. + +Once an item is in editing state, the value of the label can be edited. Pressing Enter again or bluring the item will save the new value. Pressing Esc will cancel the action and restore the item to its original state. + +::: + +## Limit editing to some items + +If you pass a method to `isItemEditable`, only the items for which the method returns `true` will be editable: + +{{"demo": "LabelEditingSomeItems.js"}} + +### Limit editing to leaves + +You can limit the editing to just the leaves of the tree. + +{{"demo": "EditLeaves.js"}} + +## Track item label change + +Use the `onItemLabelChange` prop to trigger an action when the label of an item changes. + +{{"demo": "EditingCallback.js"}} + +## Change the default behavior + +By default, blurring the tree item saves the new value if there is one. +To modify this behavior, use the `slotProps` of the `TreeItem2`. + +{{"demo": "CustomBehavior.js"}} + +## Validation + +You can override the event handlers of the `labelInput` and implement a custom validation logic using the interaction methods from `useTreeItem2Utils`. + +{{"demo": "Validation.js"}} + +## Enable editing using only icons + +The demo below shows how to entirely override the editing behavior, and implement it using icons. + +{{"demo": "EditWithIcons.js"}} + +## Create a custom labelInput + +The demo below shows how to use a different component in the `labelInput` slot. + +{{"demo": "CustomLabelInput.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 ; +``` + +When your component first renders, `apiRef` will be `undefined`. +After this initial render, `apiRef` holds methods to interact imperatively with the Tree View. +::: + +### Change the label of an item + +Use the `setItemExpansion` API method to change the expansion of an item. + +```ts +apiRef.current.updateItemLabel( + // The id of the item to to update + itemId, + // The new label of the item. + newLabel, +); +``` + +{{"demo": "ApiMethodUpdateItemLabel.js"}} diff --git a/docs/data/tree-view/rich-tree-view/editing/employees.ts b/docs/data/tree-view/rich-tree-view/editing/employees.ts new file mode 100644 index 0000000000000..5e1e16f8a0799 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/employees.ts @@ -0,0 +1,37 @@ +import { TreeViewBaseItem } from '@mui/x-tree-view/models'; + +export type Employee = { + editable?: boolean; + id: string; + firstName: string; + lastName: string; +}; + +export const EMPLOYEES: TreeViewBaseItem[] = [ + { + id: '1', + firstName: 'Jane', + lastName: 'Doe', + editable: true, + children: [ + { id: '1.1', firstName: 'Elena', lastName: 'Kim', editable: true }, + { id: '1.2', firstName: 'Noah', lastName: 'Rodriguez', editable: true }, + { id: '1.3', firstName: 'Maya', lastName: 'Patel', editable: true }, + ], + }, + { + id: '2', + firstName: 'Liam', + lastName: 'Clarke', + editable: true, + children: [ + { + id: '2.1', + firstName: 'Ethan', + lastName: 'Lee', + editable: true, + }, + { id: '2.2', firstName: 'Ava', lastName: 'Jones', editable: true }, + ], + }, +]; diff --git a/docs/data/tree-view/rich-tree-view/editing/products.ts b/docs/data/tree-view/rich-tree-view/editing/products.ts new file mode 100644 index 0000000000000..97929673353e6 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/editing/products.ts @@ -0,0 +1,37 @@ +import { TreeViewBaseItem } from '@mui/x-tree-view/models'; + +export 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' }], + }, +]; diff --git a/docs/data/tree-view/rich-tree-view/expansion/TrackItemExpansionToggle.js b/docs/data/tree-view/rich-tree-view/expansion/TrackItemExpansionToggle.js index ad135b68d7fa1..7e8b1b548e67f 100644 --- a/docs/data/tree-view/rich-tree-view/expansion/TrackItemExpansionToggle.js +++ b/docs/data/tree-view/rich-tree-view/expansion/TrackItemExpansionToggle.js @@ -51,7 +51,6 @@ export default function TrackItemExpansionToggle() { Last action: {action.isExpanded ? 'expand' : 'collapse'} {action.itemId} )} - ({ - color: - theme.palette.mode === 'light' - ? theme.palette.grey[800] - : theme.palette.grey[400], + color: theme.palette.grey[400], position: 'relative', [`& .${treeItemClasses.groupTransition}`]: { marginLeft: theme.spacing(3.5), }, + ...theme.applyStyles('light', { + color: theme.palette.grey[800], + }), })); - const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ flexDirection: 'row-reverse', borderRadius: theme.spacing(0.7), @@ -100,10 +99,10 @@ const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ fontWeight: 500, [`&.Mui-expanded `]: { '&:not(.Mui-focused, .Mui-selected, .Mui-selected.Mui-focused) .labelIcon': { - color: - theme.palette.mode === 'light' - ? theme.palette.primary.main - : theme.palette.primary.dark, + color: theme.palette.primary.dark, + ...theme.applyStyles('light', { + color: theme.palette.primary.main, + }), }, '&::before': { content: '""', @@ -113,22 +112,25 @@ const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ top: '44px', height: 'calc(100% - 48px)', width: '1.5px', - backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.grey[300] - : theme.palette.grey[700], + backgroundColor: theme.palette.grey[700], + ...theme.applyStyles('light', { + backgroundColor: theme.palette.grey[300], + }), }, }, '&:hover': { backgroundColor: alpha(theme.palette.primary.main, 0.1), - color: theme.palette.mode === 'light' ? theme.palette.primary.main : 'white', + color: 'white', + ...theme.applyStyles('light', { + color: theme.palette.primary.main, + }), }, [`&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused`]: { - backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.primary.main - : theme.palette.primary.dark, + backgroundColor: theme.palette.primary.dark, color: theme.palette.primary.contrastText, + ...theme.applyStyles('light', { + backgroundColor: theme.palette.primary.main, + }), }, })); diff --git a/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.tsx b/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.tsx index 77d9336531421..0a93f04cea63e 100644 --- a/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.tsx +++ b/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.tsx @@ -98,16 +98,15 @@ declare module 'react' { } const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ - color: - theme.palette.mode === 'light' - ? theme.palette.grey[800] - : theme.palette.grey[400], + color: theme.palette.grey[400], position: 'relative', [`& .${treeItemClasses.groupTransition}`]: { marginLeft: theme.spacing(3.5), }, + ...theme.applyStyles('light', { + color: theme.palette.grey[800], + }), })) as unknown as typeof TreeItem2Root; - const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ flexDirection: 'row-reverse', borderRadius: theme.spacing(0.7), @@ -117,10 +116,10 @@ const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ fontWeight: 500, [`&.Mui-expanded `]: { '&:not(.Mui-focused, .Mui-selected, .Mui-selected.Mui-focused) .labelIcon': { - color: - theme.palette.mode === 'light' - ? theme.palette.primary.main - : theme.palette.primary.dark, + color: theme.palette.primary.dark, + ...theme.applyStyles('light', { + color: theme.palette.primary.main, + }), }, '&::before': { content: '""', @@ -130,22 +129,25 @@ const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ top: '44px', height: 'calc(100% - 48px)', width: '1.5px', - backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.grey[300] - : theme.palette.grey[700], + backgroundColor: theme.palette.grey[700], + ...theme.applyStyles('light', { + backgroundColor: theme.palette.grey[300], + }), }, }, '&:hover': { backgroundColor: alpha(theme.palette.primary.main, 0.1), - color: theme.palette.mode === 'light' ? theme.palette.primary.main : 'white', + color: 'white', + ...theme.applyStyles('light', { + color: theme.palette.primary.main, + }), }, [`&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused`]: { - backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.primary.main - : theme.palette.primary.dark, + backgroundColor: theme.palette.primary.dark, color: theme.palette.primary.contrastText, + ...theme.applyStyles('light', { + backgroundColor: theme.palette.primary.main, + }), }, })); diff --git a/docs/data/tree-view/simple-tree-view/customization/CustomStyling.js b/docs/data/tree-view/simple-tree-view/customization/CustomStyling.js index 3b2fc9d59aa69..aed1efe19d500 100644 --- a/docs/data/tree-view/simple-tree-view/customization/CustomStyling.js +++ b/docs/data/tree-view/simple-tree-view/customization/CustomStyling.js @@ -5,10 +5,7 @@ import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; import { TreeItem, treeItemClasses } from '@mui/x-tree-view/TreeItem'; const CustomTreeItem = styled(TreeItem)(({ theme }) => ({ - color: - theme.palette.mode === 'light' - ? theme.palette.grey[800] - : theme.palette.grey[200], + color: theme.palette.grey[200], [`& .${treeItemClasses.content}`]: { borderRadius: theme.spacing(0.5), padding: theme.spacing(0.5, 1), @@ -20,18 +17,23 @@ const CustomTreeItem = styled(TreeItem)(({ theme }) => ({ }, [`& .${treeItemClasses.iconContainer}`]: { borderRadius: '50%', - backgroundColor: - theme.palette.mode === 'light' - ? alpha(theme.palette.primary.main, 0.25) - : theme.palette.primary.dark, - color: theme.palette.mode === 'dark' && theme.palette.primary.contrastText, + backgroundColor: theme.palette.primary.dark, padding: theme.spacing(0, 1.2), + ...theme.applyStyles('light', { + backgroundColor: alpha(theme.palette.primary.main, 0.25), + }), + ...theme.applyStyles('dark', { + color: theme.palette.primary.contrastText, + }), }, [`& .${treeItemClasses.groupTransition}`]: { marginLeft: 15, paddingLeft: 18, borderLeft: `1px dashed ${alpha(theme.palette.text.primary, 0.4)}`, }, + ...theme.applyStyles('light', { + color: theme.palette.grey[800], + }), })); export default function CustomStyling() { diff --git a/docs/data/tree-view/simple-tree-view/customization/CustomStyling.tsx b/docs/data/tree-view/simple-tree-view/customization/CustomStyling.tsx index 8a8b2d2137194..aed1efe19d500 100644 --- a/docs/data/tree-view/simple-tree-view/customization/CustomStyling.tsx +++ b/docs/data/tree-view/simple-tree-view/customization/CustomStyling.tsx @@ -5,11 +5,7 @@ import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; import { TreeItem, treeItemClasses } from '@mui/x-tree-view/TreeItem'; const CustomTreeItem = styled(TreeItem)(({ theme }) => ({ - color: - theme.palette.mode === 'light' - ? theme.palette.grey[800] - : theme.palette.grey[200], - + color: theme.palette.grey[200], [`& .${treeItemClasses.content}`]: { borderRadius: theme.spacing(0.5), padding: theme.spacing(0.5, 1), @@ -21,18 +17,23 @@ const CustomTreeItem = styled(TreeItem)(({ theme }) => ({ }, [`& .${treeItemClasses.iconContainer}`]: { borderRadius: '50%', - backgroundColor: - theme.palette.mode === 'light' - ? alpha(theme.palette.primary.main, 0.25) - : theme.palette.primary.dark, - color: theme.palette.mode === 'dark' && theme.palette.primary.contrastText, + backgroundColor: theme.palette.primary.dark, padding: theme.spacing(0, 1.2), + ...theme.applyStyles('light', { + backgroundColor: alpha(theme.palette.primary.main, 0.25), + }), + ...theme.applyStyles('dark', { + color: theme.palette.primary.contrastText, + }), }, [`& .${treeItemClasses.groupTransition}`]: { marginLeft: 15, paddingLeft: 18, borderLeft: `1px dashed ${alpha(theme.palette.text.primary, 0.4)}`, }, + ...theme.applyStyles('light', { + color: theme.palette.grey[800], + }), })); export default function CustomStyling() { diff --git a/docs/data/tree-view/simple-tree-view/expansion/TrackItemExpansionToggle.js b/docs/data/tree-view/simple-tree-view/expansion/TrackItemExpansionToggle.js index 6164a3bc57499..07a0900e3d401 100644 --- a/docs/data/tree-view/simple-tree-view/expansion/TrackItemExpansionToggle.js +++ b/docs/data/tree-view/simple-tree-view/expansion/TrackItemExpansionToggle.js @@ -21,7 +21,6 @@ export default function TrackItemExpansionToggle() { Last action: {action.isExpanded ? 'expand' : 'collapse'} {action.itemId} )} - diff --git a/docs/docs-env.d.ts b/docs/docs-env.d.ts new file mode 100644 index 0000000000000..879a2a3ad5316 --- /dev/null +++ b/docs/docs-env.d.ts @@ -0,0 +1 @@ +import '@types/react-docgen'; diff --git a/docs/package.json b/docs/package.json index 1c2da49546121..9ff42848c707c 100644 --- a/docs/package.json +++ b/docs/package.json @@ -20,22 +20,22 @@ }, "dependencies": { "@babel/core": "^7.25.2", - "@babel/runtime": "^7.25.0", - "@babel/runtime-corejs2": "^7.25.0", + "@babel/runtime": "^7.25.4", + "@babel/runtime-corejs2": "^7.25.4", "@docsearch/react": "^3.6.1", "@emotion/cache": "^11.13.1", - "@emotion/react": "^11.13.0", + "@emotion/react": "^11.13.3", "@emotion/server": "^11.11.0", "@emotion/styled": "^11.13.0", - "@mui/docs": "6.0.0-beta.4", - "@mui/icons-material": "^5.16.5", + "@mui/docs": "6.0.1", + "@mui/icons-material": "^5.16.7", "@mui/joy": "^5.0.0-beta.48", "@mui/lab": "^5.0.0-alpha.173", - "@mui/material": "^5.16.5", - "@mui/material-nextjs": "^5.16.4", - "@mui/styles": "^5.16.5", - "@mui/system": "^5.16.5", - "@mui/utils": "^5.16.5", + "@mui/material": "^5.16.7", + "@mui/material-nextjs": "^5.16.6", + "@mui/styles": "^5.16.7", + "@mui/system": "^5.16.7", + "@mui/utils": "^5.16.6", "@mui/x-charts": "workspace:*", "@mui/x-charts-vendor": "workspace:*", "@mui/x-data-grid": "workspace:*", @@ -46,9 +46,9 @@ "@mui/x-date-pickers-pro": "workspace:*", "@mui/x-tree-view": "workspace:*", "@react-spring/web": "^9.7.4", - "@tanstack/query-core": "^5.51.15", + "@tanstack/query-core": "^5.53.1", "ast-types": "^0.14.2", - "autoprefixer": "^10.4.19", + "autoprefixer": "^10.4.20", "babel-plugin-module-resolver": "^5.0.2", "babel-plugin-optimize-clsx": "^2.6.2", "babel-plugin-preval": "^5.1.0", @@ -69,29 +69,29 @@ "fg-loadcss": "^3.1.0", "jscodeshift": "0.16.1", "lodash": "^4.17.21", - "luxon": "^3.4.4", + "luxon": "^3.5.0", "lz-string": "^1.5.0", - "markdown-to-jsx": "^7.4.7", + "markdown-to-jsx": "^7.5.0", "moment": "^2.30.1", "moment-hijri": "^2.30.0", "moment-jalaali": "^0.10.1", "moment-timezone": "^0.5.45", - "next": "^14.2.5", + "next": "^14.2.7", "nprogress": "^0.2.0", - "postcss": "^8.4.40", + "postcss": "^8.4.41", "prismjs": "^1.29.0", "prop-types": "^15.8.1", "react": "^18.3.1", "react-docgen": "^5.4.3", "react-dom": "^18.3.1", - "react-hook-form": "^7.52.1", + "react-hook-form": "^7.53.0", "react-is": "^18.3.1", - "react-router": "^6.25.1", - "react-router-dom": "^6.25.1", + "react-router": "^6.26.1", + "react-router-dom": "^6.26.1", "react-runner": "^1.0.5", "react-simple-code-editor": "^0.14.1", "recast": "^0.23.9", - "rimraf": "^5.0.9", + "rimraf": "^5.0.10", "rxjs": "^7.8.1", "styled-components": "^6.1.12", "stylis": "^4.3.2", @@ -101,8 +101,8 @@ "devDependencies": { "@babel/plugin-transform-react-constant-elements": "^7.25.1", "@babel/preset-typescript": "^7.24.7", - "@mui/internal-docs-utils": "^1.0.8", - "@mui/internal-scripts": "^1.0.13", + "@mui/internal-docs-utils": "^1.0.12", + "@mui/internal-scripts": "^1.0.18", "@types/chance": "^1.1.6", "@types/d3-scale": "^4.0.8", "@types/d3-scale-chromatic": "^3.0.3", diff --git a/docs/pages/_document.js b/docs/pages/_document.js index f9dfc9a7f73a6..d3958152942a4 100644 --- a/docs/pages/_document.js +++ b/docs/pages/_document.js @@ -1,3 +1,3 @@ -import MyDocument from '@mui/monorepo/docs/pages/_document'; +import MyDocument from 'docs/pages/_document'; export default MyDocument; diff --git a/docs/pages/playground/tsconfig.json b/docs/pages/playground/tsconfig.json index a1dce267bbdef..d8edc36bd6260 100644 --- a/docs/pages/playground/tsconfig.json +++ b/docs/pages/playground/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "../../tsconfig.json", - "include": ["*"], + "include": ["**/*"], "exclude": [] } diff --git a/docs/pages/x/api/charts/bar-chart-pro.json b/docs/pages/x/api/charts/bar-chart-pro.json index 141bd308bd168..8cc679f15c36f 100644 --- a/docs/pages/x/api/charts/bar-chart-pro.json +++ b/docs/pages/x/api/charts/bar-chart-pro.json @@ -73,6 +73,13 @@ "describedArgs": ["event", "barItemIdentifier"] } }, + "onZoomChange": { + "type": { "name": "func" }, + "signature": { + "type": "function(zoomData: Array) => void", + "describedArgs": ["zoomData"] + } + }, "rightAxis": { "type": { "name": "union", "description": "object
| string" }, "default": "null" @@ -99,13 +106,19 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { filterMode?: 'discard'
| 'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { filterMode?: 'discard'
| 'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + } + }, + "zoom": { + "type": { + "name": "arrayOf", + "description": "Array<{ axisId: number
| string, end: number, start: number }>" } } }, diff --git a/docs/pages/x/api/charts/bar-chart.json b/docs/pages/x/api/charts/bar-chart.json index 2e7d219fdfdc6..4fbc846f99487 100644 --- a/docs/pages/x/api/charts/bar-chart.json +++ b/docs/pages/x/api/charts/bar-chart.json @@ -99,13 +99,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } } }, @@ -191,7 +191,7 @@ ], "classes": [], "spread": true, - "themeDefaultProps": false, + "themeDefaultProps": true, "muiName": "MuiBarChart", "forwardsRefTo": "SVGSVGElement", "filename": "/packages/x-charts/src/BarChart/BarChart.tsx", diff --git a/docs/pages/x/api/charts/chart-container-pro.json b/docs/pages/x/api/charts/chart-container-pro.json index 5ac5c1b78337b..2eca09b9465f0 100644 --- a/docs/pages/x/api/charts/chart-container-pro.json +++ b/docs/pages/x/api/charts/chart-container-pro.json @@ -43,13 +43,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { filterMode?: 'discard'
| 'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { filterMode?: 'discard'
| 'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" } }, "zAxis": { diff --git a/docs/pages/x/api/charts/chart-container.json b/docs/pages/x/api/charts/chart-container.json index 3a78a1ef89b5d..08260a1a5255d 100644 --- a/docs/pages/x/api/charts/chart-container.json +++ b/docs/pages/x/api/charts/chart-container.json @@ -36,13 +36,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "zAxis": { diff --git a/docs/pages/x/api/charts/charts-surface.json b/docs/pages/x/api/charts/charts-surface.json index bd8d62382ec47..6e280f8a01193 100644 --- a/docs/pages/x/api/charts/charts-surface.json +++ b/docs/pages/x/api/charts/charts-surface.json @@ -6,13 +6,13 @@ }, "name": "ChartsSurface", "imports": [ - "import { ChartsSurface } from '/packages/x-charts/src/ChartsSurface.tsx';", + "import { ChartsSurface } from '@mui/x-charts/ChartsSurface';", "import { ChartsSurface } from '@mui/x-charts';", "import { ChartsSurface } from '@mui/x-charts-pro';" ], "classes": [], "muiName": "MuiChartsSurface", - "filename": "/packages/x-charts/src/ChartsSurface.tsx", + "filename": "/packages/x-charts/src/ChartsSurface/ChartsSurface.tsx", "inheritance": null, "demos": "", "cssComponent": false diff --git a/docs/pages/x/api/charts/charts-tooltip.json b/docs/pages/x/api/charts/charts-tooltip.json index 4d4e12f08b2d1..cc191b7a0bf88 100644 --- a/docs/pages/x/api/charts/charts-tooltip.json +++ b/docs/pages/x/api/charts/charts-tooltip.json @@ -22,7 +22,7 @@ "name": "enum", "description": "'axis'
| 'item'
| 'none'" }, - "default": "'item'" + "default": "'axis'" } }, "name": "ChartsTooltip", @@ -76,6 +76,12 @@ "description": "Styles applied to the markCell element.", "isGlobal": false }, + { + "key": "paper", + "className": "MuiChartsTooltip-paper", + "description": "Styles applied to the paper element.", + "isGlobal": false + }, { "key": "root", "className": "MuiChartsTooltip-root", diff --git a/docs/pages/x/api/charts/default-charts-axis-tooltip-content.json b/docs/pages/x/api/charts/default-charts-axis-tooltip-content.json index 274550fe14442..2594869a42ab4 100644 --- a/docs/pages/x/api/charts/default-charts-axis-tooltip-content.json +++ b/docs/pages/x/api/charts/default-charts-axis-tooltip-content.json @@ -53,6 +53,12 @@ "description": "Styles applied to the markCell element.", "isGlobal": false }, + { + "key": "paper", + "className": "MuiDefaultChartsAxisTooltipContent-paper", + "description": "Styles applied to the paper element.", + "isGlobal": false + }, { "key": "root", "className": "MuiDefaultChartsAxisTooltipContent-root", diff --git a/docs/pages/x/api/charts/default-charts-item-tooltip-content.json b/docs/pages/x/api/charts/default-charts-item-tooltip-content.json index c3582356897c9..7d76f641a34f6 100644 --- a/docs/pages/x/api/charts/default-charts-item-tooltip-content.json +++ b/docs/pages/x/api/charts/default-charts-item-tooltip-content.json @@ -54,6 +54,12 @@ "description": "Styles applied to the markCell element.", "isGlobal": false }, + { + "key": "paper", + "className": "MuiDefaultChartsItemTooltipContent-paper", + "description": "Styles applied to the paper element.", + "isGlobal": false + }, { "key": "root", "className": "MuiDefaultChartsItemTooltipContent-root", diff --git a/docs/pages/x/api/charts/default-heatmap-tooltip.json b/docs/pages/x/api/charts/default-heatmap-tooltip.json index caa6575ad64ce..66e8ffa29495c 100644 --- a/docs/pages/x/api/charts/default-heatmap-tooltip.json +++ b/docs/pages/x/api/charts/default-heatmap-tooltip.json @@ -53,6 +53,12 @@ "description": "Styles applied to the markCell element.", "isGlobal": false }, + { + "key": "paper", + "className": "MuiDefaultHeatmapTooltip-paper", + "description": "Styles applied to the paper element.", + "isGlobal": false + }, { "key": "root", "className": "MuiDefaultHeatmapTooltip-root", diff --git a/docs/pages/x/api/charts/heatmap.json b/docs/pages/x/api/charts/heatmap.json index a80558e101165..22b2bb7bbaa1e 100644 --- a/docs/pages/x/api/charts/heatmap.json +++ b/docs/pages/x/api/charts/heatmap.json @@ -7,14 +7,14 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ barGapRatio?: number, categoryGapRatio?: number, classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + "description": "Array<{ barGapRatio?: number, categoryGapRatio?: number, classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { filterMode?: 'discard'
| 'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" }, "required": true }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ barGapRatio?: number, categoryGapRatio?: number, classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + "description": "Array<{ barGapRatio?: number, categoryGapRatio?: number, classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { filterMode?: 'discard'
| 'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" }, "required": true }, diff --git a/docs/pages/x/api/charts/line-chart-pro.json b/docs/pages/x/api/charts/line-chart-pro.json index 6b2c4e32e0460..9737b36e65cc9 100644 --- a/docs/pages/x/api/charts/line-chart-pro.json +++ b/docs/pages/x/api/charts/line-chart-pro.json @@ -65,6 +65,13 @@ }, "onLineClick": { "type": { "name": "func" } }, "onMarkClick": { "type": { "name": "func" } }, + "onZoomChange": { + "type": { "name": "func" }, + "signature": { + "type": "function(zoomData: Array) => void", + "describedArgs": ["zoomData"] + } + }, "rightAxis": { "type": { "name": "union", "description": "object
| string" }, "default": "null" @@ -92,13 +99,19 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { filterMode?: 'discard'
| 'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { filterMode?: 'discard'
| 'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + } + }, + "zoom": { + "type": { + "name": "arrayOf", + "description": "Array<{ axisId: number
| string, end: number, start: number }>" } } }, diff --git a/docs/pages/x/api/charts/line-chart.json b/docs/pages/x/api/charts/line-chart.json index f146dd6804d5e..51e2f75d22ab1 100644 --- a/docs/pages/x/api/charts/line-chart.json +++ b/docs/pages/x/api/charts/line-chart.json @@ -92,13 +92,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } } }, @@ -186,7 +186,7 @@ ], "classes": [], "spread": true, - "themeDefaultProps": false, + "themeDefaultProps": true, "muiName": "MuiLineChart", "forwardsRefTo": "SVGSVGElement", "filename": "/packages/x-charts/src/LineChart/LineChart.tsx", diff --git a/docs/pages/x/api/charts/line-series-type.json b/docs/pages/x/api/charts/line-series-type.json index 8043690cf4491..61608bfce9c69 100644 --- a/docs/pages/x/api/charts/line-series-type.json +++ b/docs/pages/x/api/charts/line-series-type.json @@ -4,6 +4,7 @@ "properties": { "type": { "type": { "description": "'line'" }, "required": true }, "area": { "type": { "description": "boolean" } }, + "baseline": { "type": { "description": "number | 'min' | 'max'" }, "default": "0" }, "color": { "type": { "description": "string" } }, "connectNulls": { "type": { "description": "boolean" }, "default": "false" }, "curve": { "type": { "description": "CurveType" } }, diff --git a/docs/pages/x/api/charts/pie-chart.json b/docs/pages/x/api/charts/pie-chart.json index 42ad6eed68d55..cc208bd3f97c9 100644 --- a/docs/pages/x/api/charts/pie-chart.json +++ b/docs/pages/x/api/charts/pie-chart.json @@ -88,13 +88,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } } }, @@ -170,7 +170,7 @@ ], "classes": [], "spread": true, - "themeDefaultProps": false, + "themeDefaultProps": true, "muiName": "MuiPieChart", "forwardsRefTo": "SVGSVGElement", "filename": "/packages/x-charts/src/PieChart/PieChart.tsx", diff --git a/docs/pages/x/api/charts/responsive-chart-container-pro.json b/docs/pages/x/api/charts/responsive-chart-container-pro.json index c38d3cf3be32b..469801ba978d5 100644 --- a/docs/pages/x/api/charts/responsive-chart-container-pro.json +++ b/docs/pages/x/api/charts/responsive-chart-container-pro.json @@ -43,13 +43,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { filterMode?: 'discard'
| 'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { filterMode?: 'discard'
| 'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" } }, "zAxis": { diff --git a/docs/pages/x/api/charts/responsive-chart-container.json b/docs/pages/x/api/charts/responsive-chart-container.json index 38b6f6e9bf8f9..830314a5fcee8 100644 --- a/docs/pages/x/api/charts/responsive-chart-container.json +++ b/docs/pages/x/api/charts/responsive-chart-container.json @@ -36,13 +36,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "zAxis": { diff --git a/docs/pages/x/api/charts/scatter-chart-pro.json b/docs/pages/x/api/charts/scatter-chart-pro.json index f1b2cc769bf90..8dbc6b9ebc781 100644 --- a/docs/pages/x/api/charts/scatter-chart-pro.json +++ b/docs/pages/x/api/charts/scatter-chart-pro.json @@ -96,13 +96,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { filterMode?: 'discard'
| 'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func, zoom?: { filterMode?: 'discard'
| 'keep', maxEnd?: number, maxSpan?: number, minSpan?: number, minStart?: number, panning?: bool, step?: number }
| bool }>" } }, "zAxis": { diff --git a/docs/pages/x/api/charts/scatter-chart.json b/docs/pages/x/api/charts/scatter-chart.json index f3aa2e2e027fa..f9daa7ae5b4b4 100644 --- a/docs/pages/x/api/charts/scatter-chart.json +++ b/docs/pages/x/api/charts/scatter-chart.json @@ -89,13 +89,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "zAxis": { @@ -176,7 +176,7 @@ ], "classes": [], "spread": true, - "themeDefaultProps": false, + "themeDefaultProps": true, "muiName": "MuiScatterChart", "forwardsRefTo": "SVGSVGElement", "filename": "/packages/x-charts/src/ScatterChart/ScatterChart.tsx", diff --git a/docs/pages/x/api/charts/spark-line-chart.json b/docs/pages/x/api/charts/spark-line-chart.json index ffdba7744836e..3efe0080de11f 100644 --- a/docs/pages/x/api/charts/spark-line-chart.json +++ b/docs/pages/x/api/charts/spark-line-chart.json @@ -57,13 +57,13 @@ "xAxis": { "type": { "name": "shape", - "description": "{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }" + "description": "{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }" } }, "yAxis": { "type": { "name": "shape", - "description": "{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }" + "description": "{ classes?: object, 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, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'left'
| 'right', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, sx?: Array<func
| object
| bool>
| func
| object, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }" } } }, 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 1c79c35a04e6e..6f4425ee64e0c 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -75,7 +75,10 @@ }, "estimatedRowCount": { "type": { "name": "number" } }, "experimentalFeatures": { - "type": { "name": "shape", "description": "{ warnIfFocusStateIsNotSynced?: bool }" } + "type": { + "name": "shape", + "description": "{ ariaV8?: bool, warnIfFocusStateIsNotSynced?: bool }" + } }, "filterDebounceMs": { "type": { "name": "number" }, "default": "150" }, "filterMode": { @@ -178,6 +181,10 @@ }, "default": "false" }, + "indeterminateCheckboxAction": { + "type": { "name": "enum", "description": "'deselect'
| 'select'" }, + "default": "\"deselect\"" + }, "initialState": { "type": { "name": "object" } }, "isCellEditable": { "type": { "name": "func" }, 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 c3eb24a2ffb89..c5dc3bdb06897 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -155,6 +155,10 @@ }, "default": "false" }, + "indeterminateCheckboxAction": { + "type": { "name": "enum", "description": "'deselect'
| 'select'" }, + "default": "\"deselect\"" + }, "initialState": { "type": { "name": "object" } }, "isCellEditable": { "type": { "name": "func" }, diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index 7543c9d9f0abb..c2ecd48ba5129 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -123,6 +123,10 @@ }, "default": "false" }, + "indeterminateCheckboxAction": { + "type": { "name": "enum", "description": "'deselect'
| 'select'" }, + "default": "\"deselect\"" + }, "initialState": { "type": { "name": "object" } }, "isCellEditable": { "type": { "name": "func" }, diff --git a/docs/pages/x/api/data-grid/grid-cell-params.json b/docs/pages/x/api/data-grid/grid-cell-params.json index ff6ddfd6dd02d..2e99ec3bc0fab 100644 --- a/docs/pages/x/api/data-grid/grid-cell-params.json +++ b/docs/pages/x/api/data-grid/grid-cell-params.json @@ -6,6 +6,7 @@ "import { GridCellParams } from '@mui/x-data-grid'" ], "properties": { + "api": { "type": { "description": "GridApiCommunity" }, "required": true }, "cellMode": { "type": { "description": "GridCellMode" }, "required": true }, "colDef": { "type": { "description": "GridStateColDef" }, "required": true }, "field": { "type": { "description": "string" }, "required": true }, diff --git a/docs/pages/x/api/data-grid/selectors.json b/docs/pages/x/api/data-grid/selectors.json index 092a70faf2623..06218ff0ab86d 100644 --- a/docs/pages/x/api/data-grid/selectors.json +++ b/docs/pages/x/api/data-grid/selectors.json @@ -518,16 +518,24 @@ "name": "gridVirtualizationColumnEnabledSelector", "returnType": "boolean", "category": "Virtualization", - "description": "Get the enabled state for virtualization", + "description": "Get the enabled state for column virtualization", "supportsApiRef": true }, { "name": "gridVirtualizationEnabledSelector", "returnType": "boolean", "category": "Virtualization", + "deprecated": "Use `gridVirtualizationColumnEnabledSelector` and `gridVirtualizationRowEnabledSelector`", "description": "Get the enabled state for virtualization", "supportsApiRef": true }, + { + "name": "gridVirtualizationRowEnabledSelector", + "returnType": "boolean", + "category": "Virtualization", + "description": "Get the enabled state for row virtualization", + "supportsApiRef": true + }, { "name": "gridVirtualizationSelector", "returnType": "GridVirtualizationState", diff --git a/docs/pages/x/api/tree-view/rich-tree-view-pro.json b/docs/pages/x/api/tree-view/rich-tree-view-pro.json index 03bea300243d7..7cd100636eb8f 100644 --- a/docs/pages/x/api/tree-view/rich-tree-view-pro.json +++ b/docs/pages/x/api/tree-view/rich-tree-view-pro.json @@ -3,7 +3,7 @@ "apiRef": { "type": { "name": "shape", - "description": "{ current?: { focusItem: func, getItem: func, getItemDOMElement: func, getItemOrderedChildrenIds: func, getItemTree: func, selectItem: func, setItemExpansion: func } }" + "description": "{ current?: { focusItem: func, getItem: func, getItemDOMElement: func, getItemOrderedChildrenIds: func, getItemTree: func, selectItem: func, setItemExpansion: func, updateItemLabel: func } }" } }, "canMoveItemToNewPosition": { @@ -31,7 +31,7 @@ "experimentalFeatures": { "type": { "name": "shape", - "description": "{ indentationAtItemLevel?: bool, itemsReordering?: bool }" + "description": "{ indentationAtItemLevel?: bool, itemsReordering?: bool, labelEditing?: bool }" } }, "getItemId": { @@ -61,6 +61,7 @@ "returned": "boolean" } }, + "isItemEditable": { "type": { "name": "union", "description": "func
| bool" } }, "isItemReorderable": { "type": { "name": "func" }, "default": "() => true", @@ -104,6 +105,13 @@ "describedArgs": ["event", "itemId"] } }, + "onItemLabelChange": { + "type": { "name": "func" }, + "signature": { + "type": "function(itemId: TreeViewItemId, newLabel: string) => void", + "describedArgs": ["itemId", "newLabel"] + } + }, "onItemPositionChange": { "type": { "name": "func" }, "signature": { 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 7857c5c370bab..53335f98fc5e9 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, getItemDOMElement: func, getItemOrderedChildrenIds: func, getItemTree: func, selectItem: func, setItemExpansion: func } }" + "description": "{ current?: { focusItem: func, getItem: func, getItemDOMElement: func, getItemOrderedChildrenIds: func, getItemTree: func, selectItem: func, setItemExpansion: func, updateItemLabel: func } }" } }, "checkboxSelection": { "type": { "name": "bool" }, "default": "false" }, @@ -21,7 +21,10 @@ "default": "'content'" }, "experimentalFeatures": { - "type": { "name": "shape", "description": "{ indentationAtItemLevel?: bool }" } + "type": { + "name": "shape", + "description": "{ indentationAtItemLevel?: bool, labelEditing?: bool }" + } }, "getItemId": { "type": { "name": "func" }, @@ -50,6 +53,7 @@ "returned": "boolean" } }, + "isItemEditable": { "type": { "name": "union", "description": "func
| bool" } }, "itemChildrenIndentation": { "type": { "name": "union", "description": "number
| string" }, "default": "12px" @@ -83,6 +87,13 @@ "describedArgs": ["event", "itemId"] } }, + "onItemLabelChange": { + "type": { "name": "func" }, + "signature": { + "type": "function(itemId: TreeViewItemId, newLabel: string) => void", + "describedArgs": ["itemId", "newLabel"] + } + }, "onItemSelectionToggle": { "type": { "name": "func" }, "signature": { diff --git a/docs/pages/x/api/tree-view/tree-item-2.json b/docs/pages/x/api/tree-view/tree-item-2.json index 64dd975210041..547383a665af3 100644 --- a/docs/pages/x/api/tree-view/tree-item-2.json +++ b/docs/pages/x/api/tree-view/tree-item-2.json @@ -59,6 +59,12 @@ "default": "TreeItem2Label", "class": "MuiTreeItem2-label" }, + { + "name": "labelInput", + "description": "The component that renders the input to edit the label when the item is editable and is currently being edited.", + "default": "TreeItem2LabelInput", + "class": "MuiTreeItem2-labelInput" + }, { "name": "dragAndDropOverlay", "description": "The component that renders the overlay when an item reordering is ongoing.\nWarning: This slot is only useful when using the `RichTreeViewPro` component.", @@ -81,6 +87,18 @@ "description": "State class applied to the element when disabled.", "isGlobal": true }, + { + "key": "editable", + "className": "MuiTreeItem2-editable", + "description": "Styles applied to the content of the items that are editable.", + "isGlobal": false + }, + { + "key": "editing", + "className": "MuiTreeItem2-editing", + "description": "Styles applied to the content element when editing is enabled.", + "isGlobal": false + }, { "key": "expanded", "className": "Mui-expanded", diff --git a/docs/pages/x/api/tree-view/tree-item.json b/docs/pages/x/api/tree-view/tree-item.json index 2a7c1b9c00326..7873eb960165c 100644 --- a/docs/pages/x/api/tree-view/tree-item.json +++ b/docs/pages/x/api/tree-view/tree-item.json @@ -73,6 +73,18 @@ "description": "Styles applied to the drag and drop overlay.", "isGlobal": false }, + { + "key": "editable", + "className": "MuiTreeItem-editable", + "description": "Styles applied to the content of the items that are editable.", + "isGlobal": false + }, + { + "key": "editing", + "className": "MuiTreeItem-editing", + "description": "Styles applied to the content element when editing is enabled.", + "isGlobal": false + }, { "key": "expanded", "className": "Mui-expanded", @@ -97,6 +109,12 @@ "description": "Styles applied to the label element.", "isGlobal": false }, + { + "key": "labelInput", + "className": "MuiTreeItem-labelInput", + "description": "Styles applied to the input element that is visible when editing is enabled.", + "isGlobal": false + }, { "key": "root", "className": "MuiTreeItem-root", diff --git a/docs/pages/x/react-date-pickers/index.js b/docs/pages/x/react-date-pickers/index.js index b3933fe81dc28..204ca09b82db7 100644 --- a/docs/pages/x/react-date-pickers/index.js +++ b/docs/pages/x/react-date-pickers/index.js @@ -3,5 +3,5 @@ import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; import * as pageProps from 'docsx/data/date-pickers/overview/overview.md?muiMarkdown'; export default function Page() { - return ; + return ; } diff --git a/docs/pages/x/react-tree-view/rich-tree-view/editing.js b/docs/pages/x/react-tree-view/rich-tree-view/editing.js new file mode 100644 index 0000000000000..73aff62cb36ff --- /dev/null +++ b/docs/pages/x/react-tree-view/rich-tree-view/editing.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/tree-view/rich-tree-view/editing/editing.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/public/static/x/date-libraries/datefns.png b/docs/public/static/x/date-libraries/datefns.png new file mode 100644 index 0000000000000..ab1beba7507eb Binary files /dev/null and b/docs/public/static/x/date-libraries/datefns.png differ diff --git a/docs/public/static/x/date-libraries/dayjs.png b/docs/public/static/x/date-libraries/dayjs.png new file mode 100644 index 0000000000000..1eb9630fbee36 Binary files /dev/null and b/docs/public/static/x/date-libraries/dayjs.png differ diff --git a/docs/public/static/x/date-libraries/luxon.png b/docs/public/static/x/date-libraries/luxon.png new file mode 100644 index 0000000000000..41e6bc0d57669 Binary files /dev/null and b/docs/public/static/x/date-libraries/luxon.png differ diff --git a/docs/public/static/x/date-libraries/momentjs.png b/docs/public/static/x/date-libraries/momentjs.png new file mode 100644 index 0000000000000..32d3732df27cf Binary files /dev/null and b/docs/public/static/x/date-libraries/momentjs.png differ diff --git a/docs/src/modules/components/ChartFeaturesGrid.js b/docs/src/modules/components/ChartFeaturesGrid.js index ecff879e95c20..36af272ba23c0 100644 --- a/docs/src/modules/components/ChartFeaturesGrid.js +++ b/docs/src/modules/components/ChartFeaturesGrid.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import Grid from '@mui/material/Unstable_Grid2'; +import Grid from '@mui/material/Grid'; import { InfoCard } from '@mui/docs/InfoCard'; import LineAxisRoundedIcon from '@mui/icons-material/LineAxisRounded'; import DashboardCustomizeRoundedIcon from '@mui/icons-material/DashboardCustomizeRounded'; @@ -45,7 +45,7 @@ export default function ChartFeaturesGrid() { return ( {content.map(({ icon, title, link }) => ( - + ))} diff --git a/docs/src/modules/components/CustomizationPlayground.tsx b/docs/src/modules/components/CustomizationPlayground.tsx index 0e34112f8753c..64aaa0e48bec1 100644 --- a/docs/src/modules/components/CustomizationPlayground.tsx +++ b/docs/src/modules/components/CustomizationPlayground.tsx @@ -360,7 +360,7 @@ const CustomizationPlayground = function CustomizationPlayground({ id="select-component" label="" value={selectedDemo} - onChange={(e) => selectDemo(e.target.value as string)} + onChange={(event) => selectDemo(event.target.value as string)} > {Object.keys(examples || {}).map((item) => ( diff --git a/docs/src/modules/components/DemoPropsForm.tsx b/docs/src/modules/components/DemoPropsForm.tsx index c0dbea676285d..6af4554500ddb 100644 --- a/docs/src/modules/components/DemoPropsForm.tsx +++ b/docs/src/modules/components/DemoPropsForm.tsx @@ -129,15 +129,15 @@ function ControlledColorRadio(props: any) { ({ zIndex: 1, position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', pointerEvents: 'none', - color: (theme) => theme.palette.background.default, - }} + color: theme.palette.background.default, + })} /> } sx={{ width: '100%', height: '100%', margin: 0 }} @@ -488,19 +488,21 @@ export default function ChartDemoPropsForm({ key={placement} // variant="soft" color="primary" - sx={{ - position: 'relative', - height: '14px', - width: 32, - borderRadius: 'xs', - mx: 0.5, - ...(placement.match(/^(top|bottom)$/) && { + sx={[ + { + position: 'relative', + height: '14px', + width: 32, + borderRadius: 'xs', + mx: 0.5, + }, + placement.match(/^(top|bottom)$/) && { justifySelf: 'center', - }), - ...(placement.match(/^(top-end|bottom-end)$/) && { + }, + placement.match(/^(top-end|bottom-end)$/) && { justifySelf: 'flex-end', - }), - }} + }, + ]} > {content.map(({ icon, title, description, link }) => ( - + ({ fontWeight: 600, color: (theme.vars || theme).palette.text.secondary, '&.low': { - color: - theme.palette.mode === 'dark' - ? (theme.vars || theme).palette.text.primary - : (theme.vars || theme).palette.error.dark, + color: (theme.vars || theme).palette.error.dark, '& .progress-bar': { backgroundColor: (theme.vars || theme).palette.error.main, opacity: 0.3, @@ -29,26 +26,29 @@ const Root = styled('div')(({ theme }) => ({ border: `1px solid`, borderColor: (theme.vars || theme).palette.error.light, }, + ...theme.applyStyles('dark', { + color: (theme.vars || theme).palette.text.primary, + }), }, '&.medium': { - color: - theme.palette.mode === 'dark' - ? (theme.vars || theme).palette.text.primary - : (theme.vars || theme).palette.warning.dark, + color: (theme.vars || theme).palette.warning.dark, '& .progress-bar': { backgroundColor: (theme.vars || theme).palette.warning.main, - opacity: theme.palette.mode === 'dark' ? 0.4 : 0.25, + opacity: 0.25, + ...theme.applyStyles('dark', { + opacity: 0.4, + }), }, '& .progress-background': { border: `1px solid`, borderColor: (theme.vars || theme).palette.warning.light, }, + ...theme.applyStyles('dark', { + color: (theme.vars || theme).palette.text.primary, + }), }, '&.high': { - color: - theme.palette.mode === 'dark' - ? (theme.vars || theme).palette.text.primary - : (theme.vars || theme).palette.success.dark, + color: (theme.vars || theme).palette.success.dark, '& .progress-bar': { backgroundColor: (theme.vars || theme).palette.success.main, opacity: 0.3, @@ -57,6 +57,9 @@ const Root = styled('div')(({ theme }) => ({ border: `1px solid`, borderColor: (theme.vars || theme).palette.success.light, }, + ...theme.applyStyles('dark', { + color: (theme.vars || theme).palette.text.primary, + }), }, })); diff --git a/docs/src/modules/components/PickersPlayground.tsx b/docs/src/modules/components/PickersPlayground.tsx index 2b48de823b105..5980b1565b989 100644 --- a/docs/src/modules/components/PickersPlayground.tsx +++ b/docs/src/modules/components/PickersPlayground.tsx @@ -50,17 +50,20 @@ const ComponentSection = styled('div')(({ theme }) => ({ '& .MuiPickersLayout-root': { borderRadius: 8, border: '1px dashed', - borderColor: theme.palette.mode === 'light' ? theme.palette.grey[300] : theme.palette.divider, - ...(theme.palette.mode === 'dark' && { + borderColor: theme.palette.divider, + ...theme.applyStyles('dark', { backgroundColor: alpha(theme.palette.grey[900], 0.2), }), + ...theme.applyStyles('light', { + borderColor: theme.palette.grey[300], + }), }, })); const PropControlsSection = styled('div')(({ theme }) => ({ flexGrow: 1, background: alpha(theme.palette.grey[50], 0.5), - ...(theme.palette.mode === 'dark' && { + ...theme.applyStyles('dark', { backgroundColor: alpha(theme.palette.grey[900], 0.3), }), })); diff --git a/docs/src/modules/components/overview/CommunityOrPro.tsx b/docs/src/modules/components/overview/CommunityOrPro.tsx new file mode 100644 index 0000000000000..3e6dc197bf71a --- /dev/null +++ b/docs/src/modules/components/overview/CommunityOrPro.tsx @@ -0,0 +1,81 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Divider from '@mui/material/Divider'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import InfoCard from './InfoCard'; + +export default function CommunityOrPro() { + return ( + + + + + + + Community and Pro + + + Two packages for every need + + + Start with the free-forever Community version, then upgrade to Pro when you are ready + for additional features and components. + + + + + + + + } + description={[ + 'Free forever under an MIT license. Includes Date Pickers, Time Pickers, and Date Time Pickers.', + ]} + backgroundColor="subtle" + link="https://mui.com/pricing/" + /> + + + } + description={[ + 'Requires a commercial license. Includes all Community components plus the Date and Time Range Pickers.', + ]} + backgroundColor="subtle" + link="https://mui.com/pricing/" + /> + + + + + ); +} diff --git a/docs/src/modules/components/overview/DateLibraries.tsx b/docs/src/modules/components/overview/DateLibraries.tsx new file mode 100644 index 0000000000000..e04f459e44c36 --- /dev/null +++ b/docs/src/modules/components/overview/DateLibraries.tsx @@ -0,0 +1,113 @@ +import * as React from 'react'; +import SectionHeadline from 'docs/src/components/typography/SectionHeadline'; +import { HighlightedCode } from '@mui/docs/HighlightedCode'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Divider from '@mui/material/Divider'; +import Stack from '@mui/material/Stack'; +import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; +import ToggleButton from '@mui/material/ToggleButton'; +import Typography from '@mui/material/Typography'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; + +const dateLibraries = [ + { + name: 'Dayjs', + link: '/static/x/date-libraries/dayjs.png', + adapter: 'AdapterDayjs', + value: "dayjs('2024-04-17')", + }, + { + name: 'Luxon', + link: '/static/x/date-libraries/luxon.png', + adapter: 'AdapterLuxon', + value: "DateTime.fromISO('2024-04-17')", + }, + { + name: 'date-fns', + link: '/static/x/date-libraries/datefns.png', + adapter: 'AdapterDateFns', + value: "new Date('2024-04-17')", + }, + { + name: 'Moment.js', + link: '/static/x/date-libraries/momentjs.png', + adapter: 'AdapterMoment', + value: "moment('2024-04-17')", + }, +]; + +export default function DateLibraries() { + const [selectedLibrary, setSelectedLibrary] = React.useState(0); + + const handleLibrarySwitch = (_event: React.MouseEvent, library: number) => { + if (library !== null) { + setSelectedLibrary(library); + } + }; + return ( + + + + + + Use your favorite date library + + } + description="MUI X Date Pickers integrate smoothly with the most popular date libraries available." + /> + + + + + {dateLibraries.map((library, index) => ( + + {library.name} + {library.name} + + ))} + + + + + `, + ` `, + `
`, + ].join('\n')} + language="jsx" + copyButtonHidden + /> +
+ + + + ); +} diff --git a/docs/src/modules/components/overview/FeatureHighlight.tsx b/docs/src/modules/components/overview/FeatureHighlight.tsx new file mode 100644 index 0000000000000..684d7972340e0 --- /dev/null +++ b/docs/src/modules/components/overview/FeatureHighlight.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; +import Divider from '@mui/material/Divider'; +import Typography from '@mui/material/Typography'; +import Stack from '@mui/material/Stack'; +import FormatPaintIcon from '@mui/icons-material/FormatPaint'; +import AccessibilityNewIcon from '@mui/icons-material/AccessibilityNew'; +import LanguageIcon from '@mui/icons-material/Language'; +import InfoCard from './InfoCard'; + +const featuredItems = [ + { + title: 'Highly customizable', + description: + 'Start with our meticulous Material Design implementation, or go fully custom with your own design system.', + icon: , + }, + { + title: 'Accessibility', + description: + 'We are committed to meeting or exceeding global standards for accessibility, and we provide thorough guidance on best practices in our documentation.', + icon: , + }, + { + title: 'Internationalization', + description: + 'Serve the needs of users all around the world with built-in support for multiple time zones, languages, and date formats.', + icon: , + }, +]; + +export default function FeatureHighlight() { + return ( + + + + + + Using MUI X Date Pickers + + + First-class developer experience + + + MUI X Date and Time Pickers are designed to be delightful and intuitive for developers + and users alike. + + + + {featuredItems.map(({ title, description, icon }, index) => ( + + ))} + + + + ); +} diff --git a/docs/src/modules/components/overview/InfoCard.tsx b/docs/src/modules/components/overview/InfoCard.tsx new file mode 100644 index 0000000000000..c13bbfec8cd4a --- /dev/null +++ b/docs/src/modules/components/overview/InfoCard.tsx @@ -0,0 +1,112 @@ +import * as React from 'react'; +import { alpha } from '@mui/material/styles'; +import Link from '@mui/material/Link'; +import Typography from '@mui/material/Typography'; +import Stack from '@mui/material/Stack'; +import Paper from '@mui/material/Paper'; + +type InfoCardProps = { + active?: boolean; + backgroundColor?: 'gradient' | 'subtle'; + description?: string | string[]; + icon?: React.ReactNode; + link?: string; + onClick?: () => void; + title: string; +}; +export default function InfoCard(props: InfoCardProps) { + const { + active, + backgroundColor = 'gradient', + description, + icon: Icon, + link, + onClick, + title, + } = props; + const clickable = Boolean(onClick); + + return ( + ({ + p: 2.5, + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + textAlign: 'left', + flexGrow: 1, + height: '100%', + boxShadow: 'transparent', + background: + backgroundColor === 'gradient' + ? `${(theme.vars || theme).palette.gradients.linearSubtle}` + : 'transparent', + ...(clickable && { + cursor: 'pointer', + '&:hover': { + opacity: 1, + boxShadow: `0px 2px 30px 0px ${alpha(theme.palette.primary[50], 0.3)} inset, 0px 1px 6px 0px ${theme.palette.primary[100]}`, + borderColor: 'primary.100', + ...(active && { + borderColor: 'primary.300', + }), + }, + ...(active && { + boxShadow: `0px 2px 30px 0px ${alpha(theme.palette.primary[50], 0.3)} inset, 0px 1px 6px 0px ${theme.palette.primary[100]}`, + borderColor: 'primary.200', + }), + ...(!active && { borderColor: 'grey.300', opacity: 0.7 }), + }), + ...theme.applyDarkStyles({ + bgcolor: alpha(theme.palette.primaryDark[800], 0.25), + background: `${(theme.vars || theme).palette.gradients.linearSubtle}`, + borderColor: 'primaryDark.700', + ...(clickable && { + '&:hover': { + boxShadow: `0px 2px 30px 0px ${alpha(theme.palette.primary[800], 0.1)} inset, 0px 1px 6px 0px ${theme.palette.primary[900]}`, + + borderColor: 'primary.300', + }, + ...(active && { + boxShadow: `0px 2px 30px 0px ${alpha(theme.palette.primary[800], 0.1)} inset, 0px 1px 6px 0px ${theme.palette.primary[900]}`, + borderColor: 'primary.100', + }), + }), + }), + })} + > + + {Icon} + + {title} + + + {description && typeof description === 'string' && ( + + {description} + + )} + {description && + typeof description === 'object' && + description.map((item, index) => ( + + {item} + + ))} + + ); +} diff --git a/docs/src/modules/components/overview/Internationalization.tsx b/docs/src/modules/components/overview/Internationalization.tsx new file mode 100644 index 0000000000000..b0f898001d8a4 --- /dev/null +++ b/docs/src/modules/components/overview/Internationalization.tsx @@ -0,0 +1,384 @@ +import * as React from 'react'; +import dayjs from 'dayjs'; +import 'dayjs/locale/ro'; +import 'dayjs/locale/zh-cn'; +import utc from 'dayjs/plugin/utc'; +import timezone from 'dayjs/plugin/timezone'; +import { styled, createTheme, ThemeProvider, useTheme } from '@mui/material/styles'; +import SectionHeadline from 'docs/src/components/typography/SectionHeadline'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Divider from '@mui/material/Divider'; +import Paper from '@mui/material/Paper'; +import Stack from '@mui/material/Stack'; +import MuiToggleButtonGroup, { toggleButtonGroupClasses } from '@mui/material/ToggleButtonGroup'; +import MuiToggleButton from '@mui/material/ToggleButton'; +import Typography from '@mui/material/Typography'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { DateTimeRangePicker } from '@mui/x-date-pickers-pro/DateTimeRangePicker'; +import { DateTimeField } from '@mui/x-date-pickers/DateTimeField'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import { DateTimeValidationError } from '@mui/x-date-pickers/models'; +import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; +import { AdapterDayjs } from '@mui/x-date-pickers-pro/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers-pro/LocalizationProvider'; +import { roRO, enUS, zhCN } from '@mui/x-date-pickers-pro/locales'; +import InfoCard from './InfoCard'; +import WorldMapSvg, { ContinentClickHandler } from './WorldMapSvg'; + +dayjs.extend(utc); +dayjs.extend(timezone); + +const internationalizationFeatures = [ + { + title: 'Support for multiple timezones', + description: 'Accommodate global users and events in any geographical location.', + }, + { + title: 'Support for multiple languages', + description: + "Meet users where they're at with support for common date formats and languages used around the world.", + }, + { + title: 'Validation and error handling', + description: 'We have all use cases covered for you and your end users.', + }, +]; + +function DemoWrapper({ + children, + controls: ToolbarControls, + link, +}: { + children: React.ReactNode; + controls?: React.ReactNode; + link: string; +}) { + return ( + ({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + border: '1px solid', + borderColor: 'divider', + borderRadius: 1, + flexGrow: 1, + width: '100%', + justifyContent: 'space-between', + background: (brandingTheme.vars || brandingTheme).palette.gradients.linearSubtle, + })} + > + {children} + + ({ + width: '100%', + border: '1px solid transparent', + borderTopColor: 'divider', + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + display: 'flex', + padding: brandingTheme.spacing(1), + justifyContent: 'flex-end', + alignItems: { md: 'center' }, + gap: 2, + })} + > + {ToolbarControls} + + + + ); +} + +function TimezonesDemo() { + const [selectedTimezone, setSelectedTimezone] = React.useState(null); + const brandingTheme = useTheme(); + const theme = createTheme({ palette: { mode: brandingTheme.palette.mode } }); + + const handleContinentClick: ContinentClickHandler = (e, newTimezone) => { + if (selectedTimezone === newTimezone) { + setSelectedTimezone(null); + } else { + setSelectedTimezone(newTimezone); + } + }; + + return ( + + + + + {selectedTimezone ? `Selected timezone: ${selectedTimezone}` : 'Select timezone'} + + + + + + + + + + + + + ); +} + +type Languages = 'en' | 'ro' | 'zh-cn'; +const locales = { + ro: roRO, + en: enUS, + 'zh-cn': zhCN, +}; + +const ToggleButton = styled(MuiToggleButton)({ + borderColor: 'transparent', + padding: '5px 8px', +}); +const ToggleButtonGroup = styled(MuiToggleButtonGroup)(({ theme }) => ({ + gap: theme.spacing(1), + [`& .${toggleButtonGroupClasses.firstButton}, & .${toggleButtonGroupClasses.lastButton},& .${toggleButtonGroupClasses.middleButton} `]: + { + borderRadius: theme.shape.borderRadius, + }, +})); + +function Controls({ + selectedLanguage, + handleLanguageSwitch, +}: { + selectedLanguage: Languages; + handleLanguageSwitch: (event: React.MouseEvent, newLanguage: Languages) => void; +}) { + return ( + + + + RomĆ¢nă + + + English + + + ę—„ęœ¬čŖž + + + + ); +} + +function LanguagesDemo() { + const brandingTheme = useTheme(); + const [selectedLanguage, setSelectedLanguage] = React.useState('zh-cn'); + + const theme = createTheme({ palette: { mode: brandingTheme.palette.mode } }); + + const handleLanguageSwitch = (_event: React.MouseEvent, newLanguage: Languages) => { + if (newLanguage !== null) { + setSelectedLanguage(newLanguage); + } + }; + + return ( + + + } + link="/x/react-date-pickers/localization/" + > + + + + + + + + + + + + ); +} + +const startOfQ12022 = dayjs('2024-01-01T00:00:00.000'); +const endOfQ12022 = dayjs('2024-03-31T23:59:59.999'); +const fiveAM = dayjs().set('hour', 5).startOf('hour'); +const nineAM = dayjs().set('hour', 9).startOf('hour'); + +const getError = (error: DateTimeValidationError | null) => { + switch (error) { + case 'maxDate': + case 'minDate': { + return 'Please select a date in the first quarter of 2024'; + } + + case 'invalidDate': { + return 'Your date is not valid'; + } + + default: { + return ''; + } + } +}; + +function ValidationDemo() { + const brandingTheme = useTheme(); + const [fieldError, setFieldError] = React.useState(null); + const [pickerError, setPickerError] = React.useState(null); + + const theme = createTheme({ palette: { mode: brandingTheme.palette.mode } }); + + return ( + + + + + setFieldError(newError)} + slotProps={{ + textField: { + helperText: getError(fieldError), + fullWidth: true, + }, + }} + minDate={startOfQ12022} + maxDate={endOfQ12022} + /> + setPickerError(newError)} + slotProps={{ + textField: { + helperText: getError(pickerError), + fullWidth: true, + }, + }} + minDate={startOfQ12022} + maxDate={endOfQ12022} + /> + + + + + + ); +} + +export default function Internationalization() { + const [activeItem, setActiveItem] = React.useState(0); + + return ( + + + + + + {internationalizationFeatures[activeItem].title} + + } + /> + {internationalizationFeatures.map(({ title, description }, index) => ( + setActiveItem(index)} + backgroundColor="subtle" + /> + ))} + + + {activeItem === 0 && } + {activeItem === 1 && } + {activeItem === 2 && } + + + + ); +} diff --git a/docs/src/modules/components/overview/Keyboard.tsx b/docs/src/modules/components/overview/Keyboard.tsx new file mode 100644 index 0000000000000..456604d975c90 --- /dev/null +++ b/docs/src/modules/components/overview/Keyboard.tsx @@ -0,0 +1,501 @@ +import * as React from 'react'; +import clsx from 'clsx'; +import dayjs from 'dayjs'; +import SectionHeadline from 'docs/src/components/typography/SectionHeadline'; +import Button from '@mui/material/Button'; +import Divider from '@mui/material/Divider'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { styled, alpha, createTheme, ThemeProvider, useTheme } from '@mui/material/styles'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { DateField } from '@mui/x-date-pickers/DateField'; +import { FieldSelectedSections } from '@mui/x-date-pickers/models'; + +type KeyType = { + label: string; + showLabel?: boolean; + width?: number; + displayedLabel?: string; + location?: number; + shouldSelect?: boolean; + code?: string; + keyCode?: number; + height?: number; + x?: number; + y?: number; + keyType?: 'navigate-left' | 'navigate-right' | 'input'; +}; + +const keys: KeyType[][] = [ + [ + { label: 'Escape', width: 43, showLabel: true, displayedLabel: 'Esc', shouldSelect: true }, + { label: 'f1', width: 23, showLabel: false }, + { label: 'f2', width: 23, showLabel: false }, + { label: 'f3', width: 23, showLabel: false }, + { label: 'f4', width: 23, showLabel: false }, + { label: 'f5', width: 23, showLabel: false }, + { label: 'f6', width: 23, showLabel: false }, + { label: 'f7', width: 23, showLabel: false }, + { label: 'f8', width: 23, showLabel: false }, + { label: 'f9', width: 23, showLabel: false }, + { label: 'f10', width: 23, showLabel: false }, + { label: 'f11', width: 23, showLabel: false }, + { label: 'f12', width: 23, showLabel: false }, + { label: 'Delete', width: 23, showLabel: true, shouldSelect: true, displayedLabel: 'Del' }, + ], + [ + { label: '`', width: 23, showLabel: true }, + { + label: '1', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit1', + keyCode: 49, + keyType: 'input', + }, + { + label: '2', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit2', + keyCode: 50, + keyType: 'input', + }, + { + label: '3', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit3', + keyCode: 51, + keyType: 'input', + }, + { + label: '4', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit4', + keyCode: 52, + keyType: 'input', + }, + { + label: '5', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit5', + keyCode: 53, + keyType: 'input', + }, + { + label: '6', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit6', + keyCode: 54, + keyType: 'input', + }, + { + label: '7', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit7', + keyCode: 55, + keyType: 'input', + }, + { + label: '8', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit8', + keyCode: 56, + keyType: 'input', + }, + { + label: '9', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit9', + keyCode: 57, + keyType: 'input', + }, + { + label: '0', + width: 23, + showLabel: true, + shouldSelect: true, + code: 'Digit0', + keyCode: 58, + keyType: 'input', + }, + { label: '-', width: 23, showLabel: false }, + { label: '=', width: 23, showLabel: false }, + { + label: 'Backspace', + displayedLabel: 'Back', + width: 43, + showLabel: true, + code: 'Backspace', + keyCode: 8, + shouldSelect: true, + }, + ], + [ + { label: 'Tab', width: 43, showLabel: true, shouldSelect: true }, + { label: 'q', width: 23, showLabel: false }, + { label: 'w', width: 23, showLabel: false }, + { label: 'e', width: 23, showLabel: false }, + { label: 'r', width: 23, showLabel: false }, + { label: 't', width: 23, showLabel: false }, + { label: 'y', width: 23, showLabel: false }, + { label: 'u', width: 23, showLabel: false }, + { label: 'i', width: 23, showLabel: false }, + { label: 'o', width: 23, showLabel: false }, + { label: 'p', width: 23, showLabel: false }, + { label: '[', width: 23, showLabel: false }, + { label: ']', width: 23, showLabel: false }, + { label: '\\', width: 23, showLabel: false }, + ], + [ + { label: 'CapsLock', width: 49, showLabel: true, displayedLabel: 'Caps', shouldSelect: true }, + { label: 'a', width: 23, showLabel: false }, + { label: 's', width: 23, showLabel: false }, + { label: 'd', width: 23, showLabel: false }, + { label: 'f', width: 23, showLabel: false }, + { label: 'g', width: 23, showLabel: false }, + { label: 'h', width: 23, showLabel: false }, + { label: 'j', width: 23, showLabel: false }, + { label: 'k', width: 23, showLabel: false }, + { label: 'l', width: 23, showLabel: false }, + { label: ';', width: 23, showLabel: false }, + { label: "'", width: 23, showLabel: false }, + { label: 'Enter', width: 45, showLabel: true, shouldSelect: true }, + ], + [ + { label: 'Shift', width: 59, showLabel: true, location: 1 }, + { label: 'z', width: 23, showLabel: false }, + { label: 'x', width: 23, showLabel: false }, + { label: 'c', width: 23, showLabel: false }, + { label: 'v', width: 23, showLabel: false }, + { label: 'b', width: 23, showLabel: false }, + { label: 'n', width: 23, showLabel: false }, + { label: 'm', width: 23, showLabel: false }, + { label: ',', width: 23, showLabel: false }, + { label: '.', width: 23, showLabel: false }, + { label: '/', width: 23, showLabel: true }, + { label: 'Shift', width: 63, showLabel: true, location: 2 }, + ], + [ + { label: 'Control', width: 43, showLabel: true, location: 1, displayedLabel: 'Ctrl' }, + { label: 'Meta', width: 29, showLabel: false }, + { label: 'Alt', width: 42, showLabel: true, location: 1 }, + { label: ' ', width: 119, showLabel: false, displayedLabel: 'Space' }, + { label: 'Alt', width: 42, showLabel: true, location: 2 }, + { label: 'Control', width: 23, showLabel: true, location: 2, displayedLabel: 'Ctrl' }, + ], +]; + +const arrowKeys: KeyType[] = [ + { + label: 'ArrowLeft', + width: 23, + height: 9, + showLabel: false, + shouldSelect: true, + x: 340.5, + y: 166.5, + keyType: 'navigate-left', + }, + { + label: 'ArrowUp', + width: 23, + height: 9, + showLabel: false, + shouldSelect: true, + x: 368.5, + y: 152.5, + }, + { + label: 'ArrowDown', + width: 23, + height: 9, + showLabel: false, + shouldSelect: true, + x: 368.5, + y: 166.5, + }, + { + label: 'ArrowRight', + width: 23, + height: 9, + showLabel: false, + shouldSelect: true, + x: 396.5, + y: 166.5, + keyType: 'navigate-right', + }, +]; + +const RootRectangle = styled('rect')(({ theme }) => ({ + fill: 'white', + stroke: theme.palette.grey[500], + ...theme.applyStyles('dark', { + stroke: theme.palette.grey[600], + fill: theme.palette.background.paper, + }), +})); +const KeyRoot = styled('g')(({ theme }) => ({ + cursor: 'pointer', + '&:not(.selected):hover ': { '& .key-rect': { fill: theme.palette.action.hover } }, + '&.selected': { '& .key-rect': { fill: alpha(theme.palette.primary.main, 0.2) } }, +})); +const KeyRectangle = styled('rect')(({ theme }) => ({ + fill: 'white', + stroke: theme.palette.grey[500], + ...theme.applyStyles('dark', { + stroke: theme.palette.grey[600], + fill: theme.palette.background.paper, + }), +})); +const KeyText = styled('text')(({ theme }) => ({ + fill: theme.palette.grey[800], + fontSize: 9, + fontFamily: 'IBM Plex Sans', + ...theme.applyStyles('dark', { fill: theme.palette.text.primary }), +})); + +type KeyboardSvgProps = { + handleKeySelection: HandleKeySelection; + selectedKey: SelectedKey | null; +}; + +export function KeyboardSvg({ selectedKey, handleKeySelection }: KeyboardSvgProps) { + return ( + + + + + + {keys.map((row, rowIndex) => { + let xPosition = 12.5; + const yPosition = 12.5 + rowIndex * 28; + return ( + + {row.map( + ( + { + label, + displayedLabel, + showLabel, + width = 23, + location = 0, + shouldSelect = false, + code, + keyCode, + }, + keyIndex, + ) => { + const textXPosition = xPosition + width / 2; + const textYPosition = yPosition + 11.5; + const keyComponent = ( + { + if (shouldSelect) { + event.preventDefault(); + + handleKeySelection(event, { + key: label, + location: location || 0, + code: code || label, + keyCode: keyCode || 0, + }); + } + }} + onMouseUp={(event) => { + if (shouldSelect) { + handleKeySelection(event, null); + } + }} + > + + {showLabel && ( + + {displayedLabel || label} + + )} + + ); + xPosition = xPosition + width + 5; + + return keyComponent; + }, + )} + + ); + })} + {arrowKeys.map( + ({ label: key, location = 0, code = key, keyCode, width, height, x, y }, keyIndex) => { + return ( + { + event.preventDefault(); + handleKeySelection(event, { key, location, code, keyCode }); + }} + onMouseUp={(event) => { + handleKeySelection(event, null); + }} + > + + + ); + }, + )} + + ); +} + +type SelectedKey = { + key: string; + location?: number; + code?: string; + keyCode?: number; +}; +type HandleKeySelection = (event: React.SyntheticEvent, key: SelectedKey | null) => void; + +export default function Keyboard() { + const [selectedKey, setSelectedKey] = React.useState(null); + const ref = React.useRef(null); + const selectedSection = React.useRef(0); + + const brandingTheme = useTheme(); + const theme = createTheme({ palette: { mode: brandingTheme.palette.mode } }); + + const handleKeySelection = (e: React.SyntheticEvent, key: SelectedKey | null) => { + const sectionContent = (ref.current as any).querySelector( + `.MuiPickersSectionList-section[data-sectionindex="${selectedSection.current || 0}"] .MuiPickersSectionList-sectionContent`, + ); + sectionContent.focus(); + + if (key) { + const event = new KeyboardEvent('keydown', { + ...key, + bubbles: true, + cancelable: true, + }); + + sectionContent.dispatchEvent(event); + + if (key.key === 'Backspace') { + sectionContent.textContent = ''; + const inputEvent = new InputEvent('input', { + data: '', + inputType: 'insertText', + bubbles: true, + cancelable: true, + }); + + sectionContent.dispatchEvent(inputEvent); + } else if (key?.keyCode && key?.keyCode >= 49 && key?.keyCode <= 58) { + sectionContent.textContent = key.key; + const inputEvent = new InputEvent('input', { + data: key.key, + inputType: 'insertText', + bubbles: true, + cancelable: true, + }); + sectionContent.dispatchEvent(inputEvent); + } + } + setSelectedKey(key); + }; + + return ( + + + + + + Assistive technology support + + } + description="The MUI X Date Pickers feature advanced keyboard support that's compliant with WCAG and WAI-ARIA standards, so users who require assistive technology can navigate your interface with ease." + /> + + + + + { + setSelectedKey({ + key: event.key, + code: event.code, + location: event.location, + }); + }} + onKeyUp={() => { + setSelectedKey(null); + }} + enableAccessibleFieldDOMStructure + onSelectedSectionsChange={(newSelectedSection) => { + selectedSection.current = newSelectedSection; + }} + /> + + + + + + ); +} diff --git a/docs/src/modules/components/overview/MainDemo.tsx b/docs/src/modules/components/overview/MainDemo.tsx new file mode 100644 index 0000000000000..279e801896734 --- /dev/null +++ b/docs/src/modules/components/overview/MainDemo.tsx @@ -0,0 +1,125 @@ +import * as React from 'react'; +import { useTheme, ThemeProvider, createTheme, Theme, Components } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import Stack from '@mui/material/Stack'; +import Paper from '@mui/material/Paper'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import FlightPicker from './mainDemo/FlightPicker'; +import ThemeToggleGroup from './mainDemo/ThemeToggleGroup'; +import Clock from './mainDemo/Clock'; +import Birthday from './mainDemo/Birthday'; +import DigitalClock from './mainDemo/DigitalClock'; +import DateRangeWithShortcuts from './mainDemo/DateRangeWithShortcuts'; +import PickerButton from './mainDemo/PickerButton'; +import '@mui/x-date-pickers-pro/themeAugmentation'; + +const components: Components = { + MuiPickersDay: { + styleOverrides: { + root: { + fontWeight: 400, + }, + today: ({ theme }) => ({ + fontWeight: 600, + borderColor: theme.palette.primary.light, + }), + }, + }, + MuiPickersMonth: { + styleOverrides: { + monthButton: ({ theme }) => ({ + fontWeight: 400, + fontSize: '0.875rem', + borderRadius: theme.shape.borderRadius, + height: 28, + width: 64, + }), + }, + }, + MuiPickersYear: { + styleOverrides: { + yearButton: ({ theme }) => ({ + fontWeight: 400, + fontSize: '0.875rem', + borderRadius: theme.shape.borderRadius, + height: 28, + width: 64, + }), + }, + }, + MuiPickersToolbar: { styleOverrides: { content: { justifyContent: 'space-between' } } }, + MuiTimePickerToolbar: { + styleOverrides: { + ampmSelection: { marginRight: 0 }, + }, + }, + MuiButton: { + styleOverrides: { + root: ({ theme, ownerState }) => ({ + ...(ownerState.size === 'medium' && { + padding: theme.spacing('6px', '8px'), + }), + }), + }, + }, +}; + +export default function MainDemo() { + const brandingTheme = useTheme(); + const isMobile = useMediaQuery(brandingTheme.breakpoints.down('sm')); + const isTablet = useMediaQuery(brandingTheme.breakpoints.up('md')); + const isDesktop = useMediaQuery(brandingTheme.breakpoints.up('xl')); + + const [showCustomTheme, setShowCustomTheme] = React.useState(false); + + const toggleCustomTheme = () => { + setShowCustomTheme((prev) => !prev); + }; + + const theme = createTheme({ palette: { mode: brandingTheme.palette.mode } }); + const customTheme = createTheme(brandingTheme, { + components, + shape: { borderRadius: 8 }, + typography: { fontFamily: ['"Inter", "sans-serif"'].join(',') }, + }); + + return ( + + + + + + + + + + + + {isTablet && ( + + + + + )} + + + {isDesktop && ( + + + + + )} + + + + + ); +} diff --git a/docs/src/modules/components/overview/WorldMapSvg.tsx b/docs/src/modules/components/overview/WorldMapSvg.tsx new file mode 100644 index 0000000000000..9818f694d745d --- /dev/null +++ b/docs/src/modules/components/overview/WorldMapSvg.tsx @@ -0,0 +1,123 @@ +import * as React from 'react'; +import { styled, useTheme } from '@mui/material/styles'; +import clsx from 'clsx'; + +const Continent = styled('g')(({ theme }) => ({ + cursor: 'pointer', + opacity: 0.9, + '&:hover ': { opacity: 1 }, + '&.selected': { + fill: theme.palette.primary.main, + }, +})); + +export type ContinentClickHandler = (event: React.MouseEvent, timezone: string) => void; + +type WorldMapSvgProps = { + onClickContinent: ContinentClickHandler; + selectedTimezone: string | null; +}; + +export default function WorldMapSvg({ onClickContinent, selectedTimezone }: WorldMapSvgProps) { + const brandingTheme = useTheme(); + + const getMapBaseColor = (lightness: number, opacity: number = 1) => { + return brandingTheme.palette.mode === 'light' + ? `hsla(210,20%,${lightness}%, ${opacity * 100}%)` + : `hsla(210,20%,${lightness}%, ${opacity * 100}%)`; + }; + + const timezones: { [key: string]: string } = { + europe: 'Europe/Paris', + asia: 'Asia/Hong_kong', + southAmerica: 'America/Sao_Paulo', + northAmerica: 'America/Chicago', + africa: 'Africa/Casablanca', + australia: 'Australia/Brisbane', + }; + // TODO: simplify SVG + return ( + + onClickContinent(e, timezones.northAmerica)} + > + + + + + + + + + + + onClickContinent(e, timezones.southAmerica)} + > + + + onClickContinent(e, timezones.europe)} + > + + + + + + + + + + + + + onClickContinent(e, timezones.asia)} + > + + + + + + + + + + + + + + + + + + + + onClickContinent(e, timezones.africa)} + > + + + + onClickContinent(e, timezones.australia)} + > + + + + + + ); +} diff --git a/docs/src/modules/components/overview/XLogo.tsx b/docs/src/modules/components/overview/XLogo.tsx new file mode 100644 index 0000000000000..f8271a15362d6 --- /dev/null +++ b/docs/src/modules/components/overview/XLogo.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import Typography from '@mui/material/Typography'; +import IconImage from 'docs/src/components/icon/IconImage'; + +export default function XLogo() { + return ( + ({ + color: 'primary.600', + display: 'flex', + alignItems: 'center', + justifyContent: { xs: 'center', md: 'flex-start' }, + '& > *': { mr: 1 }, + ...theme.applyDarkStyles({ + color: 'primary.400', + }), + }), + ]} + > + MUI X + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/Birthday.tsx b/docs/src/modules/components/overview/mainDemo/Birthday.tsx new file mode 100644 index 0000000000000..80d58a8139fef --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/Birthday.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import dayjs from 'dayjs'; +import Button from '@mui/material/Button'; +import Card from '@mui/material/Card'; +import Stack from '@mui/material/Stack'; +import InputAdornment from '@mui/material/InputAdornment'; +import CakeIcon from '@mui/icons-material/Cake'; +import { DateField } from '@mui/x-date-pickers/DateField'; + +export default function Birthday() { + return ( + + + + + + ), + }} + /> + + + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/Clock.tsx b/docs/src/modules/components/overview/mainDemo/Clock.tsx new file mode 100644 index 0000000000000..2b1b1d5eb80bb --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/Clock.tsx @@ -0,0 +1,52 @@ +import * as React from 'react'; +import dayjs, { Dayjs } from 'dayjs'; +import { styled } from '@mui/material/styles'; +import Card from '@mui/material/Card'; +import { StaticTimePicker } from '@mui/x-date-pickers/StaticTimePicker'; +import { + PickersLayoutProps, + usePickerLayout, + pickersLayoutClasses, + PickersLayoutRoot, + PickersLayoutContentWrapper, +} from '@mui/x-date-pickers/PickersLayout'; +import { TimeView } from '@mui/x-date-pickers/models'; + +const StyledLayout = styled(PickersLayoutRoot)({ + overflow: 'auto', + minWidth: 'fit-content', + [`.${pickersLayoutClasses.toolbar}`]: { + padding: '4px 16px', + }, + [`.${pickersLayoutClasses.contentWrapper}`]: { + '& .MuiTimeClock-root': { + width: 'fit-content', + }, + '& .MuiPickersArrowSwitcher-root': { + justifyContent: 'space-between', + width: '100%', + right: 0, + top: '2px', + }, + }, +}); + +function CustomLayout(props: PickersLayoutProps) { + const { actionBar, content, toolbar } = usePickerLayout(props); + return ( + + {toolbar} + + {content} + {actionBar} + + + ); +} +export default function Clock() { + return ( + + + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/DateRangeWithShortcuts.tsx b/docs/src/modules/components/overview/mainDemo/DateRangeWithShortcuts.tsx new file mode 100644 index 0000000000000..a197487ce22d3 --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/DateRangeWithShortcuts.tsx @@ -0,0 +1,114 @@ +import * as React from 'react'; +import dayjs, { Dayjs } from 'dayjs'; +import { useTheme } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import Card from '@mui/material/Card'; +import { StaticDateRangePicker } from '@mui/x-date-pickers-pro/StaticDateRangePicker'; +import { PickersShortcutsItem } from '@mui/x-date-pickers/PickersShortcuts'; +import { + PickersLayoutProps, + usePickerLayout, + pickersLayoutClasses, + PickersLayoutRoot, + PickersLayoutContentWrapper, +} from '@mui/x-date-pickers/PickersLayout'; +import { DateRange } from '@mui/x-date-pickers-pro/models'; + +const shortcutsItems: PickersShortcutsItem>[] = [ + { + label: 'This Week', + getValue: () => { + const today = dayjs(); + return [today.startOf('week'), today.endOf('week')]; + }, + }, + { + label: 'Last Week', + getValue: () => { + const today = dayjs(); + const prevWeek = today.subtract(7, 'day'); + return [prevWeek.startOf('week'), prevWeek.endOf('week')]; + }, + }, + { + label: 'Last 7 Days', + getValue: () => { + const today = dayjs(); + return [today.subtract(7, 'day'), today]; + }, + }, + { + label: 'Current Month', + getValue: () => { + const today = dayjs(); + return [today.startOf('month'), today.endOf('month')]; + }, + }, + { + label: 'Next Month', + getValue: () => { + const today = dayjs(); + const startOfNextMonth = today.endOf('month').add(1, 'day'); + return [startOfNextMonth, startOfNextMonth.endOf('month')]; + }, + }, + { label: 'Reset', getValue: () => [null, null] }, +]; + +interface CustomLayoutProps extends PickersLayoutProps, Dayjs, 'day'> { + isHorizontal?: boolean; +} +function CustomLayout(props: CustomLayoutProps) { + const { isHorizontal, ...other } = props; + const { tabs, content, shortcuts } = usePickerLayout(other); + + return ( + + {shortcuts} + + {tabs} + {content} + + + ); +} + +export default function DateRangeWithShortcuts() { + const theme = useTheme(); + const showTwoCalendars = useMediaQuery('(min-width:700px)'); + const lgDown = useMediaQuery(theme.breakpoints.down('lg')); + const smUp = useMediaQuery(theme.breakpoints.up('sm')); + const xlDown = useMediaQuery(theme.breakpoints.down('xl')); + return ( + + + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/DigitalClock.tsx b/docs/src/modules/components/overview/mainDemo/DigitalClock.tsx new file mode 100644 index 0000000000000..d7c96867004b8 --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/DigitalClock.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import { Dayjs } from 'dayjs'; +import { styled } from '@mui/material/styles'; +import Card from '@mui/material/Card'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; +import { StaticTimePicker, StaticTimePickerProps } from '@mui/x-date-pickers/StaticTimePicker'; +import { + PickersLayoutProps, + usePickerLayout, + pickersLayoutClasses, + PickersLayoutRoot, + PickersLayoutContentWrapper, +} from '@mui/x-date-pickers/PickersLayout'; +import { renderMultiSectionDigitalClockTimeView } from '@mui/x-date-pickers/timeViewRenderers'; +import { TimeView } from '@mui/x-date-pickers/models'; + +const StyledLayout = styled(PickersLayoutRoot)({ + overflow: 'auto', + [`.${pickersLayoutClasses.contentWrapper}`]: { + '& .MuiClock-root': { + width: 'fit-content', + }, + }, +}); + +function CustomLayout(props: PickersLayoutProps) { + const { actionBar, content } = usePickerLayout(props); + return ( + + + {content} + {actionBar} + + + ); +} +export default function DigitalClock() { + return ( + + + Book now! + + `1px solid ${theme.palette.divider}`, + flexWrap: 'wrap', + padding: 0, + }} + > + ['viewRenderers'] + } + /> + + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/FlightPicker.tsx b/docs/src/modules/components/overview/mainDemo/FlightPicker.tsx new file mode 100644 index 0000000000000..d75649a275373 --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/FlightPicker.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import Card from '@mui/material/Card'; +import Typography from '@mui/material/Typography'; +import InputAdornment from '@mui/material/InputAdornment'; +import FlightLandIcon from '@mui/icons-material/FlightLand'; +import FlightTakeoffIcon from '@mui/icons-material/FlightTakeoff'; +import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; + +export default function FlightPicker() { + return ( + + + Book your flight + + ({ + label: position === 'start' ? 'Outbound' : 'Inbound', + InputProps: { + startAdornment: ( + + {position === 'start' ? : } + + ), + }, + }), + }} + /> + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx new file mode 100644 index 0000000000000..0dc82b364e0f4 --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import dayjs, { Dayjs } from 'dayjs'; +import Button from '@mui/material/Button'; +import Card from '@mui/material/Card'; +import CalendarTodayRoundedIcon from '@mui/icons-material/CalendarTodayRounded'; +import { UseDateFieldProps } from '@mui/x-date-pickers/DateField'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import { + BaseSingleInputFieldProps, + DateValidationError, + FieldSection, +} from '@mui/x-date-pickers/models'; + +interface ButtonFieldProps + extends UseDateFieldProps, + BaseSingleInputFieldProps { + setOpen?: React.Dispatch>; +} + +function ButtonField(props: ButtonFieldProps) { + const { + setOpen, + label, + id, + disabled, + InputProps: { ref } = {}, + inputProps: { 'aria-label': ariaLabel } = {}, + } = props; + + return ( + + ); +} + +export default function PickerButton() { + const [value, setValue] = React.useState(dayjs('2023-04-17')); + const [open, setOpen] = React.useState(false); + + return ( + + setValue(newValue)} + slots={{ field: ButtonField }} + slotProps={{ + field: { setOpen } as any, + nextIconButton: { size: 'small' }, + previousIconButton: { size: 'small' }, + }} + open={open} + onClose={() => setOpen(false)} + onOpen={() => setOpen(true)} + views={['day', 'month', 'year']} + /> + + ); +} diff --git a/docs/src/modules/components/overview/mainDemo/ThemeToggleGroup.tsx b/docs/src/modules/components/overview/mainDemo/ThemeToggleGroup.tsx new file mode 100644 index 0000000000000..5483088efd980 --- /dev/null +++ b/docs/src/modules/components/overview/mainDemo/ThemeToggleGroup.tsx @@ -0,0 +1,63 @@ +import * as React from 'react'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { styled, useTheme } from '@mui/material/styles'; +import ToggleButton from '@mui/material/ToggleButton'; +import ToggleButtonGroup, { toggleButtonGroupClasses } from '@mui/material/ToggleButtonGroup'; +import Paper from '@mui/material/Paper'; +import SettingsSuggestIcon from '@mui/icons-material/SettingsSuggest'; +import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; + +const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }) => ({ + flexGrow: 1, + gap: theme.spacing(1), + padding: theme.spacing(0.8), + [`& .${toggleButtonGroupClasses.grouped}`]: { + border: 0, + borderRadius: theme.shape.borderRadius, + }, +})); + +export type ThemeToggleGroupProps = { + showCustomTheme: boolean; + toggleCustomTheme: () => void; +}; + +export default function ThemeToggleGroup({ + showCustomTheme, + toggleCustomTheme, +}: ThemeToggleGroupProps) { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + + return ( + + { + if (newValue !== null) { + toggleCustomTheme(); + } + }} + exclusive + size="small" + > + + + {isMobile && 'Custom Theme'} + + + + {isMobile && 'Default Theme'} + + + + ); +} diff --git a/docs/translations/api-docs/charts/bar-chart-pro/bar-chart-pro.json b/docs/translations/api-docs/charts/bar-chart-pro/bar-chart-pro.json index 48e64539542e8..58fc1a173e710 100644 --- a/docs/translations/api-docs/charts/bar-chart-pro/bar-chart-pro.json +++ b/docs/translations/api-docs/charts/bar-chart-pro/bar-chart-pro.json @@ -52,6 +52,10 @@ "barItemIdentifier": "The bar item identifier." } }, + "onZoomChange": { + "description": "Callback fired when the zoom has changed.", + "typeDescriptions": { "zoomData": "Updated zoom data." } + }, "rightAxis": { "description": "Indicate which axis to display the right of the charts. Can be a string (the id of the axis) or an object ChartsYAxisProps." }, @@ -76,7 +80,8 @@ }, "yAxis": { "description": "The configuration of the y-axes. If not provided, a default axis config is used. An array of AxisConfig objects." - } + }, + "zoom": { "description": "The list of zoom data related to each axis." } }, "classDescriptions": {} } diff --git a/docs/translations/api-docs/charts/charts-tooltip/charts-tooltip.json b/docs/translations/api-docs/charts/charts-tooltip/charts-tooltip.json index 152217da8fe54..a268da37e6cf3 100644 --- a/docs/translations/api-docs/charts/charts-tooltip/charts-tooltip.json +++ b/docs/translations/api-docs/charts/charts-tooltip/charts-tooltip.json @@ -25,6 +25,7 @@ "description": "Styles applied to {{nodeName}}.", "nodeName": "the markCell element" }, + "paper": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the paper element" }, "root": { "description": "Styles applied to the root element." }, "row": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the row element" }, "table": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the table element" }, diff --git a/docs/translations/api-docs/charts/default-charts-axis-tooltip-content/default-charts-axis-tooltip-content.json b/docs/translations/api-docs/charts/default-charts-axis-tooltip-content/default-charts-axis-tooltip-content.json index ef97ccfe86a31..3e7c7c6cc6e98 100644 --- a/docs/translations/api-docs/charts/default-charts-axis-tooltip-content/default-charts-axis-tooltip-content.json +++ b/docs/translations/api-docs/charts/default-charts-axis-tooltip-content/default-charts-axis-tooltip-content.json @@ -19,6 +19,7 @@ "description": "Styles applied to {{nodeName}}.", "nodeName": "the markCell element" }, + "paper": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the paper element" }, "root": { "description": "Styles applied to the root element." }, "row": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the row element" }, "table": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the table element" }, diff --git a/docs/translations/api-docs/charts/default-charts-item-tooltip-content/default-charts-item-tooltip-content.json b/docs/translations/api-docs/charts/default-charts-item-tooltip-content/default-charts-item-tooltip-content.json index 36fe7b5f92e50..c7ea4d31307fb 100644 --- a/docs/translations/api-docs/charts/default-charts-item-tooltip-content/default-charts-item-tooltip-content.json +++ b/docs/translations/api-docs/charts/default-charts-item-tooltip-content/default-charts-item-tooltip-content.json @@ -23,6 +23,7 @@ "description": "Styles applied to {{nodeName}}.", "nodeName": "the markCell element" }, + "paper": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the paper element" }, "root": { "description": "Styles applied to the root element." }, "row": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the row element" }, "table": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the table element" }, diff --git a/docs/translations/api-docs/charts/default-heatmap-tooltip/default-heatmap-tooltip.json b/docs/translations/api-docs/charts/default-heatmap-tooltip/default-heatmap-tooltip.json index 36fe7b5f92e50..c7ea4d31307fb 100644 --- a/docs/translations/api-docs/charts/default-heatmap-tooltip/default-heatmap-tooltip.json +++ b/docs/translations/api-docs/charts/default-heatmap-tooltip/default-heatmap-tooltip.json @@ -23,6 +23,7 @@ "description": "Styles applied to {{nodeName}}.", "nodeName": "the markCell element" }, + "paper": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the paper element" }, "root": { "description": "Styles applied to the root element." }, "row": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the row element" }, "table": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the table element" }, diff --git a/docs/translations/api-docs/charts/line-chart-pro/line-chart-pro.json b/docs/translations/api-docs/charts/line-chart-pro/line-chart-pro.json index a262e0ac15bf2..bc67c9eba65b4 100644 --- a/docs/translations/api-docs/charts/line-chart-pro/line-chart-pro.json +++ b/docs/translations/api-docs/charts/line-chart-pro/line-chart-pro.json @@ -46,6 +46,10 @@ }, "onLineClick": { "description": "Callback fired when a line element is clicked." }, "onMarkClick": { "description": "Callback fired when a mark element is clicked." }, + "onZoomChange": { + "description": "Callback fired when the zoom has changed.", + "typeDescriptions": { "zoomData": "Updated zoom data." } + }, "rightAxis": { "description": "Indicate which axis to display the right of the charts. Can be a string (the id of the axis) or an object ChartsYAxisProps." }, @@ -70,7 +74,8 @@ }, "yAxis": { "description": "The configuration of the y-axes. If not provided, a default axis config is used. An array of AxisConfig objects." - } + }, + "zoom": { "description": "The list of zoom data related to each axis." } }, "classDescriptions": {} } diff --git a/docs/translations/api-docs/charts/line-series-type.json b/docs/translations/api-docs/charts/line-series-type.json index ff576209272d0..824efbcd4acb1 100644 --- a/docs/translations/api-docs/charts/line-series-type.json +++ b/docs/translations/api-docs/charts/line-series-type.json @@ -3,6 +3,9 @@ "propertiesDescriptions": { "type": { "description": "" }, "area": { "description": "" }, + "baseline": { + "description": "

The value of the line at the base of the series area.

- 'min' the area will fill the space under the line.
- 'max' the area will fill the space above the line.
- number the area will fill the space between this value and the line

\n" + }, "color": { "description": "" }, "connectNulls": { "description": "If true, line and area connect points separated by null values." diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index a8b79b49fb93e..baba2f9ae0045 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -202,6 +202,9 @@ "ignoreValueFormatterDuringExport": { "description": "If true, the Data Grid will not use valueFormatter when exporting to CSV or copying to clipboard. If an object is provided, you can choose to ignore the valueFormatter for CSV export or clipboard export." }, + "indeterminateCheckboxAction": { + "description": "If select, a group header checkbox in indeterminate state (like "Select All" checkbox) will select all the rows under it. If deselect, it will deselect all the rows under it. Works only if checkboxSelection is enabled." + }, "initialState": { "description": "The initial state of the DataGridPremium. The data in it is set in the state on initialization but isn't controlled. If one of the data in initialState is also being controlled, then the control state wins." }, @@ -223,7 +226,7 @@ "description": "Determines if a row can be selected.", "typeDescriptions": { "params": "With all properties from GridRowParams.", - "boolean": "A boolean indicating if the cell is selectable." + "boolean": "A boolean indicating if the row is selectable." } }, "keepColumnPositionIfDraggedOutside": { diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index 914817291c87f..91cce7101fd81 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -183,6 +183,9 @@ "ignoreValueFormatterDuringExport": { "description": "If true, the Data Grid will not use valueFormatter when exporting to CSV or copying to clipboard. If an object is provided, you can choose to ignore the valueFormatter for CSV export or clipboard export." }, + "indeterminateCheckboxAction": { + "description": "If select, a group header checkbox in indeterminate state (like "Select All" checkbox) will select all the rows under it. If deselect, it will deselect all the rows under it. Works only if checkboxSelection is enabled." + }, "initialState": { "description": "The initial state of the DataGridPro. The data in it will be set in the state on initialization but will not be controlled. If one of the data in initialState is also being controlled, then the control state wins." }, @@ -204,7 +207,7 @@ "description": "Determines if a row can be selected.", "typeDescriptions": { "params": "With all properties from GridRowParams.", - "boolean": "A boolean indicating if the cell is selectable." + "boolean": "A boolean indicating if the row is selectable." } }, "keepColumnPositionIfDraggedOutside": { diff --git a/docs/translations/api-docs/data-grid/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid/data-grid.json index 08fa6f50e6c46..b799ebbc72064 100644 --- a/docs/translations/api-docs/data-grid/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid/data-grid.json @@ -136,6 +136,9 @@ "ignoreValueFormatterDuringExport": { "description": "If true, the Data Grid will not use valueFormatter when exporting to CSV or copying to clipboard. If an object is provided, you can choose to ignore the valueFormatter for CSV export or clipboard export." }, + "indeterminateCheckboxAction": { + "description": "If select, a group header checkbox in indeterminate state (like "Select All" checkbox) will select all the rows under it. If deselect, it will deselect all the rows under it. Works only if checkboxSelection is enabled." + }, "initialState": { "description": "The initial state of the DataGrid. The data in it will be set in the state on initialization but will not be controlled. If one of the data in initialState is also being controlled, then the control state wins." }, @@ -150,7 +153,7 @@ "description": "Determines if a row can be selected.", "typeDescriptions": { "params": "With all properties from GridRowParams.", - "boolean": "A boolean indicating if the cell is selectable." + "boolean": "A boolean indicating if the row is selectable." } }, "keepNonExistentRowsSelected": { diff --git a/docs/translations/api-docs/data-grid/grid-cell-params.json b/docs/translations/api-docs/data-grid/grid-cell-params.json index 39d3bbcc36617..b9be4d64e27e5 100644 --- a/docs/translations/api-docs/data-grid/grid-cell-params.json +++ b/docs/translations/api-docs/data-grid/grid-cell-params.json @@ -1,6 +1,7 @@ { "interfaceDescription": "Object passed as parameter in the column GridColDef cell renderer.", "propertiesDescriptions": { + "api": { "description": "GridApi that let you manipulate the grid." }, "cellMode": { "description": "The mode of the cell." }, "colDef": { "description": "The column of the row that the current cell belongs to." }, "field": { "description": "The column field of the cell that triggered the event." }, diff --git a/docs/translations/api-docs/tree-view/rich-tree-view-pro/rich-tree-view-pro.json b/docs/translations/api-docs/tree-view/rich-tree-view-pro/rich-tree-view-pro.json index bcd6e7bdb9053..3a33b96c08bd8 100644 --- a/docs/translations/api-docs/tree-view/rich-tree-view-pro/rich-tree-view-pro.json +++ b/docs/translations/api-docs/tree-view/rich-tree-view-pro/rich-tree-view-pro.json @@ -8,7 +8,7 @@ "description": "Used to determine if a given item can move to some new position.", "typeDescriptions": { "params": "The params describing the item re-ordering.", - "params.itemId": "The id of the item to check.", + "params.itemId": "The id of the item that is being moved to a new position.", "params.oldPosition": "The old position of the item.", "params.newPosition": "The new position of the item.", "boolean": "true if the item can move to the new position." @@ -55,6 +55,9 @@ "boolean": "true if the item should be disabled." } }, + "isItemEditable": { + "description": "Determines if a given item is editable or not. Make sure to also enable the labelEditing experimental feature: <RichTreeViewPro experimentalFeatures={{ labelEditing: true }} />. By default, the items are not editable." + }, "isItemReorderable": { "description": "Used to determine if a given item can be reordered.", "typeDescriptions": { @@ -100,6 +103,13 @@ "itemId": "The id of the focused item." } }, + "onItemLabelChange": { + "description": "Callback fired when the label of an item changes.", + "typeDescriptions": { + "itemId": "The id of the item that was edited.", + "newLabel": "The new label of the items." + } + }, "onItemPositionChange": { "description": "Callback fired when a tree item is moved in the tree.", "typeDescriptions": { 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 3b94a5f524426..3ba5993e51879 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 @@ -45,6 +45,9 @@ "boolean": "true if the item should be disabled." } }, + "isItemEditable": { + "description": "Determines if a given item is editable or not. Make sure to also enable the labelEditing experimental feature: <RichTreeViewPro experimentalFeatures={{ labelEditing: true }} />. By default, the items are not editable." + }, "itemChildrenIndentation": { "description": "Horizontal indentation between an item and its children. Examples: 24, "24px", "2rem", "2em"." }, @@ -80,6 +83,13 @@ "itemId": "The id of the focused item." } }, + "onItemLabelChange": { + "description": "Callback fired when the label of an item changes.", + "typeDescriptions": { + "itemId": "The id of the item that was edited.", + "newLabel": "The new label of the items." + } + }, "onItemSelectionToggle": { "description": "Callback fired when a tree item is selected or deselected.", "typeDescriptions": { diff --git a/docs/translations/api-docs/tree-view/tree-item-2/tree-item-2.json b/docs/translations/api-docs/tree-view/tree-item-2/tree-item-2.json index 6a5f8d2ae8321..1ec2e89e98d8e 100644 --- a/docs/translations/api-docs/tree-view/tree-item-2/tree-item-2.json +++ b/docs/translations/api-docs/tree-view/tree-item-2/tree-item-2.json @@ -23,6 +23,15 @@ "nodeName": "the element", "conditions": "disabled" }, + "editable": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the content of the items that are editable" + }, + "editing": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the content element", + "conditions": "editing is enabled" + }, "expanded": { "description": "State class applied to {{nodeName}} when {{conditions}}.", "nodeName": "the content element", @@ -50,6 +59,7 @@ "icon": "The icon to display next to the tree item's label.", "iconContainer": "The component that renders the icon.", "label": "The component that renders the item label.", + "labelInput": "The component that renders the input to edit the label when the item is editable and is currently being edited.", "root": "The component that renders the root." } } diff --git a/docs/translations/api-docs/tree-view/tree-item/tree-item.json b/docs/translations/api-docs/tree-view/tree-item/tree-item.json index 596c008d70f16..f0febee14167b 100644 --- a/docs/translations/api-docs/tree-view/tree-item/tree-item.json +++ b/docs/translations/api-docs/tree-view/tree-item/tree-item.json @@ -41,6 +41,15 @@ "description": "Styles applied to {{nodeName}}.", "nodeName": "the drag and drop overlay" }, + "editable": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the content of the items that are editable" + }, + "editing": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the content element", + "conditions": "editing is enabled" + }, "expanded": { "description": "State class applied to {{nodeName}} when {{conditions}}.", "nodeName": "the content element", @@ -56,6 +65,11 @@ "nodeName": "the tree item icon" }, "label": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the label element" }, + "labelInput": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the input element that is visible", + "conditions": "editing is enabled" + }, "root": { "description": "Styles applied to the root element." }, "selected": { "description": "State class applied to {{nodeName}} when {{conditions}}.", diff --git a/docs/tsconfig.json b/docs/tsconfig.json index a552a39f3001a..1975578452299 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -10,6 +10,12 @@ "esModuleInterop": true, "incremental": true }, - "include": ["pages/**/*.ts*", "data/**/*", "src/modules/components/**/*", "next.config.mjs"], + "include": [ + "pages/**/*.ts*", + "data/**/*", + "src/modules/components/**/*", + "next.config.mjs", + "docs-env.d.ts" + ], "exclude": ["docs/.next", "docs/export", "pages/playground"] } diff --git a/netlify.toml b/netlify.toml index 767f5a910c795..4d663716bd273 100644 --- a/netlify.toml +++ b/netlify.toml @@ -10,7 +10,6 @@ [build.environment] NODE_VERSION = "18" NODE_OPTIONS = "--max_old_space_size=4096" - PNPM_FLAGS = "--shamefully-hoist" [[plugins]] - package = "./node_modules/@mui/monorepo/packages/netlify-plugin-cache-docs" + package = "./packages/netlify-plugin-cache-docs" diff --git a/netlify/functions/deploy-succeeded.js b/netlify/functions/deploy-succeeded.js index d59ce476da813..ad2d1a868b5ae 100644 --- a/netlify/functions/deploy-succeeded.js +++ b/netlify/functions/deploy-succeeded.js @@ -1,5 +1,3 @@ -const fetch = require('node-fetch'); - /** * @param {object} event * @param {string} event.body - https://jsoneditoronline.org/#left=cloud.fb1a4fa30a4f475fa6887071c682e2c1 diff --git a/package.json b/package.json index 1967ff6c811cc..ea094081d8f06 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "7.12.0", + "version": "7.15.0", "private": true, "scripts": { "preinstall": "npx only-allow pnpm", @@ -8,7 +8,7 @@ "docs:serve": "pnpm --filter docs serve", "docs:create-playground": "pnpm --filter docs create-playground", "docs:api": "NODE_OPTIONS=--max-old-space-size=4096 pnpm docs:api:build && pnpm docs:api:buildX", - "docs:api:build": "cross-env BABEL_ENV=development babel-node -i \"/node_modules/(?!@mui)/\" -x .ts,.tsx,.js ./scripts/buildApiDocs/index.ts", + "docs:api:build": "tsx ./scripts/buildApiDocs/index.ts", "docs:api:buildX": "cross-env BABEL_ENV=development babel-node -i \"/node_modules/(?!@mui)/\" -x .ts,.tsx,.js ./docs/scripts/api/buildApi.ts", "docs:link-check": "cross-env BABEL_ENV=development babel-node -i \"/node_modules/(?!@mui)/\" --extensions \".tsx,.ts,.js\" ./docs/scripts/reportBrokenLinks.js", "docs:build": "pnpm --filter docs build", @@ -55,6 +55,7 @@ "typescript": "lerna run --no-bail --parallel typescript", "typescript:ci": "lerna run --concurrency 1 --no-bail --no-sort typescript", "use-react-version": "node scripts/useReactVersion.mjs", + "use-material-ui-v6": "node scripts/useMaterialUIv6.mjs", "build:codesandbox": "lerna run --concurrency 3 --no-private --scope \"@mui/*\" build", "install:codesandbox": "pnpm install --no-frozen-lockfile", "release:changelog": "node scripts/releaseChangelog.mjs", @@ -69,54 +70,55 @@ "devDependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", - "@argos-ci/core": "^2.4.0", + "@argos-ci/core": "^2.5.1", "@babel/cli": "^7.24.8", "@babel/core": "^7.25.2", "@babel/node": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.25.4", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-constant-elements": "^7.25.1", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/preset-env": "^7.25.2", + "@babel/plugin-transform-runtime": "^7.25.4", + "@babel/preset-env": "^7.25.4", "@babel/preset-react": "^7.24.7", "@babel/preset-typescript": "^7.24.7", "@babel/register": "^7.24.6", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/traverse": "^7.25.4", + "@babel/types": "^7.25.4", "@emotion/cache": "^11.13.1", - "@emotion/react": "^11.13.0", + "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@mui/icons-material": "^5.16.5", - "@mui/internal-markdown": "^1.0.8", - "@mui/internal-test-utils": "^1.0.5", - "@mui/material": "^5.16.5", - "@mui/monorepo": "github:mui/material-ui#4a82b6b0e0395db8fa0a0d49b6b76de4516b1579", - "@mui/utils": "^5.16.5", - "@next/eslint-plugin-next": "14.2.5", + "@mui/icons-material": "^5.16.7", + "@mui/internal-babel-plugin-resolve-imports": "1.0.17", + "@mui/internal-markdown": "^1.0.11", + "@mui/internal-test-utils": "^1.0.11", + "@mui/material": "^5.16.7", + "@mui/monorepo": "github:mui/material-ui#8b8732eabd272226fdc858cdfc1dce8b0ce4b294", + "@mui/utils": "^5.16.6", + "@next/eslint-plugin-next": "14.2.7", "@octokit/plugin-retry": "^7.1.1", - "@octokit/rest": "^21.0.1", + "@octokit/rest": "^21.0.2", "@playwright/test": "^1.44.1", "@types/babel__core": "^7.20.5", "@types/babel__traverse": "^7.20.6", - "@types/chai": "^4.3.16", + "@types/chai": "^4.3.19", "@types/chai-dom": "^1.11.3", "@types/fs-extra": "^11.0.4", "@types/karma": "^6.3.8", "@types/lodash": "^4.17.7", "@types/mocha": "^10.0.7", - "@types/node": "^18.19.42", - "@types/react": "^18.3.3", + "@types/node": "^18.19.47", + "@types/react": "^18.3.4", "@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.16.1", - "@typescript-eslint/parser": "^7.16.1", - "autoprefixer": "^10.4.19", - "axe-core": "4.9.1", + "@types/yargs": "^17.0.33", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "autoprefixer": "^10.4.20", + "axe-core": "4.10.0", "babel-loader": "^9.1.3", "babel-plugin-istanbul": "^7.0.0", "babel-plugin-module-resolver": "^5.0.2", @@ -141,34 +143,35 @@ "eslint-import-resolver-webpack": "^0.13.8", "eslint-plugin-filenames": "^1.3.2", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsdoc": "^48.8.3", + "eslint-plugin-jsdoc": "^48.11.0", "eslint-plugin-jsx-a11y": "^6.9.0", "eslint-plugin-material-ui": "workspace:^", - "eslint-plugin-mocha": "^10.4.3", + "eslint-plugin-mocha": "^10.5.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.35.0", "eslint-plugin-react-compiler": "0.0.0-experimental-9ed098e-20240725", "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-testing-library": "^6.3.0", "fast-glob": "^3.3.2", "format-util": "^1.0.5", "fs-extra": "^11.2.0", "glob-gitignore": "^1.0.14", - "globby": "^14.0.1", + "globby": "^14.0.2", "html-webpack-plugin": "^5.6.0", - "jsdom": "24.1.1", + "jsdom": "24.1.3", "jss": "^10.10.0", "jss-plugin-template": "^10.10.0", "jss-rtl": "^0.3.0", - "karma": "^6.4.3", + "karma": "^6.4.4", "karma-chrome-launcher": "^3.2.0", "karma-mocha": "^2.0.1", "karma-parallel": "^0.3.1", "karma-sourcemap-loader": "^0.4.0", "karma-webpack": "^5.0.1", - "lerna": "^8.1.7", + "lerna": "^8.1.8", "lodash": "^4.17.21", "markdownlint-cli2": "^0.13.0", - "mocha": "^10.7.0", + "mocha": "^10.7.3", "moment": "^2.30.1", "moment-timezone": "^0.5.45", "nyc": "^17.0.0", @@ -178,28 +181,28 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "remark": "^13.0.0", - "rimraf": "^5.0.9", + "rimraf": "^5.0.10", "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.16.2", + "tsx": "^4.18.0", "typescript": "^5.5.4", - "unist-util-visit": "^2.0.3", + "unist-util-visit": "^5.0.0", "util": "^0.12.5", - "webpack": "^5.92.1", + "webpack": "^5.94.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", "yargs": "^17.7.2" }, "resolutions": { "react-is": "^18.3.1", - "@types/node": "^18.19.42" + "@types/node": "^18.19.47" }, - "packageManager": "pnpm@9.6.0", + "packageManager": "pnpm@9.8.0", "engines": { - "pnpm": "9.6.0" + "pnpm": "9.8.0" }, "pnpm": { "patchedDependencies": { diff --git a/packages/eslint-plugin-material-ui/package.json b/packages/eslint-plugin-material-ui/package.json index c5f013fb9f826..2a8687a71bd3a 100644 --- a/packages/eslint-plugin-material-ui/package.json +++ b/packages/eslint-plugin-material-ui/package.json @@ -5,9 +5,9 @@ "description": "Custom eslint rules for MUI X.", "main": "src/index.js", "devDependencies": { - "@types/eslint": "^8.56.11", - "@typescript-eslint/utils": "^7.16.1", - "@typescript-eslint/parser": "^7.16.1" + "@types/eslint": "^8.56.12", + "@typescript-eslint/utils": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0" }, "scripts": { "test": "cd ../../ && cross-env NODE_ENV=test mocha 'packages/eslint-plugin-material-ui/**/*.test.js' --timeout 3000" diff --git a/packages/netlify-plugin-cache-docs/index.js b/packages/netlify-plugin-cache-docs/index.js new file mode 100644 index 0000000000000..e8530f898af36 --- /dev/null +++ b/packages/netlify-plugin-cache-docs/index.js @@ -0,0 +1 @@ +module.exports = require('@mui/monorepo/packages/netlify-plugin-cache-docs'); diff --git a/packages/netlify-plugin-cache-docs/manifest.yml b/packages/netlify-plugin-cache-docs/manifest.yml new file mode 100644 index 0000000000000..46be74dddcce9 --- /dev/null +++ b/packages/netlify-plugin-cache-docs/manifest.yml @@ -0,0 +1 @@ +name: netlify-plugin-cache-docs diff --git a/packages/x-charts-pro/package.json b/packages/x-charts-pro/package.json index 28536720a38f9..00d4063a8ae87 100644 --- a/packages/x-charts-pro/package.json +++ b/packages/x-charts-pro/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-charts-pro", - "version": "7.0.0-alpha.0", + "version": "7.0.0-alpha.3", "description": "The Pro plan edition of the Charts components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -39,12 +39,11 @@ "directory": "packages/x-charts-pro" }, "dependencies": { - "@babel/runtime": "^7.25.0", - "@mui/system": "^5.16.5", - "@mui/utils": "^5.16.5", + "@babel/runtime": "^7.25.4", + "@mui/utils": "^5.16.6", "@mui/x-charts": "workspace:*", - "@mui/x-license": "workspace:*", "@mui/x-charts-vendor": "workspace:*", + "@mui/x-license": "workspace:*", "@react-spring/rafz": "^9.7.4", "@react-spring/web": "^9.7.4", "clsx": "^2.1.1", @@ -53,7 +52,8 @@ "peerDependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" }, @@ -66,11 +66,13 @@ } }, "devDependencies": { + "@mui/material": "^5.16.7", + "@mui/system": "^5.16.7", "@react-spring/core": "^9.7.4", "@react-spring/shared": "^9.7.4", "@types/prop-types": "^15.7.12", "csstype": "^3.1.3", - "rimraf": "^5.0.9" + "rimraf": "^5.0.10" }, "engines": { "node": ">=14.0.0" diff --git a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx index 9f03659068b1b..e7c1e3c3cac0d 100644 --- a/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx +++ b/packages/x-charts-pro/src/BarChartPro/BarChartPro.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import { useThemeProps } from '@mui/material/styles'; import { BarChartProps, BarPlot } from '@mui/x-charts/BarChart'; import { ChartsOnAxisClickHandler } from '@mui/x-charts/ChartsOnAxisClickHandler'; import { ChartsGrid } from '@mui/x-charts/ChartsGrid'; @@ -16,6 +17,12 @@ import { ZoomSetup } from '../context/ZoomProvider/ZoomSetup'; import { useZoom } from '../context/ZoomProvider/useZoom'; import { ZoomProps } from '../context/ZoomProvider'; +function BarChartPlotZoom(props: BarPlotProps) { + const { isInteracting } = useZoom(); + + return ; +} + export interface BarChartProProps extends BarChartProps, ZoomProps {} /** @@ -29,7 +36,8 @@ export interface BarChartProProps extends BarChartProps, ZoomProps {} * * - [BarChart API](https://mui.com/x/api/charts/bar-chart/) */ -const BarChartPro = React.forwardRef(function BarChartPro(props: BarChartProProps, ref) { +const BarChartPro = React.forwardRef(function BarChartPro(inProps: BarChartProProps, ref) { + const props = useThemeProps({ props: inProps, name: 'MuiBarChartPro' }); const { zoom, onZoomChange, ...other } = props; const { chartContainerProps, @@ -54,7 +62,7 @@ const BarChartPro = React.forwardRef(function BarChartPro(props: BarChartProProp onZoomChange={onZoomChange} > {props.onAxisClick && } - {props.grid && } + @@ -200,6 +208,12 @@ BarChartPro.propTypes = { * @param {BarItemIdentifier} barItemIdentifier The bar item identifier. */ onItemClick: PropTypes.func, + /** + * Callback fired when the zoom has changed. + * + * @param {ZoomData[]} zoomData Updated zoom data. + */ + onZoomChange: PropTypes.func, /** * Indicate which axis to display the right of the charts. * Can be a string (the id of the axis) or an object `ChartsYAxisProps`. @@ -267,7 +281,6 @@ BarChartPro.propTypes = { */ xAxis: PropTypes.arrayOf( PropTypes.shape({ - axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, colorMap: PropTypes.oneOfType([ PropTypes.shape({ @@ -314,6 +327,11 @@ BarChartPro.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -331,6 +349,7 @@ BarChartPro.propTypes = { valueFormatter: PropTypes.func, zoom: PropTypes.oneOfType([ PropTypes.shape({ + filterMode: PropTypes.oneOf(['discard', 'keep']), maxEnd: PropTypes.number, maxSpan: PropTypes.number, minSpan: PropTypes.number, @@ -349,7 +368,6 @@ BarChartPro.propTypes = { */ yAxis: PropTypes.arrayOf( PropTypes.shape({ - axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, colorMap: PropTypes.oneOfType([ PropTypes.shape({ @@ -396,6 +414,11 @@ BarChartPro.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -413,6 +436,7 @@ BarChartPro.propTypes = { valueFormatter: PropTypes.func, zoom: PropTypes.oneOfType([ PropTypes.shape({ + filterMode: PropTypes.oneOf(['discard', 'keep']), maxEnd: PropTypes.number, maxSpan: PropTypes.number, minSpan: PropTypes.number, @@ -424,52 +448,16 @@ BarChartPro.propTypes = { ]), }), ), -} as any; - -function BarChartPlotZoom(props: BarPlotProps) { - const { isInteracting } = useZoom(); - - return ; -} - -BarChartPlotZoom.propTypes = { - // ----------------------------- Warning -------------------------------- - // | These PropTypes are generated from the TypeScript type definitions | - // | To update them edit the TypeScript types and run "pnpm proptypes" | - // ---------------------------------------------------------------------- /** - * If provided, the function will be used to format the label of the bar. - * It can be set to 'value' to display the current value. - * @param {BarItem} item The item to format. - * @param {BarLabelContext} context data about the bar. - * @returns {string} The formatted label. + * The list of zoom data related to each axis. */ - barLabel: PropTypes.oneOfType([PropTypes.oneOf(['value']), PropTypes.func]), - /** - * Defines the border radius of the bar element. - */ - borderRadius: PropTypes.number, - /** - * Callback fired when a bar item is clicked. - * @param {React.MouseEvent} event The event source of the callback. - * @param {BarItemIdentifier} barItemIdentifier The bar item identifier. - */ - onItemClick: PropTypes.func, - /** - * If `true`, animations are skipped. - * @default false - */ - skipAnimation: PropTypes.bool, - /** - * The props used for each component slot. - * @default {} - */ - slotProps: PropTypes.object, - /** - * Overridable component slots. - * @default {} - */ - slots: PropTypes.object, + zoom: PropTypes.arrayOf( + PropTypes.shape({ + axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + end: PropTypes.number.isRequired, + start: PropTypes.number.isRequired, + }), + ), } as any; export { BarChartPro }; diff --git a/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx b/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx index f6eb0236e4c0b..f4d006eba1cee 100644 --- a/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx +++ b/packages/x-charts-pro/src/ChartContainerPro/ChartContainerPro.tsx @@ -12,7 +12,7 @@ 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 { CartesianProviderPro } from '../context/CartesianProviderPro'; import { ZoomProps, ZoomProvider } from '../context/ZoomProvider'; import { useChartContainerProProps } from './useChartContainerProProps'; @@ -30,7 +30,7 @@ const ChartContainerPro = React.forwardRef(function ChartContainer( seriesProviderProps, zAxisContextProps, highlightedProviderProps, - cartesianContextProps, + cartesianProviderProps, chartsSurfaceProps, pluginProviderProps, children, @@ -41,9 +41,9 @@ const ChartContainerPro = React.forwardRef(function ChartContainer( return ( - - - + + + @@ -54,9 +54,9 @@ const ChartContainerPro = React.forwardRef(function ChartContainer( - - - + + + ); @@ -200,6 +200,11 @@ ChartContainerPro.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -217,6 +222,7 @@ ChartContainerPro.propTypes = { valueFormatter: PropTypes.func, zoom: PropTypes.oneOfType([ PropTypes.shape({ + filterMode: PropTypes.oneOf(['discard', 'keep']), maxEnd: PropTypes.number, maxSpan: PropTypes.number, minSpan: PropTypes.number, @@ -281,6 +287,11 @@ ChartContainerPro.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -298,6 +309,7 @@ ChartContainerPro.propTypes = { valueFormatter: PropTypes.func, zoom: PropTypes.oneOfType([ PropTypes.shape({ + filterMode: PropTypes.oneOf(['discard', 'keep']), maxEnd: PropTypes.number, maxSpan: PropTypes.number, minSpan: PropTypes.number, diff --git a/packages/x-charts-pro/src/ChartContainerPro/useChartContainerProProps.ts b/packages/x-charts-pro/src/ChartContainerPro/useChartContainerProProps.ts index a0c090029f9ee..dd4318a09f67c 100644 --- a/packages/x-charts-pro/src/ChartContainerPro/useChartContainerProProps.ts +++ b/packages/x-charts-pro/src/ChartContainerPro/useChartContainerProProps.ts @@ -12,7 +12,7 @@ export const useChartContainerProProps = ( children, drawingProviderProps, seriesProviderProps, - cartesianContextProps, + cartesianProviderProps, zAxisContextProps, highlightedProviderProps, chartsSurfaceProps, @@ -34,7 +34,7 @@ export const useChartContainerProProps = ( drawingProviderProps, pluginProviderProps, seriesProviderProps, - cartesianContextProps, + cartesianProviderProps, zAxisContextProps, highlightedProviderProps, chartsSurfaceProps, diff --git a/packages/x-charts-pro/src/Heatmap/Heatmap.tsx b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx index 5c72d19e8ea6f..eac1180542627 100644 --- a/packages/x-charts-pro/src/Heatmap/Heatmap.tsx +++ b/packages/x-charts-pro/src/Heatmap/Heatmap.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import { interpolateRgbBasis } from '@mui/x-charts-vendor/d3-interpolate'; +import { useThemeProps } from '@mui/material/styles'; import useId from '@mui/utils/useId'; +import { interpolateRgbBasis } from '@mui/x-charts-vendor/d3-interpolate'; import { ChartsAxis, ChartsAxisProps } from '@mui/x-charts/ChartsAxis'; import { ChartsTooltip, @@ -104,7 +105,8 @@ const defaultColorMap = interpolateRgbBasis([ '#084081', ]); -const Heatmap = React.forwardRef(function Heatmap(props: HeatmapProps, ref) { +const Heatmap = React.forwardRef(function Heatmap(inProps: HeatmapProps, ref) { + const props = useThemeProps({ props: inProps, name: 'MuiHeatmap' }); const { xAxis, yAxis, @@ -393,6 +395,11 @@ Heatmap.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -410,6 +417,7 @@ Heatmap.propTypes = { valueFormatter: PropTypes.func, zoom: PropTypes.oneOfType([ PropTypes.shape({ + filterMode: PropTypes.oneOf(['discard', 'keep']), maxEnd: PropTypes.number, maxSpan: PropTypes.number, minSpan: PropTypes.number, @@ -476,6 +484,11 @@ Heatmap.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -493,6 +506,7 @@ Heatmap.propTypes = { valueFormatter: PropTypes.func, zoom: PropTypes.oneOfType([ PropTypes.shape({ + filterMode: PropTypes.oneOf(['discard', 'keep']), maxEnd: PropTypes.number, maxSpan: PropTypes.number, minSpan: PropTypes.number, diff --git a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx index 323d37fb05d53..a85dbc3121e5c 100644 --- a/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx +++ b/packages/x-charts-pro/src/LineChartPro/LineChartPro.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import { useThemeProps } from '@mui/material/styles'; import { AreaPlot, AreaPlotProps, @@ -24,6 +25,21 @@ import { ZoomSetup } from '../context/ZoomProvider/ZoomSetup'; import { useZoom } from '../context/ZoomProvider/useZoom'; import { ZoomProps } from '../context/ZoomProvider'; +function AreaPlotZoom(props: AreaPlotProps) { + const { isInteracting } = useZoom(); + return ; +} + +function LinePlotZoom(props: LinePlotProps) { + const { isInteracting } = useZoom(); + return ; +} + +function MarkPlotZoom(props: MarkPlotProps) { + const { isInteracting } = useZoom(); + return ; +} + export interface LineChartProProps extends LineChartProps, ZoomProps {} /** @@ -36,7 +52,8 @@ export interface LineChartProProps extends LineChartProps, ZoomProps {} * * - [LineChart API](https://mui.com/x/api/charts/line-chart/) */ -const LineChartPro = React.forwardRef(function LineChartPro(props: LineChartProProps, ref) { +const LineChartPro = React.forwardRef(function LineChartPro(inProps: LineChartProProps, ref) { + const props = useThemeProps({ props: inProps, name: 'MuiLineChartPro' }); const { zoom, onZoomChange, ...other } = props; const { chartContainerProps, @@ -64,7 +81,7 @@ const LineChartPro = React.forwardRef(function LineChartPro(props: LineChartProP onZoomChange={onZoomChange} > {props.onAxisClick && } - {props.grid && } + @@ -208,6 +225,12 @@ LineChartPro.propTypes = { * Callback fired when a mark element is clicked. */ onMarkClick: PropTypes.func, + /** + * Callback fired when the zoom has changed. + * + * @param {ZoomData[]} zoomData Updated zoom data. + */ + onZoomChange: PropTypes.func, /** * Indicate which axis to display the right of the charts. * Can be a string (the id of the axis) or an object `ChartsYAxisProps`. @@ -276,7 +299,6 @@ LineChartPro.propTypes = { */ xAxis: PropTypes.arrayOf( PropTypes.shape({ - axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, colorMap: PropTypes.oneOfType([ PropTypes.shape({ @@ -323,6 +345,11 @@ LineChartPro.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -340,6 +367,7 @@ LineChartPro.propTypes = { valueFormatter: PropTypes.func, zoom: PropTypes.oneOfType([ PropTypes.shape({ + filterMode: PropTypes.oneOf(['discard', 'keep']), maxEnd: PropTypes.number, maxSpan: PropTypes.number, minSpan: PropTypes.number, @@ -358,7 +386,6 @@ LineChartPro.propTypes = { */ yAxis: PropTypes.arrayOf( PropTypes.shape({ - axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, colorMap: PropTypes.oneOfType([ PropTypes.shape({ @@ -405,6 +432,11 @@ LineChartPro.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -422,6 +454,7 @@ LineChartPro.propTypes = { valueFormatter: PropTypes.func, zoom: PropTypes.oneOfType([ PropTypes.shape({ + filterMode: PropTypes.oneOf(['discard', 'keep']), maxEnd: PropTypes.number, maxSpan: PropTypes.number, minSpan: PropTypes.number, @@ -433,105 +466,16 @@ LineChartPro.propTypes = { ]), }), ), -} as any; - -function MarkPlotZoom(props: MarkPlotProps) { - const { isInteracting } = useZoom(); - return ; -} - -MarkPlotZoom.propTypes = { - // ----------------------------- Warning -------------------------------- - // | These PropTypes are generated from the TypeScript type definitions | - // | To update them edit the TypeScript types and run "pnpm proptypes" | - // ---------------------------------------------------------------------- - /** - * Callback fired when a line mark item is clicked. - * @param {React.MouseEvent} event The event source of the callback. - * @param {LineItemIdentifier} lineItemIdentifier The line mark item identifier. - */ - onItemClick: PropTypes.func, /** - * If `true`, animations are skipped. - * @default false + * The list of zoom data related to each axis. */ - skipAnimation: PropTypes.bool, - /** - * The props used for each component slot. - * @default {} - */ - slotProps: PropTypes.object, - /** - * Overridable component slots. - * @default {} - */ - slots: PropTypes.object, -} as any; - -function LinePlotZoom(props: LinePlotProps) { - const { isInteracting } = useZoom(); - return ; -} - -LinePlotZoom.propTypes = { - // ----------------------------- Warning -------------------------------- - // | These PropTypes are generated from the TypeScript type definitions | - // | To update them edit the TypeScript types and run "pnpm proptypes" | - // ---------------------------------------------------------------------- - /** - * Callback fired when a line item is clicked. - * @param {React.MouseEvent} event The event source of the callback. - * @param {LineItemIdentifier} lineItemIdentifier The line item identifier. - */ - onItemClick: PropTypes.func, - /** - * If `true`, animations are skipped. - * @default false - */ - skipAnimation: PropTypes.bool, - /** - * The props used for each component slot. - * @default {} - */ - slotProps: PropTypes.object, - /** - * Overridable component slots. - * @default {} - */ - slots: PropTypes.object, -} as any; - -function AreaPlotZoom(props: AreaPlotProps) { - const { isInteracting } = useZoom(); - return ; -} - -AreaPlotZoom.propTypes = { - // ----------------------------- Warning -------------------------------- - // | These PropTypes are generated from the TypeScript type definitions | - // | To update them edit the TypeScript types and run "pnpm proptypes" | - // ---------------------------------------------------------------------- - /** - * Callback fired when a line area item is clicked. - * @param {React.MouseEvent} event The event source of the callback. - * @param {LineItemIdentifier} lineItemIdentifier The line item identifier. - */ - onItemClick: PropTypes.func, - /** - * If `true`, animations are skipped. - * @default false - */ - skipAnimation: PropTypes.bool, - /** - * The props used for each component slot. - * @default {} - */ - slotProps: PropTypes.object, - /** - * Overridable component slots. - * @default {} - */ - slots: PropTypes.object, + zoom: PropTypes.arrayOf( + PropTypes.shape({ + axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + end: PropTypes.number.isRequired, + start: PropTypes.number.isRequired, + }), + ), } as any; 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 4d5e4565a271e..569724f5829d7 100644 --- a/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.test.tsx +++ b/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { createRenderer, screen, waitFor } from '@mui/internal-test-utils'; +import { createRenderer, screen } from '@mui/internal-test-utils'; import { LicenseInfo } from '@mui/x-license'; import { sharedLicenseStatuses } from '@mui/x-license/useLicenseVerifier/useLicenseVerifier'; import { ResponsiveChartContainerPro } from './ResponsiveChartContainerPro'; @@ -21,8 +21,6 @@ describe(' - License', () => { render(), ).toErrorDev(['MUI X: Missing license key.']); - await waitFor(() => { - expect(screen.findAllByText('MUI X Missing license key')).not.to.equal(null); - }); + expect(await screen.findAllByText('MUI X Missing license key')).not.to.equal(null); }); }); diff --git a/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.tsx b/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.tsx index 2a3fafbe31241..e7a1f932174b3 100644 --- a/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.tsx +++ b/packages/x-charts-pro/src/ResponsiveChartContainerPro/ResponsiveChartContainerPro.tsx @@ -167,6 +167,11 @@ ResponsiveChartContainerPro.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -184,6 +189,7 @@ ResponsiveChartContainerPro.propTypes = { valueFormatter: PropTypes.func, zoom: PropTypes.oneOfType([ PropTypes.shape({ + filterMode: PropTypes.oneOf(['discard', 'keep']), maxEnd: PropTypes.number, maxSpan: PropTypes.number, minSpan: PropTypes.number, @@ -248,6 +254,11 @@ ResponsiveChartContainerPro.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -265,6 +276,7 @@ ResponsiveChartContainerPro.propTypes = { valueFormatter: PropTypes.func, zoom: PropTypes.oneOfType([ PropTypes.shape({ + filterMode: PropTypes.oneOf(['discard', 'keep']), maxEnd: PropTypes.number, maxSpan: PropTypes.number, minSpan: PropTypes.number, diff --git a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx index 0a6b54bfe0583..cf9a928faa737 100644 --- a/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx +++ b/packages/x-charts-pro/src/ScatterChartPro/ScatterChartPro.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import { useThemeProps } from '@mui/material/styles'; import { ChartsOverlay } from '@mui/x-charts/ChartsOverlay'; import { ScatterChartProps, ScatterPlot } from '@mui/x-charts/ScatterChart'; import { ZAxisContextProvider } from '@mui/x-charts/context'; @@ -27,9 +28,10 @@ export interface ScatterChartProProps extends ScatterChartProps, ZoomProps {} * - [ScatterChart API](https://mui.com/x/api/charts/scatter-chart/) */ const ScatterChartPro = React.forwardRef(function ScatterChartPro( - props: ScatterChartProProps, + inProps: ScatterChartProProps, ref, ) { + const props = useThemeProps({ props: inProps, name: 'MuiScatterChartPro' }); const { zoom, onZoomChange, ...other } = props; const { chartContainerProps, @@ -55,7 +57,7 @@ const ScatterChartPro = React.forwardRef(function ScatterChartPro( {!props.disableVoronoi && } - {props.grid && } + {/* The `data-drawing-container` indicates that children are part of the drawing area. Ref: https://github.com/mui/mui-x/issues/13659 */} @@ -301,6 +303,11 @@ ScatterChartPro.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -318,6 +325,7 @@ ScatterChartPro.propTypes = { valueFormatter: PropTypes.func, zoom: PropTypes.oneOfType([ PropTypes.shape({ + filterMode: PropTypes.oneOf(['discard', 'keep']), maxEnd: PropTypes.number, maxSpan: PropTypes.number, minSpan: PropTypes.number, @@ -382,6 +390,11 @@ ScatterChartPro.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -399,6 +412,7 @@ ScatterChartPro.propTypes = { valueFormatter: PropTypes.func, zoom: PropTypes.oneOfType([ PropTypes.shape({ + filterMode: PropTypes.oneOf(['discard', 'keep']), maxEnd: PropTypes.number, maxSpan: PropTypes.number, minSpan: PropTypes.number, diff --git a/packages/x-charts-pro/src/context/CartesianProviderPro/CartesianProviderPro.tsx b/packages/x-charts-pro/src/context/CartesianProviderPro/CartesianProviderPro.tsx index 9b86a03944464..462606015b80f 100644 --- a/packages/x-charts-pro/src/context/CartesianProviderPro/CartesianProviderPro.tsx +++ b/packages/x-charts-pro/src/context/CartesianProviderPro/CartesianProviderPro.tsx @@ -3,26 +3,66 @@ import { useDrawingArea, useSeries, CartesianContext, - CartesianContextProviderProps, + CartesianProviderProps, cartesianProviderUtils, useXExtremumGetter, useYExtremumGetter, + ZoomAxisFilters, } from '@mui/x-charts/internals'; import { useZoom } from '../ZoomProvider/useZoom'; +import { createAxisFilterMapper, createGetAxisFilters } from './createAxisFilterMapper'; const { computeValue } = cartesianProviderUtils; -export interface CartesianContextProviderProProps extends CartesianContextProviderProps {} +export interface CartesianProviderProProps extends CartesianProviderProps {} -function CartesianContextProviderPro(props: CartesianContextProviderProProps) { - const { xAxis, yAxis, dataset, children } = props; +function CartesianProviderPro(props: CartesianProviderProProps) { + const { xAxis, yAxis, children } = props; const formattedSeries = useSeries(); const drawingArea = useDrawingArea(); - const { zoomData } = useZoom(); + const { zoomData, options } = useZoom(); const xExtremumGetters = useXExtremumGetter(); const yExtremumGetters = useYExtremumGetter(); + const getFilters = React.useMemo(() => { + const xMapper = createAxisFilterMapper({ + zoomData, + extremumGetter: xExtremumGetters, + formattedSeries, + direction: 'x', + }); + + const yMapper = createAxisFilterMapper({ + zoomData, + extremumGetter: yExtremumGetters, + formattedSeries, + direction: 'y', + }); + + const xFilters = xAxis.reduce((acc, axis, index) => { + const filter = xMapper(axis, index); + if (filter !== null) { + acc[axis.id] = filter; + } + return acc; + }, {} as ZoomAxisFilters); + + const yFilters = yAxis.reduce((acc, axis, index) => { + const filter = yMapper(axis, index); + if (filter !== null) { + acc[axis.id] = filter; + } + return acc; + }, {} as ZoomAxisFilters); + + if (Object.keys(xFilters).length === 0 && Object.keys(yFilters).length === 0) { + return undefined; + } + + return createGetAxisFilters({ ...xFilters, ...yFilters }); + }, [formattedSeries, xAxis, xExtremumGetters, yAxis, yExtremumGetters, zoomData]); + const xValues = React.useMemo( () => computeValue({ @@ -30,11 +70,12 @@ function CartesianContextProviderPro(props: CartesianContextProviderProProps) { formattedSeries, axis: xAxis, extremumGetters: xExtremumGetters, - dataset, axisDirection: 'x', zoomData, + zoomOptions: options, + getFilters, }), - [drawingArea, formattedSeries, xAxis, xExtremumGetters, dataset, zoomData], + [drawingArea, formattedSeries, xAxis, xExtremumGetters, zoomData, options, getFilters], ); const yValues = React.useMemo( @@ -44,11 +85,12 @@ function CartesianContextProviderPro(props: CartesianContextProviderProProps) { formattedSeries, axis: yAxis, extremumGetters: yExtremumGetters, - dataset, axisDirection: 'y', zoomData, + zoomOptions: options, + getFilters, }), - [drawingArea, formattedSeries, yAxis, yExtremumGetters, dataset, zoomData], + [drawingArea, formattedSeries, yAxis, yExtremumGetters, zoomData, options, getFilters], ); const value = React.useMemo( @@ -67,4 +109,4 @@ function CartesianContextProviderPro(props: CartesianContextProviderProProps) { return {children}; } -export { CartesianContextProviderPro }; +export { CartesianProviderPro }; diff --git a/packages/x-charts-pro/src/context/CartesianProviderPro/createAxisFilterMapper.ts b/packages/x-charts-pro/src/context/CartesianProviderPro/createAxisFilterMapper.ts new file mode 100644 index 0000000000000..3a94d3512bb8d --- /dev/null +++ b/packages/x-charts-pro/src/context/CartesianProviderPro/createAxisFilterMapper.ts @@ -0,0 +1,88 @@ +import { + getAxisExtremum, + FormattedSeries, + ExtremumGettersConfig, + ExtremumFilter, + ZoomAxisFilters, + GetZoomAxisFilters, + isDefined, + getScale, +} from '@mui/x-charts/internals'; +import { ChartsAxisProps, ScaleName, AxisConfig } from '@mui/x-charts'; +import { ZoomData } from '../ZoomProvider'; + +type CreateAxisFilterMapperParams = { + zoomData: ZoomData[]; + extremumGetter: ExtremumGettersConfig; + formattedSeries: FormattedSeries; + direction: 'x' | 'y'; +}; + +export const createAxisFilterMapper = + ({ zoomData, extremumGetter, formattedSeries, direction }: CreateAxisFilterMapperParams) => + (axis: AxisConfig, axisIndex: number): ExtremumFilter | null => { + if (typeof axis.zoom !== 'object' || axis.zoom.filterMode !== 'discard') { + return null; + } + + const zoom = zoomData?.find(({ axisId }) => axisId === axis.id); + + if (zoom === undefined || (zoom.start <= 0 && zoom.end >= 100)) { + // No zoom, or zoom with all data visible + return null; + } + + let extremums: number[] = []; + const scaleType = axis.scaleType; + + if (scaleType === 'point' || scaleType === 'band') { + extremums = [0, (axis.data?.length ?? 1) - 1]; + } else { + extremums = getAxisExtremum(axis, extremumGetter, axisIndex === 0, formattedSeries); + } + + let min: number | Date; + let max: number | Date; + + // @ts-expect-error The function defaults to linear scale if the scaleType is not recognized. + [min, max] = getScale(scaleType, extremums, [0, 100]).nice().domain(); + + min = min instanceof Date ? min.getTime() : min; + max = max instanceof Date ? max.getTime() : max; + + const minVal = min + (zoom.start * (max - min)) / 100; + const maxVal = min + (zoom.end * (max - min)) / 100; + + return (value, dataIndex) => { + const val = value[direction] ?? axis.data?.[dataIndex]; + + if (val == null) { + // If the value does not exist because of missing data point, or out of range index, we just ignore. + return true; + } + + if (axis.scaleType === 'point' || axis.scaleType === 'band' || typeof val === 'string') { + return dataIndex >= minVal && dataIndex <= maxVal; + } + + return val >= minVal && val <= maxVal; + }; + }; + +export const createGetAxisFilters = + (filters: ZoomAxisFilters): GetZoomAxisFilters => + ({ currentAxisId, seriesXAxisId, seriesYAxisId, isDefaultAxis }) => { + return (value, dataIndex) => { + const axisId = currentAxisId === seriesXAxisId ? seriesYAxisId : seriesXAxisId; + + if (!axisId || isDefaultAxis) { + return Object.values(filters ?? {})[0]?.(value, dataIndex) ?? true; + } + + const data = [seriesYAxisId, seriesXAxisId] + .filter((id) => id !== currentAxisId) + .map((id) => filters[id ?? '']) + .filter(isDefined); + return data.every((f) => f(value, dataIndex)); + }; + }; diff --git a/packages/x-charts-pro/src/context/ZoomProvider/Zoom.types.ts b/packages/x-charts-pro/src/context/ZoomProvider/Zoom.types.ts index ac3f9990fc69a..4943a6750f020 100644 --- a/packages/x-charts-pro/src/context/ZoomProvider/Zoom.types.ts +++ b/packages/x-charts-pro/src/context/ZoomProvider/Zoom.types.ts @@ -96,6 +96,16 @@ export type ZoomOptions = { * @default true */ panning?: boolean; + /** + * Defines how to filter the axis data when it is outside of the zoomed range of this axis. + * + * - `keep`: The data outside the zoomed range is kept. And the other axes will stay the same. + * - `discard`: The data outside the zoomed range is discarded for the other axes. + * The other axes will be adjusted to fit the zoomed range. + * + * @default 'keep' + */ + filterMode?: 'discard' | 'keep'; }; export type ZoomData = { diff --git a/packages/x-charts-pro/src/context/ZoomProvider/defaultizeZoom.ts b/packages/x-charts-pro/src/context/ZoomProvider/defaultizeZoom.ts index d32a4ac154106..4f4c558e82b1c 100644 --- a/packages/x-charts-pro/src/context/ZoomProvider/defaultizeZoom.ts +++ b/packages/x-charts-pro/src/context/ZoomProvider/defaultizeZoom.ts @@ -1,13 +1,14 @@ import { isDefined } from '@mui/x-charts/internals'; -import { AxisConfigForZoom, DefaultizedZoomOptions } from './Zoom.types'; +import { AxisConfigForZoom, DefaultizedZoomOptions, ZoomOptions } from './Zoom.types'; -const defaultZoomOptions = { +const defaultZoomOptions: Required = { minStart: 0, maxEnd: 100, step: 5, minSpan: 10, maxSpan: 100, panning: true, + filterMode: 'keep', }; export const defaultizeZoom = ( diff --git a/packages/x-charts-pro/src/tests/materialVersion.test.tsx b/packages/x-charts-pro/src/tests/materialVersion.test.tsx new file mode 100644 index 0000000000000..dc0c0a7e6ee01 --- /dev/null +++ b/packages/x-charts-pro/src/tests/materialVersion.test.tsx @@ -0,0 +1,5 @@ +import materialPackageJson from '@mui/material/package.json'; +import { checkMaterialVersion } from 'test/utils/checkMaterialVersion'; +import packageJson from '../../package.json'; + +checkMaterialVersion({ packageJson, materialPackageJson }); diff --git a/packages/x-charts-pro/src/themeAugmentation/components.d.ts b/packages/x-charts-pro/src/themeAugmentation/components.d.ts new file mode 100644 index 0000000000000..1638a9e2a2844 --- /dev/null +++ b/packages/x-charts-pro/src/themeAugmentation/components.d.ts @@ -0,0 +1,25 @@ +import { ComponentsProps, ComponentsOverrides } from '@mui/material/styles'; + +export interface ChartsProComponents { + // BarChartPro components + MuiBarChartPro?: { + defaultProps?: ComponentsProps['MuiBarChartPro']; + }; + // LineChartPro components + MuiLineChartPro?: { + defaultProps?: ComponentsProps['MuiLineChartPro']; + }; + // Heatmap components + MuiHeatmap?: { + defaultProps?: ComponentsProps['MuiHeatmap']; + styleOverrides?: ComponentsOverrides['MuiHeatmap']; + }; + // ScatterChartPro components + MuiScatterChartPro?: { + defaultProps?: ComponentsProps['MuiScatterChartPro']; + }; +} + +declare module '@mui/material/styles' { + interface Components extends ChartsProComponents {} +} diff --git a/packages/x-charts-pro/src/themeAugmentation/index.js b/packages/x-charts-pro/src/themeAugmentation/index.js new file mode 100644 index 0000000000000..6467405078b09 --- /dev/null +++ b/packages/x-charts-pro/src/themeAugmentation/index.js @@ -0,0 +1 @@ +// Prefer to use `import type {} from '@mui/x-charts-pro/themeAugmentation';` instead to avoid importing an empty file. diff --git a/packages/x-charts-pro/src/themeAugmentation/index.ts b/packages/x-charts-pro/src/themeAugmentation/index.ts new file mode 100644 index 0000000000000..52475626c4f0f --- /dev/null +++ b/packages/x-charts-pro/src/themeAugmentation/index.ts @@ -0,0 +1,4 @@ +export type * from '@mui/x-charts/themeAugmentation'; +export type * from './overrides'; +export type * from './props'; +export type * from './components'; diff --git a/packages/x-charts-pro/src/themeAugmentation/overrides.d.ts b/packages/x-charts-pro/src/themeAugmentation/overrides.d.ts new file mode 100644 index 0000000000000..b57a5f0362d60 --- /dev/null +++ b/packages/x-charts-pro/src/themeAugmentation/overrides.d.ts @@ -0,0 +1,13 @@ +import { HeatmapClassKey } from '../Heatmap'; + +export interface ChartsProComponentNameToClassKey { + // Heatmap components + MuiHeatmap: HeatmapClassKey; +} + +declare module '@mui/material/styles' { + interface ComponentNameToClassKey extends ChartsProComponentNameToClassKey {} +} + +// disable automatic export +export {}; diff --git a/packages/x-charts-pro/src/themeAugmentation/props.d.ts b/packages/x-charts-pro/src/themeAugmentation/props.d.ts new file mode 100644 index 0000000000000..5012285682988 --- /dev/null +++ b/packages/x-charts-pro/src/themeAugmentation/props.d.ts @@ -0,0 +1,22 @@ +import { ScatterChartProProps } from '../ScatterChartPro'; +import { BarChartProProps } from '../BarChartPro'; +import { HeatmapProps } from '../Heatmap/Heatmap'; +import { LineChartProProps } from '../LineChartPro'; + +export interface ChartsProComponentsPropsList { + // BarChartPro components + MuiBarChartPro: BarChartProProps; + // LineChartPro components + MuiLineChartPro: LineChartProProps; + // Heatmap components + MuiHeatmap: HeatmapProps; + // ScatterChartPro components + MuiScatterChartPro: ScatterChartProProps; +} + +declare module '@mui/material/styles' { + interface ComponentsPropsList extends ChartsProComponentsPropsList {} +} + +// disable automatic export +export {}; diff --git a/packages/x-charts-pro/src/themeAugmentation/themeAugmentation.spec.ts b/packages/x-charts-pro/src/themeAugmentation/themeAugmentation.spec.ts new file mode 100644 index 0000000000000..074742162da11 --- /dev/null +++ b/packages/x-charts-pro/src/themeAugmentation/themeAugmentation.spec.ts @@ -0,0 +1,39 @@ +import { createTheme } from '@mui/material/styles'; + +createTheme({ + components: { + MuiBarChartPro: { + defaultProps: { + title: 'toto', + // @ts-expect-error invalid MuiChartsAxis prop + someRandomProp: true, + }, + }, + MuiLineChartPro: { + defaultProps: { + title: 'toto', + // @ts-expect-error invalid MuiChartsAxis prop + someRandomProp: true, + }, + }, + MuiScatterChartPro: { + defaultProps: { + title: 'toto', + // @ts-expect-error invalid MuiChartsAxis prop + someRandomProp: true, + }, + }, + MuiHeatmap: { + defaultProps: { + title: 'toto', + // @ts-expect-error invalid MuiChartsAxis prop + someRandomProp: true, + }, + styleOverrides: { + highlighted: { backgroundColor: 'red' }, + // @ts-expect-error invalid MuiChartsAxis class key + constent: { color: 'red' }, + }, + }, + }, +}); diff --git a/packages/x-charts-vendor/es/d3-array.js b/packages/x-charts-vendor/es/d3-array.mjs similarity index 100% rename from packages/x-charts-vendor/es/d3-array.js rename to packages/x-charts-vendor/es/d3-array.mjs diff --git a/packages/x-charts-vendor/es/d3-color.js b/packages/x-charts-vendor/es/d3-color.mjs similarity index 100% rename from packages/x-charts-vendor/es/d3-color.js rename to packages/x-charts-vendor/es/d3-color.mjs diff --git a/packages/x-charts-vendor/es/d3-delaunay.js b/packages/x-charts-vendor/es/d3-delaunay.mjs similarity index 100% rename from packages/x-charts-vendor/es/d3-delaunay.js rename to packages/x-charts-vendor/es/d3-delaunay.mjs diff --git a/packages/x-charts-vendor/es/d3-format.js b/packages/x-charts-vendor/es/d3-format.mjs similarity index 100% rename from packages/x-charts-vendor/es/d3-format.js rename to packages/x-charts-vendor/es/d3-format.mjs diff --git a/packages/x-charts-vendor/es/d3-interpolate.js b/packages/x-charts-vendor/es/d3-interpolate.mjs similarity index 100% rename from packages/x-charts-vendor/es/d3-interpolate.js rename to packages/x-charts-vendor/es/d3-interpolate.mjs diff --git a/packages/x-charts-vendor/es/d3-path.js b/packages/x-charts-vendor/es/d3-path.mjs similarity index 100% rename from packages/x-charts-vendor/es/d3-path.js rename to packages/x-charts-vendor/es/d3-path.mjs diff --git a/packages/x-charts-vendor/es/d3-scale.js b/packages/x-charts-vendor/es/d3-scale.mjs similarity index 100% rename from packages/x-charts-vendor/es/d3-scale.js rename to packages/x-charts-vendor/es/d3-scale.mjs diff --git a/packages/x-charts-vendor/es/d3-shape.js b/packages/x-charts-vendor/es/d3-shape.mjs similarity index 100% rename from packages/x-charts-vendor/es/d3-shape.js rename to packages/x-charts-vendor/es/d3-shape.mjs diff --git a/packages/x-charts-vendor/es/d3-time-format.js b/packages/x-charts-vendor/es/d3-time-format.mjs similarity index 100% rename from packages/x-charts-vendor/es/d3-time-format.js rename to packages/x-charts-vendor/es/d3-time-format.mjs diff --git a/packages/x-charts-vendor/es/d3-time.js b/packages/x-charts-vendor/es/d3-time.mjs similarity index 100% rename from packages/x-charts-vendor/es/d3-time.js rename to packages/x-charts-vendor/es/d3-time.mjs diff --git a/packages/x-charts-vendor/es/delaunator.js b/packages/x-charts-vendor/es/delaunator.mjs similarity index 100% rename from packages/x-charts-vendor/es/delaunator.js rename to packages/x-charts-vendor/es/delaunator.mjs diff --git a/packages/x-charts-vendor/es/internmap.js b/packages/x-charts-vendor/es/internmap.mjs similarity index 100% rename from packages/x-charts-vendor/es/internmap.js rename to packages/x-charts-vendor/es/internmap.mjs diff --git a/packages/x-charts-vendor/es/robust-predicates.js b/packages/x-charts-vendor/es/robust-predicates.mjs similarity index 100% rename from packages/x-charts-vendor/es/robust-predicates.js rename to packages/x-charts-vendor/es/robust-predicates.mjs diff --git a/packages/x-charts-vendor/lib-vendor/d3-array/src/disjoint.js b/packages/x-charts-vendor/lib-vendor/d3-array/src/disjoint.js index 7cac5ed41b852..cd03b0951bce5 100644 --- a/packages/x-charts-vendor/lib-vendor/d3-array/src/disjoint.js +++ b/packages/x-charts-vendor/lib-vendor/d3-array/src/disjoint.js @@ -11,10 +11,10 @@ function disjoint(values, other) { for (const v of values) { if (set.has(v)) return false; let value, done; - while (({ + while ({ value, done - } = iterator.next())) { + } = iterator.next()) { if (done) break; if (Object.is(v, value)) return false; set.add(value); diff --git a/packages/x-charts-vendor/lib-vendor/d3-array/src/reduce.js b/packages/x-charts-vendor/lib-vendor/d3-array/src/reduce.js index caddffcdcccce..6ab350666079d 100644 --- a/packages/x-charts-vendor/lib-vendor/d3-array/src/reduce.js +++ b/packages/x-charts-vendor/lib-vendor/d3-array/src/reduce.js @@ -18,10 +18,10 @@ function reduce(values, reducer, value) { if (done) return; ++index; } - while (({ + while ({ done, value: next - } = iterator.next()), !done) { + } = iterator.next(), !done) { value = reducer(value, next, ++index, values); } return value; diff --git a/packages/x-charts-vendor/lib-vendor/d3-array/src/superset.js b/packages/x-charts-vendor/lib-vendor/d3-array/src/superset.js index 04de9d7af7d00..99bb8182ab298 100644 --- a/packages/x-charts-vendor/lib-vendor/d3-array/src/superset.js +++ b/packages/x-charts-vendor/lib-vendor/d3-array/src/superset.js @@ -11,10 +11,10 @@ function superset(values, other) { const io = intern(o); if (set.has(io)) continue; let value, done; - while (({ + while ({ value, done - } = iterator.next())) { + } = iterator.next()) { if (done) return false; const ivalue = intern(value); set.add(ivalue); diff --git a/packages/x-charts-vendor/package.json b/packages/x-charts-vendor/package.json index 4f58a7096bd3a..59fef856b36de 100644 --- a/packages/x-charts-vendor/package.json +++ b/packages/x-charts-vendor/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-charts-vendor", - "version": "7.12.0", + "version": "7.15.0", "description": "Vendored dependencies for MUI X Charts", "author": "MUI Team", "main": "./index.js", @@ -19,12 +19,12 @@ "./package.json": "./package.json", "./*": { "types": "./*.d.ts", - "import": "./es/*.js", + "import": "./es/*.mjs", "default": "./lib/*.js" } }, "dependencies": { - "@babel/runtime": "^7.25.0", + "@babel/runtime": "^7.25.4", "@types/d3-color": "^3.1.3", "@types/d3-delaunay": "^6.0.4", "@types/d3-interpolate": "^3.0.4", @@ -41,7 +41,7 @@ "robust-predicates": "^3.0.2" }, "devDependencies": { - "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.25.4", "@types/d3-array": "^3.2.1", "@types/d3-format": "^3.0.4", "@types/d3-path": "^3.1.0", @@ -50,9 +50,9 @@ "d3-format": "^3.1.0", "d3-path": "^3.1.0", "d3-time-format": "^4.1.0", - "execa": "^9.3.0", + "execa": "^9.3.1", "internmap": "^2.0.3", - "rimraf": "^5.0.8" + "rimraf": "^5.0.10" }, "publishConfig": { "access": "public" diff --git a/packages/x-charts-vendor/scripts/build.js b/packages/x-charts-vendor/scripts/build.js index 169e75aee51c7..ff634f2fbac3a 100644 --- a/packages/x-charts-vendor/scripts/build.js +++ b/packages/x-charts-vendor/scripts/build.js @@ -118,7 +118,7 @@ const main = async () => { // Create library indexes and copy licenses to `lib-vendor. await Promise.all([ - fs.writeFile(path.join(EsmBasePath, `${pkgName}.js`), getEsmIndex(pkg)), + fs.writeFile(path.join(EsmBasePath, `${pkgName}.mjs`), getEsmIndex(pkg)), fs.writeFile(path.join(CjsBasePath, `${pkgName}.js`), getCjsIndex(pkg)), fs .copyFile(path.join(pkgBase, 'LICENSE'), path.join(libVendorPath, 'LICENSE')) diff --git a/packages/x-charts/package.json b/packages/x-charts/package.json index c6e011cbc4f23..751bb5f1ec073 100644 --- a/packages/x-charts/package.json +++ b/packages/x-charts/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-charts", - "version": "7.12.0", + "version": "7.15.0", "description": "The community edition of the Charts components (MUI X).", "author": "MUI Team", "main": "src/index.js", @@ -39,9 +39,8 @@ "directory": "packages/x-charts" }, "dependencies": { - "@babel/runtime": "^7.25.0", - "@mui/system": "^5.16.5", - "@mui/utils": "^5.16.5", + "@babel/runtime": "^7.25.4", + "@mui/utils": "^5.16.6", "@mui/x-charts-vendor": "workspace:*", "@react-spring/rafz": "^9.7.4", "@react-spring/web": "^9.7.4", @@ -51,7 +50,8 @@ "peerDependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" }, @@ -64,12 +64,14 @@ } }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.5", + "@mui/internal-test-utils": "^1.0.11", + "@mui/material": "^5.16.7", + "@mui/system": "^5.16.7", "@react-spring/core": "^9.7.4", "@react-spring/shared": "^9.7.4", "@types/prop-types": "^15.7.12", "csstype": "^3.1.3", - "rimraf": "^5.0.9" + "rimraf": "^5.0.10" }, "engines": { "node": ">=14.0.0" diff --git a/packages/x-charts/src/BarChart/BarChart.test.tsx b/packages/x-charts/src/BarChart/BarChart.test.tsx index 7f9dffa980691..f93e1352ca788 100644 --- a/packages/x-charts/src/BarChart/BarChart.test.tsx +++ b/packages/x-charts/src/BarChart/BarChart.test.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; -import { createRenderer, describeConformance } from '@mui/internal-test-utils'; +import { createRenderer } from '@mui/internal-test-utils/createRenderer'; +import { describeConformance } from 'test/utils/describeConformance'; import { BarChart } from '@mui/x-charts/BarChart'; describe('', () => { @@ -20,7 +21,6 @@ describe('', () => { 'slotPropsProp', 'slotPropsCallback', 'slotsProp', - 'themeDefaultProps', 'themeStyleOverrides', 'themeVariants', 'themeCustomPalette', diff --git a/packages/x-charts/src/BarChart/BarChart.tsx b/packages/x-charts/src/BarChart/BarChart.tsx index b106c22517159..ffb89e8494d40 100644 --- a/packages/x-charts/src/BarChart/BarChart.tsx +++ b/packages/x-charts/src/BarChart/BarChart.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import { useThemeProps } from '@mui/material/styles'; import { BarPlot, BarPlotProps, BarPlotSlotProps, BarPlotSlots } from './BarPlot'; import { ResponsiveChartContainer, @@ -109,7 +110,8 @@ export interface BarChartProps * * - [BarChart API](https://mui.com/x/api/charts/bar-chart/) */ -const BarChart = React.forwardRef(function BarChart(props: BarChartProps, ref) { +const BarChart = React.forwardRef(function BarChart(inProps: BarChartProps, ref) { + const props = useThemeProps({ props: inProps, name: 'MuiBarChart' }); const { chartContainerProps, barPlotProps, @@ -128,7 +130,7 @@ const BarChart = React.forwardRef(function BarChart(props: BarChartProps, ref) { return ( {props.onAxisClick && } - {props.grid && } + @@ -386,6 +388,11 @@ BarChart.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -456,6 +463,11 @@ BarChart.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), diff --git a/packages/x-charts/src/BarChart/BarLabel/BarLabel.tsx b/packages/x-charts/src/BarChart/BarLabel/BarLabel.tsx index 7b18ddf9568a0..d3c9af981dee8 100644 --- a/packages/x-charts/src/BarChart/BarLabel/BarLabel.tsx +++ b/packages/x-charts/src/BarChart/BarLabel/BarLabel.tsx @@ -29,10 +29,10 @@ export const BarLabelComponent = styled(animated.text, { export type BarLabelProps = Omit, 'ref' | 'id'> & BarLabelOwnerState; -function BarLabel(props: BarLabelProps) { - const themeProps = useThemeProps({ props, name: 'MuiBarLabel' }); +function BarLabel(inProps: BarLabelProps) { + const props = useThemeProps({ props: inProps, name: 'MuiBarLabel' }); - const { seriesId, dataIndex, color, isFaded, isHighlighted, classes, ...otherProps } = themeProps; + const { seriesId, dataIndex, color, isFaded, isHighlighted, classes, ...otherProps } = props; return ; } diff --git a/packages/x-charts/src/BarChart/BarPlot.tsx b/packages/x-charts/src/BarChart/BarPlot.tsx index 875644e4bcaf1..10fdd7dcc1a67 100644 --- a/packages/x-charts/src/BarChart/BarPlot.tsx +++ b/packages/x-charts/src/BarChart/BarPlot.tsx @@ -224,6 +224,9 @@ const enterStyle = ({ x, width, y, height }: AnimationData) => ({ function BarPlot(props: BarPlotProps) { const { completedData, masksData } = useAggregatedData(); const { skipAnimation, onItemClick, borderRadius, barLabel, ...other } = props; + + const withoutBorderRadius = !borderRadius || borderRadius <= 0; + const transition = useTransition(completedData, { keys: (bar) => `${bar.seriesId}-${bar.dataIndex}`, from: leaveStyle, @@ -233,7 +236,7 @@ function BarPlot(props: BarPlotProps) { immediate: skipAnimation, }); - const maskTransition = useTransition(masksData, { + const maskTransition = useTransition(withoutBorderRadius ? [] : masksData, { keys: (v) => v.id, from: leaveStyle, leave: leaveStyle, @@ -244,18 +247,19 @@ function BarPlot(props: BarPlotProps) { return ( - {maskTransition((style, { id, hasPositive, hasNegative, layout }) => { - return ( - - ); - })} + {!withoutBorderRadius && + maskTransition((style, { id, hasPositive, hasNegative, layout }) => { + return ( + + ); + })} {transition((style, { seriesId, dataIndex, color, maskId }) => { const barElement = ( ); - if (!borderRadius || borderRadius <= 0) { + if (withoutBorderRadius) { return barElement; } diff --git a/packages/x-charts/src/BarChart/checkClickEvent.test.tsx b/packages/x-charts/src/BarChart/checkClickEvent.test.tsx new file mode 100644 index 0000000000000..7804217e7402a --- /dev/null +++ b/packages/x-charts/src/BarChart/checkClickEvent.test.tsx @@ -0,0 +1,155 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { createRenderer, fireEvent } from '@mui/internal-test-utils'; +import { spy } from 'sinon'; +import { BarChart } from '@mui/x-charts/BarChart'; +import { firePointerEvent } from '../tests/firePointerEvent'; + +const config = { + dataset: [ + { x: 'A', v1: 4, v2: 2 }, + { x: 'B', v1: 1, v2: 1 }, + ], + margin: { top: 0, left: 0, bottom: 0, right: 0 }, + width: 400, + height: 400, +}; + +// Plot as follow to simplify click position +// +// | X +// | X +// | X X +// | X X X X +// ---A---B- + +const isJSDOM = /jsdom/.test(window.navigator.userAgent); + +describe('BarChart - click event', () => { + const { render } = createRenderer(); + + describe('onAxisClick', () => { + it('should provide the right context as second argument', function test() { + if (isJSDOM) { + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + this.skip(); + } + const onAxisClick = spy(); + render( +
+ +
, + ); + const svg = document.querySelector('svg')!; + + firePointerEvent(svg, 'pointermove', { + clientX: 198, + clientY: 60, + }); + fireEvent.click(svg); + + expect(onAxisClick.lastCall.args[1]).to.deep.equal({ + dataIndex: 0, + axisValue: 'A', + seriesValues: { s1: 4, s2: 2 }, + }); + + firePointerEvent(svg, 'pointermove', { + clientX: 201, + clientY: 60, + }); + fireEvent.click(svg); + + expect(onAxisClick.lastCall.args[1]).to.deep.equal({ + dataIndex: 1, + axisValue: 'B', + seriesValues: { s1: 1, s2: 1 }, + }); + }); + }); + + describe('onItemClick', () => { + it('should add cursor="pointer" to bar elements', function test() { + render( + {}} + />, + ); + const rectangles = document.querySelectorAll('rect.MuiBarElement-root'); + + expect( + Array.from(rectangles).map((rectangle) => rectangle.getAttribute('cursor')), + ).to.deep.equal(['pointer', 'pointer', 'pointer', 'pointer']); + }); + + it('should provide the right context as second argument', function test() { + if (isJSDOM) { + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + this.skip(); + } + const onItemClick = spy(); + render( +
+ +
, + ); + + const rectangles = document.querySelectorAll('rect.MuiBarElement-root'); + + fireEvent.click(rectangles[0]); + expect(onItemClick.lastCall.args[1]).to.deep.equal({ + type: 'bar', + seriesId: 's1', + dataIndex: 0, + }); + + fireEvent.click(rectangles[1]); + expect(onItemClick.lastCall.args[1]).to.deep.equal({ + type: 'bar', + seriesId: 's1', + dataIndex: 1, + }); + + fireEvent.click(rectangles[2]); + expect(onItemClick.lastCall.args[1]).to.deep.equal({ + type: 'bar', + seriesId: 's2', + dataIndex: 0, + }); + }); + }); +}); diff --git a/packages/x-charts/src/BarChart/extremums.test.ts b/packages/x-charts/src/BarChart/extremums.test.ts index 6222d82357e84..798954aac8dc7 100644 --- a/packages/x-charts/src/BarChart/extremums.test.ts +++ b/packages/x-charts/src/BarChart/extremums.test.ts @@ -55,8 +55,8 @@ describe('BarChart - extremums', () => { it('should correctly get Infinity when empty data', () => { const [x, y] = getExtremumX(buildData([], 'horizontal')); - expect(x).to.equal(null); - expect(y).to.equal(null); + expect(x).to.equal(Infinity); + expect(y).to.equal(-Infinity); }); }); }); diff --git a/packages/x-charts/src/BarChart/extremums.ts b/packages/x-charts/src/BarChart/extremums.ts index d6fb3f2e79d93..1ec2cc001b9fd 100644 --- a/packages/x-charts/src/BarChart/extremums.ts +++ b/packages/x-charts/src/BarChart/extremums.ts @@ -1,42 +1,67 @@ -import { - ExtremumGetter, - ExtremumGetterResult, -} from '../context/PluginProvider/ExtremumGetter.types'; +import { ExtremumGetter } from '../context/PluginProvider/ExtremumGetter.types'; + +const createResult = (data: any, direction: 'x' | 'y') => { + if (direction === 'x') { + return { x: data, y: null }; + } + return { x: null, y: data }; +}; const getBaseExtremum: ExtremumGetter<'bar'> = (params) => { - const { axis } = params; + const { axis, getFilters, isDefaultAxis } = params; - const minX = Math.min(...(axis.data ?? [])); - const maxX = Math.max(...(axis.data ?? [])); + const filter = getFilters?.({ + currentAxisId: axis.id, + isDefaultAxis, + }); + + const data = filter ? axis.data?.filter((_, i) => filter({ x: null, y: null }, i)) : axis.data; + const minX = Math.min(...(data ?? [])); + const maxX = Math.max(...(data ?? [])); return [minX, maxX]; }; -const getValueExtremum: ExtremumGetter<'bar'> = (params) => { - const { series, axis, isDefaultAxis } = params; - - return Object.keys(series) - .filter((seriesId) => { - const yAxisId = series[seriesId].yAxisId ?? series[seriesId].yAxisKey; - return yAxisId === axis.id || (isDefaultAxis && yAxisId === undefined); - }) - .reduce( - (acc: ExtremumGetterResult, seriesId) => { - const [seriesMin, seriesMax] = series[seriesId].stackedData?.reduce( - (seriesAcc, values) => [ - Math.min(...values, ...(seriesAcc[0] === null ? [] : [seriesAcc[0]])), - Math.max(...values, ...(seriesAcc[1] === null ? [] : [seriesAcc[1]])), - ], - series[seriesId].stackedData[0], - ) ?? [null, null]; - - return [ - acc[0] === null ? seriesMin : Math.min(seriesMin, acc[0]), - acc[1] === null ? seriesMax : Math.max(seriesMax, acc[1]), - ]; - }, - [null, null], - ); -}; +const getValueExtremum = + (direction: 'x' | 'y'): ExtremumGetter<'bar'> => + (params) => { + const { series, axis, getFilters, isDefaultAxis } = params; + + return Object.keys(series) + .filter((seriesId) => { + const yAxisId = series[seriesId].yAxisId ?? series[seriesId].yAxisKey; + return yAxisId === axis.id || (isDefaultAxis && yAxisId === undefined); + }) + .reduce( + (acc, seriesId) => { + const { stackedData } = series[seriesId]; + + const filter = getFilters?.({ + currentAxisId: axis.id, + isDefaultAxis, + seriesXAxisId: series[seriesId].xAxisId ?? series[seriesId].xAxisKey, + seriesYAxisId: series[seriesId].yAxisId ?? series[seriesId].yAxisKey, + }); + + const [seriesMin, seriesMax] = stackedData?.reduce( + (seriesAcc, values, index) => { + if ( + filter && + (!filter(createResult(values[0], direction), index) || + !filter(createResult(values[1], direction), index)) + ) { + return seriesAcc; + } + + return [Math.min(...values, seriesAcc[0]), Math.max(...values, seriesAcc[1])]; + }, + [Infinity, -Infinity], + ) ?? [Infinity, -Infinity]; + + return [Math.min(seriesMin, acc[0]), Math.max(seriesMax, acc[1])]; + }, + [Infinity, -Infinity], + ); + }; export const getExtremumX: ExtremumGetter<'bar'> = (params) => { // Notice that bar should be all horizontal or all vertical. @@ -45,7 +70,7 @@ export const getExtremumX: ExtremumGetter<'bar'> = (params) => { (seriesId) => params.series[seriesId].layout === 'horizontal', ); if (isHorizontal) { - return getValueExtremum(params); + return getValueExtremum('x')(params); } return getBaseExtremum(params); }; @@ -57,5 +82,5 @@ export const getExtremumY: ExtremumGetter<'bar'> = (params) => { if (isHorizontal) { return getBaseExtremum(params); } - return getValueExtremum(params); + return getValueExtremum('y')(params); }; diff --git a/packages/x-charts/src/ChartContainer/ChartContainer.tsx b/packages/x-charts/src/ChartContainer/ChartContainer.tsx index 786bf90b345d5..2de25d7461c76 100644 --- a/packages/x-charts/src/ChartContainer/ChartContainer.tsx +++ b/packages/x-charts/src/ChartContainer/ChartContainer.tsx @@ -4,10 +4,7 @@ import { DrawingProvider, DrawingProviderProps } from '../context/DrawingProvide import { SeriesProvider, SeriesProviderProps } from '../context/SeriesProvider'; import { InteractionProvider } from '../context/InteractionProvider'; import { ChartsSurface, ChartsSurfaceProps } from '../ChartsSurface'; -import { - CartesianContextProvider, - CartesianContextProviderProps, -} from '../context/CartesianProvider'; +import { CartesianProvider, CartesianProviderProps } from '../context/CartesianProvider'; import { ChartsAxesGradients } from '../internals/components/ChartsAxesGradients'; import { HighlightedProvider, @@ -24,7 +21,7 @@ export type ChartContainerProps = Omit< ChartsSurfaceProps & Omit & Omit & - Pick & + Pick & ZAxisContextProviderProps & HighlightedProviderProps & PluginProviderProps, @@ -50,7 +47,7 @@ const ChartContainer = React.forwardRef(function ChartContainer(props: ChartCont children, drawingProviderProps, seriesProviderProps, - cartesianContextProps, + cartesianProviderProps, zAxisContextProps, highlightedProviderProps, chartsSurfaceProps, @@ -61,7 +58,7 @@ const ChartContainer = React.forwardRef(function ChartContainer(props: ChartCont - + @@ -72,7 +69,7 @@ const ChartContainer = React.forwardRef(function ChartContainer(props: ChartCont - + @@ -211,6 +208,11 @@ ChartContainer.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -281,6 +283,11 @@ ChartContainer.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), diff --git a/packages/x-charts/src/ChartContainer/useChartContainerProps.ts b/packages/x-charts/src/ChartContainer/useChartContainerProps.ts index 6b44c62859f07..40c6486def244 100644 --- a/packages/x-charts/src/ChartContainer/useChartContainerProps.ts +++ b/packages/x-charts/src/ChartContainer/useChartContainerProps.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import useForkRef from '@mui/utils/useForkRef'; import type { DrawingProviderProps } from '../context/DrawingProvider'; -import type { CartesianContextProviderProps } from '../context/CartesianProvider'; +import type { CartesianProviderProps } from '../context/CartesianProvider'; import type { SeriesProviderProps } from '../context/SeriesProvider'; import type { ZAxisContextProviderProps } from '../context/ZAxisContextProvider'; import type { ChartContainerProps } from './ChartContainer'; @@ -40,7 +40,7 @@ export const useChartContainerProps = ( useReducedMotion(); // a11y reduce motion (see: https://react-spring.dev/docs/utilities/use-reduced-motion) - const [defaultizedXAxis, defaultizedYAxis] = useDefaultizeAxis(xAxis, yAxis); + const [defaultizedXAxis, defaultizedYAxis] = useDefaultizeAxis(xAxis, yAxis, dataset); const drawingProviderProps: Omit = { width, @@ -59,7 +59,7 @@ export const useChartContainerProps = ( dataset, }; - const cartesianContextProps: Omit = { + const cartesianProviderProps: Omit = { xAxis: defaultizedXAxis, yAxis: defaultizedYAxis, dataset, @@ -90,7 +90,7 @@ export const useChartContainerProps = ( children, drawingProviderProps, seriesProviderProps, - cartesianContextProps, + cartesianProviderProps, zAxisContextProps, highlightedProviderProps, chartsSurfaceProps, diff --git a/packages/x-charts/src/ChartContainer/useDefaultizeAxis.ts b/packages/x-charts/src/ChartContainer/useDefaultizeAxis.ts index 25d270384f5eb..e84a9dadf00ab 100644 --- a/packages/x-charts/src/ChartContainer/useDefaultizeAxis.ts +++ b/packages/x-charts/src/ChartContainer/useDefaultizeAxis.ts @@ -3,9 +3,11 @@ import { DEFAULT_X_AXIS_KEY, DEFAULT_Y_AXIS_KEY } from '../constants'; import { MakeOptional } from '../models/helpers'; import { AxisConfig, ScaleName } from '../models'; import { ChartsAxisProps } from '../models/axis'; +import { DatasetType } from '../models/seriesType/config'; const defaultizeAxis = ( inAxis: MakeOptional, 'id'>[] | undefined, + dataset: DatasetType | undefined, axisName: 'x' | 'y', ) => { const DEFAULT_AXIS_KEY = axisName === 'x' ? DEFAULT_X_AXIS_KEY : DEFAULT_Y_AXIS_KEY; @@ -16,15 +18,28 @@ const defaultizeAxis = ( ...(inAxis === undefined || inAxis.findIndex(({ id }) => id === DEFAULT_AXIS_KEY) === -1 ? [{ id: DEFAULT_AXIS_KEY, scaleType: 'linear' as const }] : []), - ]; + ].map((axisConfig) => { + const dataKey = axisConfig.dataKey; + if (dataKey === undefined || axisConfig.data !== undefined) { + return axisConfig; + } + if (dataset === undefined) { + throw Error(`MUI X: ${axisName}-axis uses \`dataKey\` but no \`dataset\` is provided.`); + } + return { + ...axisConfig, + data: dataset.map((d) => d[dataKey]), + }; + }); }; export const useDefaultizeAxis = ( inXAxis: MakeOptional, 'id'>[] | undefined, inYAxis: MakeOptional, 'id'>[] | undefined, + dataset: DatasetType | undefined, ) => { - const xAxis = React.useMemo(() => defaultizeAxis(inXAxis, 'x'), [inXAxis]); - const yAxis = React.useMemo(() => defaultizeAxis(inYAxis, 'y'), [inYAxis]); + const xAxis = React.useMemo(() => defaultizeAxis(inXAxis, dataset, 'x'), [inXAxis, dataset]); + const yAxis = React.useMemo(() => defaultizeAxis(inYAxis, dataset, 'y'), [inYAxis, dataset]); return [xAxis, yAxis]; }; diff --git a/packages/x-charts/src/ChartsAxisHighlight/ChartsAxisHighlight.tsx b/packages/x-charts/src/ChartsAxisHighlight/ChartsAxisHighlight.tsx index 4560673922058..fb9b4d2f74de0 100644 --- a/packages/x-charts/src/ChartsAxisHighlight/ChartsAxisHighlight.tsx +++ b/packages/x-charts/src/ChartsAxisHighlight/ChartsAxisHighlight.tsx @@ -37,16 +37,34 @@ export const ChartsAxisHighlightPath = styled('path', { name: 'MuiChartsAxisHighlight', slot: 'Root', overridesResolver: (_, styles) => styles.root, -})<{ ownerState: { axisHighlight: AxisHighlight } }>(({ ownerState, theme }) => ({ +})<{ ownerState: { axisHighlight: AxisHighlight } }>(({ theme }) => ({ pointerEvents: 'none', - ...(ownerState.axisHighlight === 'band' && { - fill: theme.palette.mode === 'light' ? 'gray' : 'white', - fillOpacity: 0.1, - }), - ...(ownerState.axisHighlight === 'line' && { - strokeDasharray: '5 2', - stroke: theme.palette.mode === 'light' ? '#000000' : '#ffffff', - }), + variants: [ + { + props: { + axisHighlight: 'band', + }, + style: { + fill: 'white', + fillOpacity: 0.1, + ...theme.applyStyles('light', { + fill: 'gray', + }), + }, + }, + { + props: { + axisHighlight: 'line', + }, + style: { + strokeDasharray: '5 2', + stroke: '#ffffff', + ...theme.applyStyles('light', { + stroke: '#000000', + }), + }, + }, + ], })); type AxisHighlight = 'none' | 'line' | 'band'; diff --git a/packages/x-charts/src/ChartsGrid/ChartsGrid.tsx b/packages/x-charts/src/ChartsGrid/ChartsGrid.tsx index c150244b2d39b..0480ea912c023 100644 --- a/packages/x-charts/src/ChartsGrid/ChartsGrid.tsx +++ b/packages/x-charts/src/ChartsGrid/ChartsGrid.tsx @@ -1,35 +1,13 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import composeClasses from '@mui/utils/composeClasses'; -import { styled, useThemeProps } from '@mui/material/styles'; - +import { useThemeProps } from '@mui/material/styles'; import { useCartesianContext } from '../context/CartesianProvider'; -import { useTicks } from '../hooks/useTicks'; -import { - ChartsGridClasses, - getChartsGridUtilityClass, - chartsGridClasses, -} from './chartsGridClasses'; - -const GridRoot = styled('g', { - name: 'MuiChartsGrid', - slot: 'Root', - overridesResolver: (props, styles) => [ - { [`&.${chartsGridClasses.verticalLine}`]: styles.verticalLine }, - { [`&.${chartsGridClasses.horizontalLine}`]: styles.horizontalLine }, - styles.root, - ], -})({}); - -const GridLine = styled('line', { - name: 'MuiChartsGrid', - slot: 'Line', - overridesResolver: (props, styles) => styles.line, -})(({ theme }) => ({ - stroke: (theme.vars || theme).palette.divider, - shapeRendering: 'crispEdges', - strokeWidth: 1, -})); +import { ChartsGridClasses, getChartsGridUtilityClass } from './chartsGridClasses'; +import { useDrawingArea } from '../hooks/useDrawingArea'; +import { GridRoot } from './styledCommonents'; +import { ChartsGridVertical } from './ChartsVerticalGrid'; +import { ChartsGridHorizontal } from './ChartsHorizontalGrid'; const useUtilityClasses = ({ classes }: ChartsGridProps) => { const slots = { @@ -65,56 +43,27 @@ export interface ChartsGridProps { * * - [ChartsGrid API](https://mui.com/x/api/charts/charts-axis/) */ -function ChartsGrid(props: ChartsGridProps) { - const themeProps = useThemeProps({ props, name: 'MuiChartsGrid' }); +function ChartsGrid(inProps: ChartsGridProps) { + const props = useThemeProps({ props: inProps, name: 'MuiChartsGrid' }); - const { vertical, horizontal, ...other } = themeProps; + const drawingArea = useDrawingArea(); + const { vertical, horizontal, ...other } = props; const { xAxis, xAxisIds, yAxis, yAxisIds } = useCartesianContext(); - const classes = useUtilityClasses(themeProps); - - const horizontalAxisId = yAxisIds[0]; - const verticalAxisId = xAxisIds[0]; + const classes = useUtilityClasses(props); - const { - scale: xScale, - tickNumber: xTickNumber, - tickInterval: xTickInterval, - } = xAxis[verticalAxisId]; - - const { - scale: yScale, - tickNumber: yTickNumber, - tickInterval: yTickInterval, - } = yAxis[horizontalAxisId]; - - const xTicks = useTicks({ scale: xScale, tickNumber: xTickNumber, tickInterval: xTickInterval }); - const yTicks = useTicks({ scale: yScale, tickNumber: yTickNumber, tickInterval: yTickInterval }); + const horizontalAxis = yAxis[yAxisIds[0]]; + const verticalAxis = xAxis[xAxisIds[0]]; return ( - {vertical && - xTicks.map(({ formattedValue, offset }) => ( - - ))} - {horizontal && - yTicks.map(({ formattedValue, offset }) => ( - - ))} + {vertical && ( + + )} + + {horizontal && ( + + )} ); } diff --git a/packages/x-charts/src/ChartsGrid/ChartsHorizontalGrid.tsx b/packages/x-charts/src/ChartsGrid/ChartsHorizontalGrid.tsx new file mode 100644 index 0000000000000..2827e782e4cd4 --- /dev/null +++ b/packages/x-charts/src/ChartsGrid/ChartsHorizontalGrid.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import { DrawingArea } from '../context/DrawingProvider'; +import { useTicks } from '../hooks/useTicks'; +import { AxisDefaultized, ChartsYAxisProps, ScaleName } from '../models/axis'; +import { GridLine } from './styledCommonents'; +import { ChartsGridClasses } from './chartsGridClasses'; + +interface ChartsGridHorizontalProps { + axis: AxisDefaultized; + drawingArea: DrawingArea; + classes: Partial; +} + +/** + * @ignore - internal component. + */ +export function ChartsGridHorizontal(props: ChartsGridHorizontalProps) { + const { axis, drawingArea, classes } = props; + + const { scale, tickNumber, tickInterval } = axis; + + const yTicks = useTicks({ scale, tickNumber, tickInterval }); + + return ( + + {yTicks.map(({ formattedValue, offset }) => ( + + ))} + + ); +} diff --git a/packages/x-charts/src/ChartsGrid/ChartsVerticalGrid.tsx b/packages/x-charts/src/ChartsGrid/ChartsVerticalGrid.tsx new file mode 100644 index 0000000000000..b033d5c1b8ea7 --- /dev/null +++ b/packages/x-charts/src/ChartsGrid/ChartsVerticalGrid.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import { DrawingArea } from '../context/DrawingProvider'; +import { useTicks } from '../hooks/useTicks'; +import { AxisDefaultized, ChartsXAxisProps, ScaleName } from '../models/axis'; +import { GridLine } from './styledCommonents'; +import { ChartsGridClasses } from './chartsGridClasses'; + +interface ChartsGridVerticalProps { + axis: AxisDefaultized; + drawingArea: DrawingArea; + classes: Partial; +} + +/** + * @ignore - internal component. + */ +export function ChartsGridVertical(props: ChartsGridVerticalProps) { + const { axis, drawingArea, classes } = props; + + const { scale, tickNumber, tickInterval } = axis; + + const xTicks = useTicks({ scale, tickNumber, tickInterval }); + + return ( + + {xTicks.map(({ formattedValue, offset }) => ( + + ))} + + ); +} diff --git a/packages/x-charts/src/ChartsGrid/styledCommonents.tsx b/packages/x-charts/src/ChartsGrid/styledCommonents.tsx new file mode 100644 index 0000000000000..02f2ea7a84d22 --- /dev/null +++ b/packages/x-charts/src/ChartsGrid/styledCommonents.tsx @@ -0,0 +1,22 @@ +import { styled } from '@mui/material/styles'; +import { chartsGridClasses } from './chartsGridClasses'; + +export const GridRoot = styled('g', { + name: 'MuiChartsGrid', + slot: 'Root', + overridesResolver: (props, styles) => [ + { [`&.${chartsGridClasses.verticalLine}`]: styles.verticalLine }, + { [`&.${chartsGridClasses.horizontalLine}`]: styles.horizontalLine }, + styles.root, + ], +})({}); + +export const GridLine = styled('line', { + name: 'MuiChartsGrid', + slot: 'Line', + overridesResolver: (props, styles) => styles.line, +})(({ theme }) => ({ + stroke: (theme.vars || theme).palette.divider, + shapeRendering: 'crispEdges', + strokeWidth: 1, +})); diff --git a/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx b/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx index 1fab773c243fe..f2b8d964d7da8 100644 --- a/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx +++ b/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx @@ -59,20 +59,21 @@ const useUtilityClasses = (ownerState: DefaultizedChartsLegendProps & { theme: T return composeClasses(slots, getLegendUtilityClass, classes); }; -const defaultProps = { - position: { horizontal: 'middle', vertical: 'top' }, - direction: 'row', -} as const; - function ChartsLegend(inProps: ChartsLegendProps) { - const props: DefaultizedChartsLegendProps = useThemeProps({ - props: { ...defaultProps, ...inProps }, + const props = useThemeProps({ + props: inProps, name: 'MuiChartsLegend', }); - const { position, direction, hidden, slots, slotProps } = props; + const defaultizedProps: DefaultizedChartsLegendProps = { + direction: 'row', + ...props, + position: { horizontal: 'middle', vertical: 'top', ...props.position }, + }; + const { position, direction, hidden, slots, slotProps } = defaultizedProps; + const theme = useTheme(); - const classes = useUtilityClasses({ ...props, theme }); + const classes = useUtilityClasses({ ...defaultizedProps, theme }); const drawingArea = useDrawingArea(); const series = useSeries(); diff --git a/packages/x-charts/src/ChartsLegend/ContinuousColorLegend.tsx b/packages/x-charts/src/ChartsLegend/ContinuousColorLegend.tsx index d331e317f945c..00880553da648 100644 --- a/packages/x-charts/src/ChartsLegend/ContinuousColorLegend.tsx +++ b/packages/x-charts/src/ChartsLegend/ContinuousColorLegend.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { ScaleSequential } from '@mui/x-charts-vendor/d3-scale'; import { useTheme } from '@mui/material/styles'; +import { useRtl } from '@mui/system/RtlProvider'; import ChartsContinuousGradient from '../internals/components/ChartsAxesGradients/ChartsContinuousGradient'; import { AxisDefaultized, ContinuousScaleName } from '../models/axis'; import { useChartId, useDrawingArea } from '../hooks'; @@ -205,6 +206,7 @@ const defaultLabelFormatter: LabelFormatter = ({ formattedValue }) => formattedV function ContinuousColorLegend(props: ContinuousColorLegendProps) { const theme = useTheme(); + const isRtl = useRtl(); const { id: idProp, minLabel = defaultLabelFormatter, @@ -224,8 +226,6 @@ function ContinuousColorLegend(props: ContinuousColorLegendProps) { const chartId = useChartId(); const id = idProp ?? `gradient-legend-${chartId}`; - const isRTL = theme.direction === 'rtl'; - const axisItem = useAxis({ axisDirection, axisId }); const { width, height, left, right, top, bottom } = useDrawingArea(); @@ -277,7 +277,7 @@ function ContinuousColorLegend(props: ContinuousColorLegendProps) { // Place bar and texts const barBox = - direction === 'column' || (isRTL && direction === 'row') + direction === 'column' || (isRtl && direction === 'row') ? { width: thickness, height: size } : { width: size, height: thickness }; diff --git a/packages/x-charts/src/ChartsLegend/LegendPerItem.tsx b/packages/x-charts/src/ChartsLegend/LegendPerItem.tsx index dd61aeb4e9f89..da5ef97569b50 100644 --- a/packages/x-charts/src/ChartsLegend/LegendPerItem.tsx +++ b/packages/x-charts/src/ChartsLegend/LegendPerItem.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import NoSsr from '@mui/material/NoSsr'; import { useTheme, styled } from '@mui/material/styles'; +import { useRtl } from '@mui/system/RtlProvider'; import { DrawingArea } from '../context/DrawingProvider'; import { DefaultizedProps } from '../models/helpers'; import { ChartsText, ChartsTextStyle } from '../ChartsText'; @@ -111,7 +112,7 @@ export function LegendPerItem(props: LegendPerItemProps) { labelStyle: inLabelStyle, } = props; const theme = useTheme(); - const isRTL = theme.direction === 'rtl'; + const isRtl = useRtl(); const drawingArea = useDrawingArea(); const labelStyle = React.useMemo( @@ -200,11 +201,11 @@ export function LegendPerItem(props: LegendPerItemProps) { diff --git a/packages/x-charts/src/ChartsSurface.tsx b/packages/x-charts/src/ChartsSurface/ChartsSurface.tsx similarity index 92% rename from packages/x-charts/src/ChartsSurface.tsx rename to packages/x-charts/src/ChartsSurface/ChartsSurface.tsx index 47147d81f2a0a..769a65fb58357 100644 --- a/packages/x-charts/src/ChartsSurface.tsx +++ b/packages/x-charts/src/ChartsSurface/ChartsSurface.tsx @@ -1,7 +1,7 @@ -import { styled, SxProps, Theme } from '@mui/material/styles'; +import { styled, SxProps, Theme, useThemeProps } from '@mui/material/styles'; import PropTypes from 'prop-types'; import * as React from 'react'; -import { useAxisEvents } from './hooks/useAxisEvents'; +import { useAxisEvents } from '../hooks/useAxisEvents'; type ViewBox = { x?: number; @@ -42,9 +42,10 @@ const ChartChartsSurfaceStyles = styled('svg', { })); const ChartsSurface = React.forwardRef(function ChartsSurface( - props: ChartsSurfaceProps, + inProps: ChartsSurfaceProps, ref, ) { + const props = useThemeProps({ props: inProps, name: 'MuiChartsSurface' }); const { children, width, diff --git a/packages/x-charts/src/ChartsSurface/index.ts b/packages/x-charts/src/ChartsSurface/index.ts new file mode 100644 index 0000000000000..488a89f2528fb --- /dev/null +++ b/packages/x-charts/src/ChartsSurface/index.ts @@ -0,0 +1 @@ +export * from './ChartsSurface'; diff --git a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx index 874d00f3ca351..b9daca3379cba 100644 --- a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx +++ b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx @@ -56,7 +56,7 @@ function ChartsAxisTooltipContent(props: { }) { const { content, contentProps, axisData, sx, classes } = props; - const isXaxis = (axisData.x && axisData.x.index) !== undefined; + const isXaxis = axisData.x && axisData.x.index !== -1; const dataIndex = isXaxis ? axisData.x && axisData.x.index : axisData.y && axisData.y.index; const axisValue = isXaxis ? axisData.x && axisData.x.value : axisData.y && axisData.y.value; diff --git a/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx b/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx index 1391416cf3d0c..16265d1e52f49 100644 --- a/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx +++ b/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx @@ -58,7 +58,7 @@ export interface ChartsTooltipProps { * - 'item': Shows data about the item below the mouse. * - 'axis': Shows values associated with the hovered x value * - 'none': Does not display tooltip - * @default 'item' + * @default 'axis' */ trigger?: TriggerOptions; /** @@ -94,6 +94,7 @@ const useUtilityClasses = (ownerState: { const slots = { root: ['root'], + paper: ['paper'], table: ['table'], row: ['row'], cell: ['cell'], @@ -124,12 +125,12 @@ const ChartsTooltipRoot = styled(Popper, { * * - [ChartsTooltip API](https://mui.com/x/api/charts/charts-tool-tip/) */ -function ChartsTooltip(props: ChartsTooltipProps) { - const themeProps = useThemeProps({ - props, +function ChartsTooltip(inProps: ChartsTooltipProps) { + const props = useThemeProps({ + props: inProps, name: 'MuiChartsTooltip', }); - const { trigger = 'axis', itemContent, axisContent, slots, slotProps } = themeProps; + const { trigger = 'axis', itemContent, axisContent, slots, slotProps } = props; const mousePosition = useMouseTracker(); @@ -140,7 +141,7 @@ function ChartsTooltip(props: ChartsTooltipProps) const tooltipHasData = getTooltipHasData(trigger, displayedData); const popperOpen = mousePosition !== null && tooltipHasData; - const classes = useUtilityClasses({ classes: themeProps.classes }); + const classes = useUtilityClasses({ classes: props.classes }); const PopperComponent = slots?.popper ?? ChartsTooltipRoot; const popperProps = useSlotProps({ @@ -170,7 +171,7 @@ function ChartsTooltip(props: ChartsTooltipProps) return ( {popperOpen && ( - + {trigger === 'item' ? ( } @@ -228,7 +229,7 @@ ChartsTooltip.propTypes = { * - 'item': Shows data about the item below the mouse. * - 'axis': Shows values associated with the hovered x value * - 'none': Does not display tooltip - * @default 'item' + * @default 'axis' */ trigger: PropTypes.oneOf(['axis', 'item', 'none']), } as any; diff --git a/packages/x-charts/src/ChartsTooltip/ChartsTooltipTable.ts b/packages/x-charts/src/ChartsTooltip/ChartsTooltipTable.ts index a83a70ae9e5be..a50e1c462c51c 100644 --- a/packages/x-charts/src/ChartsTooltip/ChartsTooltipTable.ts +++ b/packages/x-charts/src/ChartsTooltip/ChartsTooltipTable.ts @@ -8,6 +8,7 @@ import { chartsTooltipClasses } from './chartsTooltipClasses'; export const ChartsTooltipPaper = styled('div', { name: 'MuiChartsTooltip', slot: 'Container', + overridesResolver: (props, styles) => styles.paper, })(({ theme }) => ({ boxShadow: theme.shadows[1], backgroundColor: (theme.vars || theme).palette.background.paper, @@ -22,6 +23,7 @@ export const ChartsTooltipPaper = styled('div', { export const ChartsTooltipTable = styled('table', { name: 'MuiChartsTooltip', slot: 'Table', + overridesResolver: (props, styles) => styles.table, })(({ theme }) => ({ borderSpacing: 0, '& thead td': { @@ -35,6 +37,7 @@ export const ChartsTooltipTable = styled('table', { export const ChartsTooltipRow = styled('tr', { name: 'MuiChartsTooltip', slot: 'Row', + overridesResolver: (props, styles) => styles.row, })(({ theme }) => ({ 'tr:first-of-type& td': { paddingTop: theme.spacing(1), @@ -50,6 +53,7 @@ export const ChartsTooltipRow = styled('tr', { export const ChartsTooltipCell = styled('td', { name: 'MuiChartsTooltip', slot: 'Cell', + overridesResolver: (props, styles) => styles.cell, })(({ theme }) => ({ verticalAlign: 'middle', color: (theme.vars || theme).palette.text.secondary, @@ -74,6 +78,7 @@ export const ChartsTooltipCell = styled('td', { export const ChartsTooltipMark = styled('div', { name: 'MuiChartsTooltip', slot: 'Mark', + overridesResolver: (props, styles) => styles.mark, shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'color', })<{ color: string }>(({ theme, color }) => ({ width: theme.spacing(1), diff --git a/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx index 5f76ce4edd6c6..c0382f27f2a49 100644 --- a/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx +++ b/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx @@ -27,7 +27,7 @@ function DefaultChartsAxisTooltipContent(props: ChartsAxisContentProps) { axis.scaleType === 'utc' ? utcFormatter(v) : v.toLocaleString()); return ( - + {axisValue != null && !axis.hideTooltip && ( diff --git a/packages/x-charts/src/ChartsTooltip/DefaultChartsItemTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/DefaultChartsItemTooltipContent.tsx index 29fb6f74338b1..e4dec21ff9afc 100644 --- a/packages/x-charts/src/ChartsTooltip/DefaultChartsItemTooltipContent.tsx +++ b/packages/x-charts/src/ChartsTooltip/DefaultChartsItemTooltipContent.tsx @@ -43,7 +43,7 @@ function DefaultChartsItemTooltipContent['valueFormatter'] )?.(value, { dataIndex: itemData.dataIndex }); return ( - + diff --git a/packages/x-charts/src/ChartsTooltip/chartsTooltipClasses.ts b/packages/x-charts/src/ChartsTooltip/chartsTooltipClasses.ts index 0771fbc2dd2c6..b4ada8e0254b8 100644 --- a/packages/x-charts/src/ChartsTooltip/chartsTooltipClasses.ts +++ b/packages/x-charts/src/ChartsTooltip/chartsTooltipClasses.ts @@ -6,6 +6,8 @@ import { export interface ChartsTooltipClasses { /** Styles applied to the root element. */ root: string; + /** Styles applied to the paper element. */ + paper: string; /** Styles applied to the table element. */ table: string; /** Styles applied to the row element. */ @@ -33,5 +35,5 @@ export function getChartsTooltipUtilityClass(slot: string) { } export const chartsTooltipClasses: ChartsTooltipClasses = generateUtilityClasses( 'MuiChartsTooltip', - ['root', 'table', 'row', 'cell', 'mark', 'markCell', 'labelCell', 'valueCell'], + ['root', 'paper', 'table', 'row', 'cell', 'mark', 'markCell', 'labelCell', 'valueCell'], ); diff --git a/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx b/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx new file mode 100644 index 0000000000000..6b4634241e3d5 --- /dev/null +++ b/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx @@ -0,0 +1,251 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { createRenderer, fireEvent } from '@mui/internal-test-utils'; +import { BarChart } from '@mui/x-charts/BarChart'; +import { firePointerEvent } from '../tests/firePointerEvent'; + +const config = { + dataset: [ + { x: 'A', v1: 4, v2: 2 }, + { x: 'B', v1: 1, v2: 1 }, + ], + margin: { top: 0, left: 0, bottom: 0, right: 0 }, + width: 400, + height: 400, +}; + +// Plot as follow to simplify click position +// +// | X +// | X +// | X X +// | X X X X +// ---A---B- + +const isJSDOM = /jsdom/.test(window.navigator.userAgent); + +describe('ChartsTooltip', () => { + const { render } = createRenderer(); + + describe('axis trigger', () => { + it('should show right values with vertical layout', function test() { + if (isJSDOM) { + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + this.skip(); + } + + render( +
+ +
, + ); + const svg = document.querySelector('svg')!; + + firePointerEvent(svg, 'pointermove', { + clientX: 198, + clientY: 60, + }); + + let cells = document.querySelectorAll('.MuiChartsTooltip-root td'); + expect([...cells].map((cell) => cell.textContent)).to.deep.equal([ + // Header + 'A', + // First row + '', // mark + 'S1', // label + '4', // value + // Second row + '', + 'S2', + '2', + ]); + + firePointerEvent(svg, 'pointermove', { + clientX: 201, + clientY: 60, + }); + + cells = document.querySelectorAll('.MuiChartsTooltip-root td'); + expect([...cells].map((cell) => cell.textContent)).to.deep.equal([ + // Header + 'B', + // First row + '', + 'S1', + '1', + // Second row + '', + 'S2', + '1', + ]); + }); + + it('should show right values with horizontal layout', function test() { + if (isJSDOM) { + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + this.skip(); + } + + render( +
+ +
, + ); + const svg = document.querySelector('svg')!; + + firePointerEvent(svg, 'pointermove', { + clientX: 150, + clientY: 60, + }); + + let cells = document.querySelectorAll('.MuiChartsTooltip-root td'); + expect([...cells].map((cell) => cell.textContent)).to.deep.equal([ + // Header + 'A', + // First row + '', + 'S1', + '4', + // Second row + '', + 'S2', + '2', + ]); + + firePointerEvent(svg, 'pointermove', { + clientX: 150, + clientY: 220, + }); + + cells = document.querySelectorAll('.MuiChartsTooltip-root td'); + expect([...cells].map((cell) => cell.textContent)).to.deep.equal([ + // Header + 'B', + // First row + '', + 'S1', + '1', + // Second row + '', + 'S2', + '1', + ]); + }); + }); + + describe('item trigger', () => { + it('should show right values with vertical layout', function test() { + if (isJSDOM) { + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + this.skip(); + } + + render( +
+ +
, + ); + const svg = document.querySelector('svg')!; + const rectangles = document.querySelectorAll('rect'); + + fireEvent.pointerEnter(rectangles[0]); + + firePointerEvent(svg, 'pointermove', { + clientX: 150, + clientY: 60, + }); // Only to set the tooltip position + + let cells = document.querySelectorAll('.MuiChartsTooltip-root td'); + expect([...cells].map((cell) => cell.textContent)).to.deep.equal(['', 'S1', '4']); + + fireEvent.pointerEnter(rectangles[3]); + cells = document.querySelectorAll('.MuiChartsTooltip-root td'); + expect([...cells].map((cell) => cell.textContent)).to.deep.equal(['', 'S2', '1']); + }); + + it('should show right values with horizontal layout', function test() { + if (isJSDOM) { + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + this.skip(); + } + + render( +
+ +
, + ); + const svg = document.querySelector('svg')!; + const rectangles = document.querySelectorAll('rect'); + + fireEvent.pointerEnter(rectangles[0]); + + firePointerEvent(svg, 'pointermove', { + clientX: 150, + clientY: 60, + }); // Only to set the tooltip position + + let cells = document.querySelectorAll('.MuiChartsTooltip-root td'); + expect([...cells].map((cell) => cell.textContent)).to.deep.equal(['', 'S1', '4']); + + fireEvent.pointerEnter(rectangles[3]); + cells = document.querySelectorAll('.MuiChartsTooltip-root td'); + expect([...cells].map((cell) => cell.textContent)).to.deep.equal(['', 'S2', '1']); + }); + }); +}); diff --git a/packages/x-charts/src/ChartsTooltip/utils.tsx b/packages/x-charts/src/ChartsTooltip/utils.tsx index 8a7ad4a359a96..f637bbe80b1af 100644 --- a/packages/x-charts/src/ChartsTooltip/utils.tsx +++ b/packages/x-charts/src/ChartsTooltip/utils.tsx @@ -57,8 +57,10 @@ export function useMouseTracker() { return () => {}; } - const handleOut = () => { - setMousePosition(null); + const handleOut = (event: PointerEvent) => { + if (event.pointerType !== 'mouse') { + setMousePosition(null); + } }; const handleMove = (event: PointerEvent) => { diff --git a/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx b/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx index dd5ce3424fad2..995ef6f370db4 100644 --- a/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx +++ b/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import useSlotProps from '@mui/utils/useSlotProps'; import composeClasses from '@mui/utils/composeClasses'; -import { useThemeProps, useTheme, Theme } from '@mui/material/styles'; +import { useThemeProps, useTheme, Theme, styled } from '@mui/material/styles'; import { useCartesianContext } from '../context/CartesianProvider'; import { useTicks, TickItemType } from '../hooks/useTicks'; import { AxisDefaultized, ChartsXAxisProps } from '../models/axis'; @@ -13,6 +13,8 @@ import { getMinXTranslation } from '../internals/geometry'; import { useMounted } from '../hooks/useMounted'; import { useDrawingArea } from '../hooks/useDrawingArea'; import { getWordsByLines } from '../internals/getWordsByLines'; +import { isInfinity } from '../internals/isInfinity'; +import { isBandScale } from '../internals/isBandScale'; const useUtilityClasses = (ownerState: ChartsXAxisProps & { theme: Theme }) => { const { classes, position } = ownerState; @@ -81,6 +83,12 @@ function addLabelDimension( }); } +const XAxisRoot = styled(AxisRoot, { + name: 'MuiChartsXAxis', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({}); + const defaultProps = { position: 'bottom', disableLine: false, @@ -127,11 +135,12 @@ function ChartsXAxis(inProps: ChartsXAxisProps) { tickLabelInterval, tickPlacement, tickLabelPlacement, + sx, } = defaultizedProps; const theme = useTheme(); const classes = useUtilityClasses({ ...defaultizedProps, theme }); - const { left, top, width, height } = useDrawingArea(); + const { left, top, width, height, isPointInside } = useDrawingArea(); const tickSize = disableTicks ? 4 : tickSizeProp; @@ -193,16 +202,18 @@ function ChartsXAxis(inProps: ChartsXAxisProps) { }); const domain = xScale.domain(); - if (domain.length === 0 || domain[0] === domain[1]) { - // Skip axis rendering if - // - the data is empty (for band and point axis) - // - No data is associated to the axis (other scale types) + const ordinalAxis = isBandScale(xScale); + // Skip axis rendering if no data is available + // - The domain is an empty array for band/point scales. + // - The domains contains Infinity for continuous scales. + if ((ordinalAxis && domain.length === 0) || (!ordinalAxis && domain.some(isInfinity))) { return null; } return ( - {!disableLine && ( @@ -212,9 +223,8 @@ function ChartsXAxis(inProps: ChartsXAxisProps) { const xTickLabel = labelOffset ?? 0; const yTickLabel = positionSign * (tickSize + 3); - const showTick = offset >= left - 1 && offset <= left + width + 1; - const showTickLabel = - offset + xTickLabel >= left - 1 && offset + xTickLabel <= left + width + 1; + const showTick = isPointInside({ x: offset, y: -1 }, { direction: 'x' }); + const showTickLabel = isPointInside({ x: offset + xTickLabel, y: -1 }, { direction: 'x' }); return ( {!disableTicks && showTick && ( @@ -242,7 +252,7 @@ function ChartsXAxis(inProps: ChartsXAxisProps) { )} - + ); } @@ -308,6 +318,11 @@ ChartsXAxis.propTypes = { * @default 'currentColor' */ stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), /** * The font size of the axis ticks text. * @default 12 diff --git a/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx b/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx index 43f365d4881fd..923a20dc06aa8 100644 --- a/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx +++ b/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx @@ -2,7 +2,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import useSlotProps from '@mui/utils/useSlotProps'; import composeClasses from '@mui/utils/composeClasses'; -import { useThemeProps, useTheme, Theme } from '@mui/material/styles'; +import { useThemeProps, useTheme, Theme, styled } from '@mui/material/styles'; +import { useRtl } from '@mui/system/RtlProvider'; import { useCartesianContext } from '../context/CartesianProvider'; import { useTicks } from '../hooks/useTicks'; import { useDrawingArea } from '../hooks/useDrawingArea'; @@ -10,6 +11,8 @@ import { ChartsYAxisProps } from '../models/axis'; import { AxisRoot } from '../internals/components/AxisSharedComponents'; import { ChartsText, ChartsTextProps } from '../ChartsText'; import { getAxisUtilityClass } from '../ChartsAxis/axisClasses'; +import { isInfinity } from '../internals/isInfinity'; +import { isBandScale } from '../internals/isBandScale'; const useUtilityClasses = (ownerState: ChartsYAxisProps & { theme: Theme }) => { const { classes, position } = ownerState; @@ -25,6 +28,12 @@ const useUtilityClasses = (ownerState: ChartsYAxisProps & { theme: Theme }) => { return composeClasses(slots, getAxisUtilityClass, classes); }; +const YAxisRoot = styled(AxisRoot, { + name: 'MuiChartsYAxis', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, +})({}); + const defaultProps = { position: 'left', disableLine: false, @@ -71,14 +80,15 @@ function ChartsYAxis(inProps: ChartsYAxisProps) { tickLabelPlacement, tickInterval, tickLabelInterval, + sx, } = defaultizedProps; const theme = useTheme(); - const isRTL = theme.direction === 'rtl'; + const isRtl = useRtl(); const classes = useUtilityClasses({ ...defaultizedProps, theme }); - const { left, top, width, height } = useDrawingArea(); + const { left, top, width, height, isPointInside } = useDrawingArea(); const tickSize = disableTicks ? 4 : tickSizeProp; @@ -103,7 +113,7 @@ function ChartsYAxis(inProps: ChartsYAxisProps) { const TickLabel = slots?.axisTickLabel ?? ChartsText; const Label = slots?.axisLabel ?? ChartsText; - const revertAnchor = (!isRTL && position === 'right') || (isRTL && position !== 'right'); + const revertAnchor = (!isRtl && position === 'right') || (isRtl && position !== 'right'); const axisTickLabelProps = useSlotProps({ elementType: TickLabel, externalSlotProps: slotProps?.axisTickLabel, @@ -144,17 +154,19 @@ function ChartsYAxis(inProps: ChartsYAxisProps) { }); const domain = yScale.domain(); - if (domain.length === 0 || domain[0] === domain[1]) { - // Skip axis rendering if - // - the data is empty (for band and point axis) - // - No data is associated to the axis (other scale types) + const ordinalAxis = isBandScale(yScale); + // Skip axis rendering if no data is available + // - The domain is an empty array for band/point scales. + // - The domains contains Infinity for continuous scales. + if ((ordinalAxis && domain.length === 0) || (!ordinalAxis && domain.some(isInfinity))) { return null; } return ( - {!disableLine && ( @@ -166,7 +178,7 @@ function ChartsYAxis(inProps: ChartsYAxisProps) { const skipLabel = typeof tickLabelInterval === 'function' && !tickLabelInterval?.(value, index); - const showLabel = offset >= top - 1 && offset <= height + top + 1; + const showLabel = isPointInside({ x: -1, y: offset }, { direction: 'y' }); if (!showLabel) { return null; @@ -198,7 +210,7 @@ function ChartsYAxis(inProps: ChartsYAxisProps) {
)} - + ); } @@ -264,6 +276,11 @@ ChartsYAxis.propTypes = { * @default 'currentColor' */ stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), /** * The font size of the axis ticks text. * @default 12 diff --git a/packages/x-charts/src/LineChart/AnimatedArea.tsx b/packages/x-charts/src/LineChart/AnimatedArea.tsx index adebfc0ac8662..0a1c8ec420b3c 100644 --- a/packages/x-charts/src/LineChart/AnimatedArea.tsx +++ b/packages/x-charts/src/LineChart/AnimatedArea.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { styled } from '@mui/material/styles'; -import { animated, useSpring } from '@react-spring/web'; +import { animated, useTransition } from '@react-spring/web'; import { color as d3Color } from '@mui/x-charts-vendor/d3-color'; -import { useAnimatedPath } from '../internals/useAnimatedPath'; import { cleanId } from '../internals/cleanId'; import type { AreaElementOwnerState } from './AreaElement'; import { useChartId, useDrawingArea } from '../hooks'; +import { useStringInterpolator } from '../internals/useStringInterpolator'; export const AreaElementPath = styled(animated.path, { name: 'MuiAreaElement', @@ -47,11 +47,21 @@ function AnimatedArea(props: AnimatedAreaProps) { const { left, top, right, bottom, width, height } = useDrawingArea(); const chartId = useChartId(); - const path = useAnimatedPath(d, skipAnimation); + const stringInterpolator = useStringInterpolator(d); - const { animatedWidth } = useSpring({ + const transitionAppear = useTransition([1], { from: { animatedWidth: left }, to: { animatedWidth: width + left + right }, + enter: { animatedWidth: width + left + right }, + leave: { animatedWidth: left }, + reset: false, + immediate: skipAnimation, + }); + + const transitionChange = useTransition([stringInterpolator], { + from: { value: 0 }, + to: { value: 1 }, + enter: { value: 1 }, reset: false, immediate: skipAnimation, }); @@ -60,10 +70,14 @@ function AnimatedArea(props: AnimatedAreaProps) { return ( - + {transitionAppear((style) => ( + + ))} - + {transitionChange((style, interpolator) => ( + + ))} ); diff --git a/packages/x-charts/src/LineChart/AnimatedLine.tsx b/packages/x-charts/src/LineChart/AnimatedLine.tsx index 968f44652d01d..50c09474ba44f 100644 --- a/packages/x-charts/src/LineChart/AnimatedLine.tsx +++ b/packages/x-charts/src/LineChart/AnimatedLine.tsx @@ -1,13 +1,13 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import { animated, useSpring } from '@react-spring/web'; +import { animated, useTransition } from '@react-spring/web'; import { color as d3Color } from '@mui/x-charts-vendor/d3-color'; import { styled } from '@mui/material/styles'; -import { useAnimatedPath } from '../internals/useAnimatedPath'; import { cleanId } from '../internals/cleanId'; import type { LineElementOwnerState } from './LineElement'; import { useChartId } from '../hooks/useChartId'; import { useDrawingArea } from '../hooks/useDrawingArea'; +import { useStringInterpolator } from '../internals/useStringInterpolator'; export const LineElementPath = styled(animated.path, { name: 'MuiLineElement', @@ -50,11 +50,21 @@ function AnimatedLine(props: AnimatedLineProps) { const { left, top, bottom, width, height, right } = useDrawingArea(); const chartId = useChartId(); - const path = useAnimatedPath(d, skipAnimation); + const stringInterpolator = useStringInterpolator(d); - const { animatedWidth } = useSpring({ + const transitionAppear = useTransition([1], { from: { animatedWidth: left }, to: { animatedWidth: width + left + right }, + enter: { animatedWidth: width + left + right }, + leave: { animatedWidth: left }, + reset: false, + immediate: skipAnimation, + }); + + const transitionChange = useTransition([stringInterpolator], { + from: { value: 0 }, + to: { value: 1 }, + enter: { value: 1 }, reset: false, immediate: skipAnimation, }); @@ -63,10 +73,14 @@ function AnimatedLine(props: AnimatedLineProps) { return ( - + {transitionAppear((style) => ( + + ))} - + {transitionChange((style, interpolator) => ( + + ))} ); diff --git a/packages/x-charts/src/LineChart/AreaPlot.tsx b/packages/x-charts/src/LineChart/AreaPlot.tsx index 79412e5211e60..e87e4581e1198 100644 --- a/packages/x-charts/src/LineChart/AreaPlot.tsx +++ b/packages/x-charts/src/LineChart/AreaPlot.tsx @@ -38,88 +38,104 @@ const useAggregatedData = () => { const seriesData = useLineSeries(); const axisData = useCartesianContext(); - if (seriesData === undefined) { - return []; - } - - const { series, stackingGroups } = seriesData; - const { xAxis, yAxis, xAxisIds, yAxisIds } = axisData; - const defaultXAxisId = xAxisIds[0]; - const defaultYAxisId = yAxisIds[0]; - - return stackingGroups.flatMap(({ ids: groupIds }) => { - return [...groupIds] - .reverse() // Revert stacked area for a more pleasant animation - .map((seriesId) => { - const { - xAxisId: xAxisIdProp, - yAxisId: yAxisIdProp, - xAxisKey = defaultXAxisId, - yAxisKey = defaultYAxisId, - stackedData, - data, - connectNulls, - } = series[seriesId]; - - const xAxisId = xAxisIdProp ?? xAxisKey; - const yAxisId = yAxisIdProp ?? yAxisKey; - - const xScale = getValueToPositionMapper(xAxis[xAxisId].scale); - const yScale = yAxis[yAxisId].scale; - const xData = xAxis[xAxisId].data; - - const gradientUsed: [AxisId, 'x' | 'y'] | undefined = - (yAxis[yAxisId].colorScale && [yAxisId, 'y']) || - (xAxis[xAxisId].colorScale && [xAxisId, 'x']) || - undefined; - - if (process.env.NODE_ENV !== 'production') { - if (xData === undefined) { - throw new Error( - `MUI X: ${ - xAxisId === DEFAULT_X_AXIS_KEY - ? 'The first `xAxis`' - : `The x-axis with id "${xAxisId}"` - } should have data property to be able to display a line plot.`, - ); - } - if (xData.length < stackedData.length) { - throw new Error( - `MUI X: The data length of the x axis (${xData.length} items) is lower than the length of series (${stackedData.length} items).`, - ); - } - } - - const areaPath = d3Area<{ - x: any; - y: [number, number]; - }>() - .x((d) => xScale(d.x)) - .defined((_, i) => connectNulls || data[i] != null) - .y0((d) => { - const value = d.y && yScale(d.y[0])!; - if (Number.isNaN(value)) { - return yScale.range()[0]; + // This memo prevents odd line chart behavior when hydrating. + const allData = React.useMemo(() => { + if (seriesData === undefined) { + return []; + } + + const { series, stackingGroups } = seriesData; + const { xAxis, yAxis, xAxisIds, yAxisIds } = axisData; + const defaultXAxisId = xAxisIds[0]; + const defaultYAxisId = yAxisIds[0]; + + return stackingGroups.flatMap(({ ids: groupIds }) => { + return [...groupIds] + .reverse() // Revert stacked area for a more pleasant animation + .map((seriesId) => { + const { + xAxisId: xAxisIdProp, + yAxisId: yAxisIdProp, + xAxisKey = defaultXAxisId, + yAxisKey = defaultYAxisId, + stackedData, + data, + connectNulls, + baseline, + } = series[seriesId]; + + const xAxisId = xAxisIdProp ?? xAxisKey; + const yAxisId = yAxisIdProp ?? yAxisKey; + + const xScale = getValueToPositionMapper(xAxis[xAxisId].scale); + const yScale = yAxis[yAxisId].scale; + const xData = xAxis[xAxisId].data; + + const gradientUsed: [AxisId, 'x' | 'y'] | undefined = + (yAxis[yAxisId].colorScale && [yAxisId, 'y']) || + (xAxis[xAxisId].colorScale && [xAxisId, 'x']) || + undefined; + + if (process.env.NODE_ENV !== 'production') { + if (xData === undefined) { + throw new Error( + `MUI X: ${ + xAxisId === DEFAULT_X_AXIS_KEY + ? 'The first `xAxis`' + : `The x-axis with id "${xAxisId}"` + } should have data property to be able to display a line plot.`, + ); + } + if (xData.length < stackedData.length) { + throw new Error( + `MUI X: The data length of the x axis (${xData.length} items) is lower than the length of series (${stackedData.length} items).`, + ); } - return value; - }) - .y1((d) => d.y && yScale(d.y[1])!); - - const curve = getCurveFactory(series[seriesId].curve); - const formattedData = xData?.map((x, index) => ({ x, y: stackedData[index] })) ?? []; - const d3Data = connectNulls - ? formattedData.filter((_, i) => data[i] != null) - : formattedData; - - const d = areaPath.curve(curve)(d3Data) || ''; - return { - ...series[seriesId], - gradientUsed, - d, - seriesId, - }; - }); - }); + } + + const areaPath = d3Area<{ + x: any; + y: [number, number]; + }>() + .x((d) => xScale(d.x)) + .defined((_, i) => connectNulls || data[i] != null) + .y0((d) => { + if (typeof baseline === 'number') { + return yScale(baseline)!; + } + if (baseline === 'max') { + return yScale.range()[1]; + } + if (baseline === 'min') { + return yScale.range()[0]; + } + + const value = d.y && yScale(d.y[0])!; + if (Number.isNaN(value)) { + return yScale.range()[0]; + } + return value; + }) + .y1((d) => d.y && yScale(d.y[1])!); + + const curve = getCurveFactory(series[seriesId].curve); + const formattedData = xData?.map((x, index) => ({ x, y: stackedData[index] })) ?? []; + const d3Data = connectNulls + ? formattedData.filter((_, i) => data[i] != null) + : formattedData; + + const d = areaPath.curve(curve)(d3Data) || ''; + return { + ...series[seriesId], + gradientUsed, + d, + seriesId, + }; + }); + }); + }, [seriesData, axisData]); + + return allData; }; /** diff --git a/packages/x-charts/src/LineChart/LineChart.test.tsx b/packages/x-charts/src/LineChart/LineChart.test.tsx index 760dc0317e61e..1ec0c3f8ac2a5 100644 --- a/packages/x-charts/src/LineChart/LineChart.test.tsx +++ b/packages/x-charts/src/LineChart/LineChart.test.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; -import { createRenderer, describeConformance } from '@mui/internal-test-utils'; +import { createRenderer } from '@mui/internal-test-utils/createRenderer'; +import { describeConformance } from 'test/utils/describeConformance'; import { LineChart } from '@mui/x-charts/LineChart'; describe('', () => { @@ -19,7 +20,6 @@ describe('', () => { 'slotPropsProp', 'slotPropsCallback', 'slotsProp', - 'themeDefaultProps', 'themeStyleOverrides', 'themeVariants', 'themeCustomPalette', diff --git a/packages/x-charts/src/LineChart/LineChart.tsx b/packages/x-charts/src/LineChart/LineChart.tsx index d73abe04bf371..94235ce0c79d6 100644 --- a/packages/x-charts/src/LineChart/LineChart.tsx +++ b/packages/x-charts/src/LineChart/LineChart.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import { useThemeProps } from '@mui/material/styles'; import { AreaPlot, AreaPlotProps, AreaPlotSlotProps, AreaPlotSlots } from './AreaPlot'; import { LinePlot, LinePlotProps, LinePlotSlotProps, LinePlotSlots } from './LinePlot'; import { @@ -135,7 +136,8 @@ export interface LineChartProps * * - [LineChart API](https://mui.com/x/api/charts/line-chart/) */ -const LineChart = React.forwardRef(function LineChart(props: LineChartProps, ref) { +const LineChart = React.forwardRef(function LineChart(inProps: LineChartProps, ref) { + const props = useThemeProps({ props: inProps, name: 'MuiLineChart' }); const { chartContainerProps, axisClickHandlerProps, @@ -157,7 +159,7 @@ const LineChart = React.forwardRef(function LineChart(props: LineChartProps, ref return ( {props.onAxisClick && } - {props.grid && } + @@ -414,6 +416,11 @@ LineChart.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -484,6 +491,11 @@ LineChart.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), diff --git a/packages/x-charts/src/LineChart/LinePlot.tsx b/packages/x-charts/src/LineChart/LinePlot.tsx index 96603b232d98f..d4e1e579ff2e1 100644 --- a/packages/x-charts/src/LineChart/LinePlot.tsx +++ b/packages/x-charts/src/LineChart/LinePlot.tsx @@ -38,76 +38,83 @@ const useAggregatedData = () => { const seriesData = useLineSeries(); const axisData = useCartesianContext(); - if (seriesData === undefined) { - return []; - } - - const { series, stackingGroups } = seriesData; - const { xAxis, yAxis, xAxisIds, yAxisIds } = axisData; - const defaultXAxisId = xAxisIds[0]; - const defaultYAxisId = yAxisIds[0]; - - return stackingGroups.flatMap(({ ids: groupIds }) => { - return groupIds.flatMap((seriesId) => { - const { - xAxisId: xAxisIdProp, - yAxisId: yAxisIdProp, - xAxisKey = defaultXAxisId, - yAxisKey = defaultYAxisId, - stackedData, - data, - connectNulls, - } = series[seriesId]; - - const xAxisId = xAxisIdProp ?? xAxisKey; - const yAxisId = yAxisIdProp ?? yAxisKey; - - const xScale = getValueToPositionMapper(xAxis[xAxisId].scale); - const yScale = yAxis[yAxisId].scale; - const xData = xAxis[xAxisId].data; - - const gradientUsed: [AxisId, 'x' | 'y'] | undefined = - (yAxis[yAxisId].colorScale && [yAxisId, 'y']) || - (xAxis[xAxisId].colorScale && [xAxisId, 'x']) || - undefined; - - if (process.env.NODE_ENV !== 'production') { - if (xData === undefined) { - throw new Error( - `MUI X: ${ - xAxisId === DEFAULT_X_AXIS_KEY - ? 'The first `xAxis`' - : `The x-axis with id "${xAxisId}"` - } should have data property to be able to display a line plot.`, - ); + // This memo prevents odd line chart behavior when hydrating. + const allData = React.useMemo(() => { + if (seriesData === undefined) { + return []; + } + + const { series, stackingGroups } = seriesData; + const { xAxis, yAxis, xAxisIds, yAxisIds } = axisData; + const defaultXAxisId = xAxisIds[0]; + const defaultYAxisId = yAxisIds[0]; + + return stackingGroups.flatMap(({ ids: groupIds }) => { + return groupIds.flatMap((seriesId) => { + const { + xAxisId: xAxisIdProp, + yAxisId: yAxisIdProp, + xAxisKey = defaultXAxisId, + yAxisKey = defaultYAxisId, + stackedData, + data, + connectNulls, + } = series[seriesId]; + + const xAxisId = xAxisIdProp ?? xAxisKey; + const yAxisId = yAxisIdProp ?? yAxisKey; + + const xScale = getValueToPositionMapper(xAxis[xAxisId].scale); + const yScale = yAxis[yAxisId].scale; + const xData = xAxis[xAxisId].data; + + const gradientUsed: [AxisId, 'x' | 'y'] | undefined = + (yAxis[yAxisId].colorScale && [yAxisId, 'y']) || + (xAxis[xAxisId].colorScale && [xAxisId, 'x']) || + undefined; + + if (process.env.NODE_ENV !== 'production') { + if (xData === undefined) { + throw new Error( + `MUI X: ${ + xAxisId === DEFAULT_X_AXIS_KEY + ? 'The first `xAxis`' + : `The x-axis with id "${xAxisId}"` + } should have data property to be able to display a line plot.`, + ); + } + if (xData.length < stackedData.length) { + throw new Error( + `MUI X: The data length of the x axis (${xData.length} items) is lower than the length of series (${stackedData.length} items).`, + ); + } } - if (xData.length < stackedData.length) { - throw new Error( - `MUI X: The data length of the x axis (${xData.length} items) is lower than the length of series (${stackedData.length} items).`, - ); - } - } - - const linePath = d3Line<{ - x: any; - y: [number, number]; - }>() - .x((d) => xScale(d.x)) - .defined((_, i) => connectNulls || data[i] != null) - .y((d) => yScale(d.y[1])!); - - const formattedData = xData?.map((x, index) => ({ x, y: stackedData[index] })) ?? []; - const d3Data = connectNulls ? formattedData.filter((_, i) => data[i] != null) : formattedData; - - const d = linePath.curve(getCurveFactory(series[seriesId].curve))(d3Data) || ''; - return { - ...series[seriesId], - gradientUsed, - d, - seriesId, - }; + + const linePath = d3Line<{ + x: any; + y: [number, number]; + }>() + .x((d) => xScale(d.x)) + .defined((_, i) => connectNulls || data[i] != null) + .y((d) => yScale(d.y[1])!); + + const formattedData = xData?.map((x, index) => ({ x, y: stackedData[index] })) ?? []; + const d3Data = connectNulls + ? formattedData.filter((_, i) => data[i] != null) + : formattedData; + + const d = linePath.curve(getCurveFactory(series[seriesId].curve))(d3Data) || ''; + return { + ...series[seriesId], + gradientUsed, + d, + seriesId, + }; + }); }); - }); + }, [seriesData, axisData]); + + return allData; }; /** diff --git a/packages/x-charts/src/LineChart/MarkElement.tsx b/packages/x-charts/src/LineChart/MarkElement.tsx index 137f44f252b74..b86ef162168d0 100644 --- a/packages/x-charts/src/LineChart/MarkElement.tsx +++ b/packages/x-charts/src/LineChart/MarkElement.tsx @@ -107,7 +107,7 @@ function MarkElement(props: MarkElementProps) { }); const { axis } = React.useContext(InteractionContext); - const position = useSpring({ x, y, immediate: skipAnimation }); + const position = useSpring({ to: { x, y }, immediate: skipAnimation }); const ownerState = { id, classes: innerClasses, diff --git a/packages/x-charts/src/LineChart/checkClickEvent.test.tsx b/packages/x-charts/src/LineChart/checkClickEvent.test.tsx new file mode 100644 index 0000000000000..8c2e0800f2272 --- /dev/null +++ b/packages/x-charts/src/LineChart/checkClickEvent.test.tsx @@ -0,0 +1,282 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { createRenderer, fireEvent } from '@mui/internal-test-utils'; +import { spy } from 'sinon'; +import { LineChart } from '@mui/x-charts/LineChart'; +import { firePointerEvent } from '../tests/firePointerEvent'; + +const config = { + dataset: [ + { x: 10, v1: 0, v2: 10 }, + { x: 20, v1: 5, v2: 8 }, + { x: 30, v1: 8, v2: 5 }, + { x: 40, v1: 10, v2: 0 }, + ], + margin: { top: 0, left: 0, bottom: 0, right: 0 }, + width: 400, + height: 400, +}; + +const isJSDOM = /jsdom/.test(window.navigator.userAgent); + +describe('LineChart - click event', () => { + const { render } = createRenderer(); + + describe('onAxisClick', () => { + it('should provide the right context as second argument', function test() { + if (isJSDOM) { + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + this.skip(); + } + const onAxisClick = spy(); + render( +
+ +
, + ); + const svg = document.querySelector('svg')!; + + firePointerEvent(svg, 'pointermove', { + clientX: 198, + clientY: 60, + }); + fireEvent.click(svg); + + expect(onAxisClick.lastCall.args[1]).to.deep.equal({ + dataIndex: 1, + axisValue: 20, + seriesValues: { s1: 5, s2: 8 }, + }); + + firePointerEvent(svg, 'pointermove', { + clientX: 201, + clientY: 60, + }); + fireEvent.click(svg); + + expect(onAxisClick.lastCall.args[1]).to.deep.equal({ + dataIndex: 2, + axisValue: 30, + seriesValues: { s1: 8, s2: 5 }, + }); + }); + }); + + describe('onMarkClick', () => { + it('should add cursor="pointer" to bar elements', function test() { + render( + {}} + />, + ); + const marks = document.querySelectorAll('path.MuiMarkElement-root'); + + expect(Array.from(marks).map((mark) => mark.getAttribute('cursor'))).to.deep.equal([ + 'pointer', + 'pointer', + 'pointer', + 'pointer', + 'pointer', + 'pointer', + 'pointer', + 'pointer', + ]); + }); + + it('should provide the right context as second argument', function test() { + if (isJSDOM) { + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + this.skip(); + } + const onMarkClick = spy(); + render( +
+ +
, + ); + + const marks = document.querySelectorAll('path.MuiMarkElement-root'); + + fireEvent.click(marks[0]); + expect(onMarkClick.lastCall.args[1]).to.deep.equal({ + type: 'line', + seriesId: 's1', + dataIndex: 0, + }); + + fireEvent.click(marks[1]); + expect(onMarkClick.lastCall.args[1]).to.deep.equal({ + type: 'line', + seriesId: 's1', + dataIndex: 1, + }); + + fireEvent.click(marks[4]); + expect(onMarkClick.lastCall.args[1]).to.deep.equal({ + type: 'line', + seriesId: 's2', + dataIndex: 0, + }); + }); + }); + + describe('onAreaClick', () => { + it('should add cursor="pointer" to bar elements', function test() { + render( + {}} + />, + ); + const areas = document.querySelectorAll('path.MuiAreaElement-root'); + + expect(Array.from(areas).map((area) => area.getAttribute('cursor'))).to.deep.equal([ + 'pointer', + 'pointer', + ]); + }); + + it('should provide the right context as second argument', function test() { + if (isJSDOM) { + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + this.skip(); + } + const onAreaClick = spy(); + render( +
+ +
, + ); + + const areas = document.querySelectorAll('path.MuiAreaElement-root'); + + fireEvent.click(areas[0]); + expect(onAreaClick.lastCall.args[1]).to.deep.equal({ + type: 'line', + seriesId: 's1', + }); + + fireEvent.click(areas[1]); + expect(onAreaClick.lastCall.args[1]).to.deep.equal({ + type: 'line', + seriesId: 's2', + }); + }); + }); + + describe('onLineClick', () => { + it('should add cursor="pointer" to bar elements', function test() { + render( + {}} + />, + ); + const lines = document.querySelectorAll('path.MuiLineElement-root'); + + expect(Array.from(lines).map((line) => line.getAttribute('cursor'))).to.deep.equal([ + 'pointer', + 'pointer', + ]); + }); + + it('should provide the right context as second argument', function test() { + if (isJSDOM) { + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + this.skip(); + } + const onLineClick = spy(); + render( +
+ +
, + ); + + const lines = document.querySelectorAll('path.MuiLineElement-root'); + + fireEvent.click(lines[0]); + expect(onLineClick.lastCall.args[1]).to.deep.equal({ + type: 'line', + seriesId: 's1', + }); + + fireEvent.click(lines[1]); + expect(onLineClick.lastCall.args[1]).to.deep.equal({ + type: 'line', + seriesId: 's2', + }); + }); + }); +}); diff --git a/packages/x-charts/src/LineChart/extremums.ts b/packages/x-charts/src/LineChart/extremums.ts index e913bc31c26ba..e903b5b6e7482 100644 --- a/packages/x-charts/src/LineChart/extremums.ts +++ b/packages/x-charts/src/LineChart/extremums.ts @@ -1,7 +1,4 @@ -import { - ExtremumGetter, - ExtremumGetterResult, -} from '../context/PluginProvider/ExtremumGetter.types'; +import { ExtremumGetter, ExtremumFilter } from '../context/PluginProvider/ExtremumGetter.types'; export const getExtremumX: ExtremumGetter<'line'> = (params) => { const { axis } = params; @@ -11,27 +8,31 @@ export const getExtremumX: ExtremumGetter<'line'> = (params) => { return [minX, maxX]; }; -type GetValuesTypes = (d: [number, number]) => [number, number]; +type GetValues = (d: [number, number]) => [number, number]; function getSeriesExtremums( - getValues: GetValuesTypes, + getValues: GetValues, stackedData: [number, number][], -): ExtremumGetterResult { - if (stackedData.length === 0) { - return [null, null]; - } - return stackedData.reduce((seriesAcc, stackedValue) => { - const [base, value] = getValues(stackedValue); - - if (seriesAcc[0] === null) { - return [Math.min(base, value), Math.max(base, value)] as [number, number]; - } - return [Math.min(base, value, seriesAcc[0]), Math.max(base, value, seriesAcc[1])]; - }, getValues(stackedData[0])); + filter?: ExtremumFilter, +): [number, number] { + return stackedData.reduce<[number, number]>( + (seriesAcc, stackedValue, index) => { + const [base, value] = getValues(stackedValue); + if ( + filter && + (!filter({ y: base, x: null }, index) || !filter({ y: value, x: null }, index)) + ) { + return seriesAcc; + } + + return [Math.min(base, value, seriesAcc[0]), Math.max(base, value, seriesAcc[1])]; + }, + [Infinity, -Infinity], + ); } export const getExtremumY: ExtremumGetter<'line'> = (params) => { - const { series, axis, isDefaultAxis } = params; + const { series, axis, isDefaultAxis, getFilters } = params; return Object.keys(series) .filter((seriesId) => { @@ -39,25 +40,28 @@ export const getExtremumY: ExtremumGetter<'line'> = (params) => { return yAxisId === axis.id || (isDefaultAxis && yAxisId === undefined); }) .reduce( - (acc: ExtremumGetterResult, seriesId) => { + (acc, seriesId) => { const { area, stackedData } = series[seriesId]; const isArea = area !== undefined; - const getValues: GetValuesTypes = - isArea && axis.scaleType !== 'log' ? (d) => d : (d) => [d[1], d[1]]; // Since this series is not used to display an area, we do not consider the base (the d[0]). + const filter = getFilters?.({ + currentAxisId: axis.id, + isDefaultAxis, + seriesXAxisId: series[seriesId].xAxisId ?? series[seriesId].xAxisKey, + seriesYAxisId: series[seriesId].yAxisId ?? series[seriesId].yAxisKey, + }); - const seriesExtremums = getSeriesExtremums(getValues, stackedData); + // Since this series is not used to display an area, we do not consider the base (the d[0]). + const getValues: GetValues = + isArea && axis.scaleType !== 'log' && typeof series[seriesId].baseline !== 'string' + ? (d) => d + : (d) => [d[1], d[1]]; - if (acc[0] === null) { - return seriesExtremums; - } - if (seriesExtremums[0] === null) { - return acc; - } + const seriesExtremums = getSeriesExtremums(getValues, stackedData, filter); const [seriesMin, seriesMax] = seriesExtremums; return [Math.min(seriesMin, acc[0]), Math.max(seriesMax, acc[1])]; }, - [null, null], + [Infinity, -Infinity], ); }; diff --git a/packages/x-charts/src/PieChart/PieChart.test.tsx b/packages/x-charts/src/PieChart/PieChart.test.tsx index d77fe8f258610..d1d5c3fd55ff9 100644 --- a/packages/x-charts/src/PieChart/PieChart.test.tsx +++ b/packages/x-charts/src/PieChart/PieChart.test.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; -import { createRenderer, describeConformance } from '@mui/internal-test-utils'; +import { createRenderer } from '@mui/internal-test-utils/createRenderer'; +import { describeConformance } from 'test/utils/describeConformance'; import { PieChart } from '@mui/x-charts/PieChart'; describe('', () => { @@ -30,7 +31,6 @@ describe('', () => { 'slotPropsProp', 'slotPropsCallback', 'slotsProp', - 'themeDefaultProps', 'themeStyleOverrides', 'themeVariants', 'themeCustomPalette', diff --git a/packages/x-charts/src/PieChart/PieChart.tsx b/packages/x-charts/src/PieChart/PieChart.tsx index 75e954f5ab06d..51fe9e0d87a31 100644 --- a/packages/x-charts/src/PieChart/PieChart.tsx +++ b/packages/x-charts/src/PieChart/PieChart.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import { useRtl } from '@mui/system/RtlProvider'; +import { useThemeProps } from '@mui/material/styles'; import { ResponsiveChartContainer, ResponsiveChartContainerProps, @@ -29,7 +31,6 @@ import { ChartsXAxisProps, ChartsYAxisProps, } from '../models/axis'; -import { useIsRTL } from '../internals/useIsRTL'; import { ChartsOverlay, ChartsOverlayProps, @@ -123,7 +124,8 @@ const defaultRTLMargin = { top: 5, bottom: 5, left: 100, right: 5 }; * * - [PieChart API](https://mui.com/x/api/charts/pie-chart/) */ -const PieChart = React.forwardRef(function PieChart(props: PieChartProps, ref) { +const PieChart = React.forwardRef(function PieChart(inProps: PieChartProps, ref) { + const props = useThemeProps({ props: inProps, name: 'MuiPieChart' }); const { xAxis, yAxis, @@ -151,12 +153,12 @@ const PieChart = React.forwardRef(function PieChart(props: PieChartProps, ref) { className, ...other } = props; - const isRTL = useIsRTL(); + const isRtl = useRtl(); - const margin = { ...(isRTL ? defaultRTLMargin : defaultMargin), ...marginProps }; + const margin = { ...(isRtl ? defaultRTLMargin : defaultMargin), ...marginProps }; const legend: ChartsLegendProps = { direction: 'column', - position: { vertical: 'middle', horizontal: isRTL ? 'left' : 'right' }, + position: { vertical: 'middle', horizontal: isRtl ? 'left' : 'right' }, ...legendProps, }; @@ -424,6 +426,11 @@ PieChart.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -494,6 +501,11 @@ PieChart.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), diff --git a/packages/x-charts/src/PieChart/checkClickEvent.test.tsx b/packages/x-charts/src/PieChart/checkClickEvent.test.tsx new file mode 100644 index 0000000000000..bc5f72d36874b --- /dev/null +++ b/packages/x-charts/src/PieChart/checkClickEvent.test.tsx @@ -0,0 +1,74 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { createRenderer, fireEvent } from '@mui/internal-test-utils'; +import { spy } from 'sinon'; +import { PieChart } from '@mui/x-charts/PieChart'; + +const config = { + width: 400, + height: 400, +}; + +describe('PieChart - click event', () => { + const { render } = createRenderer(); + + describe('onItemClick', () => { + it('should add cursor="pointer" to bar elements', function test() { + render( + {}} + />, + ); + const slices = document.querySelectorAll('path.MuiPieArc-root'); + + expect(Array.from(slices).map((slice) => slice.getAttribute('cursor'))).to.deep.equal([ + 'pointer', + 'pointer', + ]); + }); + + it('should provide the right context as second argument', function test() { + const onItemClick = spy(); + render( + , + ); + const slices = document.querySelectorAll('path.MuiPieArc-root'); + + fireEvent.click(slices[0]); + expect(onItemClick.lastCall.args[1]).to.deep.equal({ + type: 'pie', + seriesId: 's1', + dataIndex: 0, + }); + + fireEvent.click(slices[1]); + expect(onItemClick.lastCall.args[1]).to.deep.equal({ + type: 'pie', + seriesId: 's1', + dataIndex: 1, + }); + }); + }); +}); diff --git a/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx b/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx index f5afa5bdf2713..5293e22ae8860 100644 --- a/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx +++ b/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx @@ -162,6 +162,11 @@ ResponsiveChartContainer.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -232,6 +237,11 @@ ResponsiveChartContainer.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), diff --git a/packages/x-charts/src/ScatterChart/ScatterChart.test.tsx b/packages/x-charts/src/ScatterChart/ScatterChart.test.tsx index e96d0e0681a1c..6a0810ce8dd3e 100644 --- a/packages/x-charts/src/ScatterChart/ScatterChart.test.tsx +++ b/packages/x-charts/src/ScatterChart/ScatterChart.test.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; -import { createRenderer, describeConformance } from '@mui/internal-test-utils'; +import { createRenderer } from '@mui/internal-test-utils/createRenderer'; +import { describeConformance } from 'test/utils/describeConformance'; import { ScatterChart } from '@mui/x-charts/ScatterChart'; describe('', () => { @@ -31,7 +32,6 @@ describe('', () => { 'slotPropsProp', 'slotPropsCallback', 'slotsProp', - 'themeDefaultProps', 'themeStyleOverrides', 'themeVariants', 'themeCustomPalette', diff --git a/packages/x-charts/src/ScatterChart/ScatterChart.tsx b/packages/x-charts/src/ScatterChart/ScatterChart.tsx index ee2cd79132f99..d8ae6735b52b7 100644 --- a/packages/x-charts/src/ScatterChart/ScatterChart.tsx +++ b/packages/x-charts/src/ScatterChart/ScatterChart.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import { useThemeProps } from '@mui/material/styles'; import { ScatterPlot, ScatterPlotProps, @@ -118,7 +119,8 @@ export interface ScatterChartProps * * - [ScatterChart API](https://mui.com/x/api/charts/scatter-chart/) */ -const ScatterChart = React.forwardRef(function ScatterChart(props: ScatterChartProps, ref) { +const ScatterChart = React.forwardRef(function ScatterChart(inProps: ScatterChartProps, ref) { + const props = useThemeProps({ props: inProps, name: 'MuiScatterChart' }); const { chartContainerProps, zAxisProps, @@ -137,7 +139,7 @@ const ScatterChart = React.forwardRef(function ScatterChart(props: ScatterChartP {!props.disableVoronoi && } - {props.grid && } + {/* The `data-drawing-container` indicates that children are part of the drawing area. Ref: https://github.com/mui/mui-x/issues/13659 */} @@ -376,6 +378,11 @@ ScatterChart.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), @@ -446,6 +453,11 @@ ScatterChart.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([ PropTypes.oneOf(['auto']), diff --git a/packages/x-charts/src/ScatterChart/checkClickEvent.test.tsx b/packages/x-charts/src/ScatterChart/checkClickEvent.test.tsx new file mode 100644 index 0000000000000..08b8148eff60b --- /dev/null +++ b/packages/x-charts/src/ScatterChart/checkClickEvent.test.tsx @@ -0,0 +1,183 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { createRenderer, fireEvent } from '@mui/internal-test-utils'; +import { spy } from 'sinon'; +import { ScatterChart } from '@mui/x-charts/ScatterChart'; + +const config = { + dataset: [ + { id: 1, x: 0, y: 10 }, + { id: 2, x: 10, y: 10 }, + { id: 3, x: 10, y: 0 }, + { id: 4, x: 0, y: 0 }, + { id: 5, x: 5, y: 5 }, + ], + margin: { top: 0, left: 0, bottom: 0, right: 0 }, + width: 100, + height: 100, +}; + +// Plot on series as a dice 5 +// +// 1...2 +// ..... +// ..5.. +// ..... +// 4...3 + +const isJSDOM = /jsdom/.test(window.navigator.userAgent); + +describe('ScatterChart - click event', () => { + const { render } = createRenderer(); + + describe('onItemClick - using vornoid', () => { + it('should provide the right context as second argument when clicking svg', function test() { + if (isJSDOM) { + // svg.createSVGPoint not supported by JSDom https://github.com/jsdom/jsdom/issues/300 + this.skip(); + } + const onItemClick = spy(); + render( +
+ +
, + ); + const svg = document.querySelector('svg')!; + + fireEvent.click(svg, { + clientX: 10, + clientY: 10, + }); + expect(onItemClick.lastCall.args[1]).to.deep.equal({ + type: 'scatter', + dataIndex: 0, + seriesId: 's1', + }); + + fireEvent.click(svg, { + clientX: 30, + clientY: 30, + }); + expect(onItemClick.lastCall.args[1]).to.deep.equal({ + type: 'scatter', + dataIndex: 4, + seriesId: 's1', + }); + + expect(onItemClick.callCount).to.equal(2); + }); + + it('should provide the right context as second argument when clicking mark', function test() { + if (isJSDOM) { + this.skip(); + } + const onItemClick = spy(); + render( +
+ +
, + ); + const marks = document.querySelectorAll('circle'); + + fireEvent.click(marks[1], { + clientX: 99, + clientY: 2, + }); + + expect(onItemClick.lastCall.args[1]).to.deep.equal({ + type: 'scatter', + dataIndex: 1, + seriesId: 's1', + }); + expect(onItemClick.callCount).to.equal(1); // Make sure voronoid + item click does not duplicate event triggering + }); + }); + + describe('onItemClick - disabling vornoid', () => { + it('should not call onItemClick when clicking the SVG', function test() { + if (isJSDOM) { + this.skip(); + } + const onItemClick = spy(); + render( +
+ +
, + ); + const svg = document.querySelector('svg')!; + + fireEvent.click(svg, { + clientX: 10, + clientY: 10, + }); + expect(onItemClick.callCount).to.equal(0); + }); + + it('should provide the right context as second argument when clicking mark', function test() { + if (isJSDOM) { + this.skip(); + } + const onItemClick = spy(); + render( +
+ +
, + ); + const marks = document.querySelectorAll('circle'); + + fireEvent.click(marks[1], { + clientX: 99, + clientY: 2, + }); + + expect(onItemClick.lastCall.args[1]).to.deep.equal({ + type: 'scatter', + dataIndex: 1, + seriesId: 's1', + }); + expect(onItemClick.callCount).to.equal(1); // Make sure voronoid + item click does not duplicate event triggering + }); + }); +}); diff --git a/packages/x-charts/src/ScatterChart/extremums.ts b/packages/x-charts/src/ScatterChart/extremums.ts index e63cad1b03b19..77fcd28946bee 100644 --- a/packages/x-charts/src/ScatterChart/extremums.ts +++ b/packages/x-charts/src/ScatterChart/extremums.ts @@ -7,17 +7,11 @@ const mergeMinMax = ( acc: ExtremumGetterResult, val: ExtremumGetterResult, ): ExtremumGetterResult => { - if (acc[0] === null || acc[1] === null) { - return val; - } - if (val[0] === null || val[1] === null) { - return acc; - } return [Math.min(acc[0], val[0]), Math.max(acc[1], val[1])]; }; export const getExtremumX: ExtremumGetter<'scatter'> = (params) => { - const { series, axis, isDefaultAxis } = params; + const { series, axis, isDefaultAxis, getFilters } = params; return Object.keys(series) .filter((seriesId) => { @@ -25,22 +19,31 @@ export const getExtremumX: ExtremumGetter<'scatter'> = (params) => { return axisId === axis.id || (axisId === undefined && isDefaultAxis); }) .reduce( - (acc: ExtremumGetterResult, seriesId) => { - const seriesMinMax = series[seriesId].data.reduce( - (accSeries: ExtremumGetterResult, { x }) => { - const val = [x, x] as ExtremumGetterResult; - return mergeMinMax(accSeries, val); + (acc, seriesId) => { + const filter = getFilters?.({ + currentAxisId: axis.id, + isDefaultAxis, + seriesXAxisId: series[seriesId].xAxisId ?? series[seriesId].xAxisKey, + seriesYAxisId: series[seriesId].yAxisId ?? series[seriesId].yAxisKey, + }); + + const seriesMinMax = series[seriesId].data.reduce( + (accSeries, d, dataIndex) => { + if (filter && !filter(d, dataIndex)) { + return accSeries; + } + return mergeMinMax(accSeries, [d.x, d.x]); }, - [null, null], + [Infinity, -Infinity], ); return mergeMinMax(acc, seriesMinMax); }, - [null, null] as ExtremumGetterResult, + [Infinity, -Infinity], ); }; export const getExtremumY: ExtremumGetter<'scatter'> = (params) => { - const { series, axis, isDefaultAxis } = params; + const { series, axis, isDefaultAxis, getFilters } = params; return Object.keys(series) .filter((seriesId) => { @@ -48,16 +51,25 @@ export const getExtremumY: ExtremumGetter<'scatter'> = (params) => { return axisId === axis.id || (axisId === undefined && isDefaultAxis); }) .reduce( - (acc: ExtremumGetterResult, seriesId) => { - const seriesMinMax = series[seriesId].data.reduce( - (accSeries: ExtremumGetterResult, { y }) => { - const val = [y, y] as ExtremumGetterResult; - return mergeMinMax(accSeries, val); + (acc, seriesId) => { + const filter = getFilters?.({ + currentAxisId: axis.id, + isDefaultAxis, + seriesXAxisId: series[seriesId].xAxisId ?? series[seriesId].xAxisKey, + seriesYAxisId: series[seriesId].yAxisId ?? series[seriesId].yAxisKey, + }); + + const seriesMinMax = series[seriesId].data.reduce( + (accSeries, d, dataIndex) => { + if (filter && !filter(d, dataIndex)) { + return accSeries; + } + return mergeMinMax(accSeries, [d.y, d.y]); }, - [null, null], + [Infinity, -Infinity], ); return mergeMinMax(acc, seriesMinMax); }, - [null, null] as ExtremumGetterResult, + [Infinity, -Infinity], ); }; diff --git a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx index 01b76065fcd95..5a1ba89889c86 100644 --- a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx +++ b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx @@ -421,6 +421,11 @@ SparkLineChart.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.array, PropTypes.func]), tickLabelInterval: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.func]), @@ -482,6 +487,11 @@ SparkLineChart.propTypes = { slotProps: PropTypes.object, slots: PropTypes.object, stroke: PropTypes.string, + sx: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), + PropTypes.func, + PropTypes.object, + ]), tickFontSize: PropTypes.number, tickInterval: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.array, PropTypes.func]), tickLabelInterval: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.func]), diff --git a/packages/x-charts/src/constants.ts b/packages/x-charts/src/constants/index.ts similarity index 100% rename from packages/x-charts/src/constants.ts rename to packages/x-charts/src/constants/index.ts diff --git a/packages/x-charts/src/context/CartesianProvider/Cartesian.types.ts b/packages/x-charts/src/context/CartesianProvider/Cartesian.types.ts new file mode 100644 index 0000000000000..7f7244cd55cb0 --- /dev/null +++ b/packages/x-charts/src/context/CartesianProvider/Cartesian.types.ts @@ -0,0 +1,67 @@ +import { DatasetType } from '../../models/seriesType/config'; +import { + AxisDefaultized, + ScaleName, + ChartsXAxisProps, + ChartsYAxisProps, + AxisId, + AxisConfig, +} from '../../models/axis'; +import { ExtremumFilter } from '../PluginProvider'; + +export type CartesianProviderProps = { + /** + * The configuration of the x-axes. + * If not provided, a default axis config is used. + * An array of [[AxisConfig]] objects. + */ + xAxis: AxisConfig[]; + /** + * The configuration of the y-axes. + * If not provided, a default axis config is used. + * An array of [[AxisConfig]] objects. + */ + yAxis: AxisConfig[]; + /** + * An array of objects that can be used to populate series and axes data using their `dataKey` property. + */ + dataset?: DatasetType; + children: React.ReactNode; +}; + +export type DefaultizedAxisConfig = { + [axisId: AxisId]: AxisDefaultized; +}; + +export type CartesianContextState = { + /** + * Mapping from x-axis key to scaling configuration. + */ + xAxis: DefaultizedAxisConfig; + /** + * Mapping from y-axis key to scaling configuration. + */ + yAxis: DefaultizedAxisConfig; + /** + * The x-axes IDs sorted by order they got provided. + */ + xAxisIds: AxisId[]; + /** + * The y-axes IDs sorted by order they got provided. + */ + yAxisIds: AxisId[]; +}; + +export type ZoomData = { axisId: AxisId; start: number; end: number }; + +export type ZoomFilterMode = 'keep' | 'discard' | 'empty'; +export type ZoomOptions = Record; + +export type ZoomAxisFilters = Record; + +export type GetZoomAxisFilters = (params: { + currentAxisId: AxisId | undefined; + seriesXAxisId?: AxisId; + seriesYAxisId?: AxisId; + isDefaultAxis: boolean; +}) => ExtremumFilter; diff --git a/packages/x-charts/src/context/CartesianProvider/CartesianContext.ts b/packages/x-charts/src/context/CartesianProvider/CartesianContext.ts index 6351090516cbe..fb71cc5fdd559 100644 --- a/packages/x-charts/src/context/CartesianProvider/CartesianContext.ts +++ b/packages/x-charts/src/context/CartesianProvider/CartesianContext.ts @@ -1,36 +1,7 @@ import * as React from 'react'; import { Initializable } from '../context.types'; -import { - AxisDefaultized, - ScaleName, - ChartsXAxisProps, - ChartsYAxisProps, - AxisId, -} from '../../models/axis'; - -export type DefaultizedAxisConfig = { - [axisId: AxisId]: AxisDefaultized; -}; - -export type CartesianContextState = { - /** - * Mapping from x-axis key to scaling configuration. - */ - xAxis: DefaultizedAxisConfig; - /** - * Mapping from y-axis key to scaling configuration. - */ - yAxis: DefaultizedAxisConfig; - /** - * The x-axes IDs sorted by order they got provided. - */ - xAxisIds: AxisId[]; - /** - * The y-axes IDs sorted by order they got provided. - */ - yAxisIds: AxisId[]; -}; +import { CartesianContextState } from './Cartesian.types'; export const CartesianContext = React.createContext>({ isInitialized: false, diff --git a/packages/x-charts/src/context/CartesianProvider/CartesianProvider.tsx b/packages/x-charts/src/context/CartesianProvider/CartesianProvider.tsx index 52e8c38cac93a..516bc8386d804 100644 --- a/packages/x-charts/src/context/CartesianProvider/CartesianProvider.tsx +++ b/packages/x-charts/src/context/CartesianProvider/CartesianProvider.tsx @@ -1,35 +1,14 @@ import * as React from 'react'; -import { AxisConfig, ChartsXAxisProps, ChartsYAxisProps, ScaleName } from '../../models/axis'; -import { DatasetType } from '../../models/seriesType/config'; import { useDrawingArea } from '../../hooks/useDrawingArea'; import { useSeries } from '../../hooks/useSeries'; import { CartesianContext } from './CartesianContext'; import { computeValue } from './computeValue'; import { useXExtremumGetter } from '../PluginProvider/useXExtremumGetter'; import { useYExtremumGetter } from '../PluginProvider'; +import { CartesianProviderProps } from './Cartesian.types'; -export type CartesianContextProviderProps = { - /** - * The configuration of the x-axes. - * If not provided, a default axis config is used. - * An array of [[AxisConfig]] objects. - */ - xAxis: AxisConfig[]; - /** - * The configuration of the y-axes. - * If not provided, a default axis config is used. - * An array of [[AxisConfig]] objects. - */ - yAxis: AxisConfig[]; - /** - * An array of objects that can be used to populate series and axes data using their `dataKey` property. - */ - dataset?: DatasetType; - children: React.ReactNode; -}; - -function CartesianContextProvider(props: CartesianContextProviderProps) { - const { xAxis, yAxis, dataset, children } = props; +function CartesianProvider(props: CartesianProviderProps) { + const { xAxis, yAxis, children } = props; const formattedSeries = useSeries(); const drawingArea = useDrawingArea(); @@ -43,10 +22,9 @@ function CartesianContextProvider(props: CartesianContextProviderProps) { formattedSeries, axis: xAxis, extremumGetters: xExtremumGetters, - dataset, axisDirection: 'x', }), - [drawingArea, formattedSeries, xAxis, xExtremumGetters, dataset], + [drawingArea, formattedSeries, xAxis, xExtremumGetters], ); const yValues = React.useMemo( @@ -56,10 +34,9 @@ function CartesianContextProvider(props: CartesianContextProviderProps) { formattedSeries, axis: yAxis, extremumGetters: yExtremumGetters, - dataset, axisDirection: 'y', }), - [drawingArea, formattedSeries, yAxis, yExtremumGetters, dataset], + [drawingArea, formattedSeries, yAxis, yExtremumGetters], ); const value = React.useMemo( @@ -78,4 +55,4 @@ function CartesianContextProvider(props: CartesianContextProviderProps) { return {children}; } -export { CartesianContextProvider }; +export { CartesianProvider }; diff --git a/packages/x-charts/src/context/CartesianProvider/computeValue.ts b/packages/x-charts/src/context/CartesianProvider/computeValue.ts index 4b8645c185de7..19eb03e996b92 100644 --- a/packages/x-charts/src/context/CartesianProvider/computeValue.ts +++ b/packages/x-charts/src/context/CartesianProvider/computeValue.ts @@ -6,18 +6,22 @@ import { ChartsYAxisProps, isBandScaleConfig, isPointScaleConfig, - AxisId, } from '../../models/axis'; -import { CartesianChartSeriesType, DatasetType } from '../../models/seriesType/config'; -import { DefaultizedAxisConfig } from './CartesianContext'; +import { CartesianChartSeriesType } from '../../models/seriesType/config'; import { getColorScale, getOrdinalColorScale } from '../../internals/colorScale'; import { getTickNumber } from '../../hooks/useTicks'; import { getScale } from '../../internals/getScale'; import { DrawingArea } from '../DrawingProvider'; import { FormattedSeries } from '../SeriesProvider'; -import { getAxisExtremum } from './getAxisExtremum'; -import { normalizeAxis } from './normalizeAxis'; +import { zoomScaleRange } from './zoom'; import { ExtremumGetter } from '../PluginProvider'; +import { + DefaultizedAxisConfig, + ZoomData, + ZoomOptions, + GetZoomAxisFilters, +} from './Cartesian.types'; +import { getAxisExtremum } from './getAxisExtremum'; const getRange = (drawingArea: DrawingArea, axisDirection: 'x' | 'y', isReverse?: boolean) => { const range = @@ -28,17 +32,6 @@ const getRange = (drawingArea: DrawingArea, axisDirection: 'x' | 'y', isReverse? 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( @@ -54,74 +47,75 @@ function createDateFormatter( const DEFAULT_CATEGORY_GAP_RATIO = 0.2; const DEFAULT_BAR_GAP_RATIO = 0.1; -export function computeValue(options: { - drawingArea: DrawingArea; - formattedSeries: FormattedSeries; - axis: AxisConfig[] | undefined; - extremumGetters: { [K in CartesianChartSeriesType]?: ExtremumGetter }; - axisDirection: 'y'; - dataset: DatasetType | undefined; - zoomData?: { axisId: AxisId; start: number; end: number }[]; -}): { - axis: DefaultizedAxisConfig; +type ComputeResult = { + axis: DefaultizedAxisConfig; axisIds: string[]; }; -export function computeValue(options: { + +type ComputeCommonParams = { drawingArea: DrawingArea; formattedSeries: FormattedSeries; - axis: AxisConfig[] | undefined; extremumGetters: { [K in CartesianChartSeriesType]?: ExtremumGetter }; - axisDirection: 'x'; - dataset: DatasetType | undefined; - zoomData?: { axisId: AxisId; start: number; end: number }[]; -}): { - axis: DefaultizedAxisConfig; - axisIds: string[]; + zoomData?: ZoomData[]; + zoomOptions?: ZoomOptions; + getFilters?: GetZoomAxisFilters; }; + +export function computeValue( + options: ComputeCommonParams & { + axis: AxisConfig[]; + axisDirection: 'y'; + }, +): ComputeResult; +export function computeValue( + options: ComputeCommonParams & { + axis: AxisConfig[]; + axisDirection: 'x'; + }, +): ComputeResult; export function computeValue({ drawingArea, formattedSeries, - axis: inAxis, + axis: allAxis, extremumGetters, - dataset, axisDirection, zoomData, -}: { - drawingArea: DrawingArea; - formattedSeries: FormattedSeries; - axis: AxisConfig[] | undefined; - extremumGetters: { [K in CartesianChartSeriesType]?: ExtremumGetter }; + zoomOptions, + getFilters, +}: ComputeCommonParams & { + axis: AxisConfig[]; axisDirection: 'x' | 'y'; - dataset: DatasetType | undefined; - zoomData?: { axisId: AxisId; start: number; end: number }[]; }) { - const allAxis = normalizeAxis(inAxis, dataset, axisDirection); - const completeAxis: DefaultizedAxisConfig = {}; - allAxis.forEach((axis, axisIndex) => { + allAxis.forEach((eachAxis, axisIndex) => { + const axis = eachAxis as Readonly>>; const isDefaultAxis = axisIndex === 0; + const zoomOption = zoomOptions?.[axis.id]; + const zoom = zoomData?.find(({ axisId }) => axisId === axis.id); + const zoomRange: [number, number] = zoom ? [zoom.start, zoom.end] : [0, 100]; + const range = getRange(drawingArea, axisDirection, axis.reverse); + const [minData, maxData] = getAxisExtremum( axis, extremumGetters, isDefaultAxis, formattedSeries, + zoom === undefined && !zoomOption ? getFilters : undefined, // Do not apply filtering if zoom is already defined. ); - - const zoom = zoomData?.find(({ axisId }) => axisId === axis.id); - const zoomRange: [number, number] = zoom ? [zoom.start, zoom.end] : [0, 100]; - const range = getRange(drawingArea, axisDirection, axis.reverse); + const data = axis.data ?? []; if (isBandScaleConfig(axis)) { const categoryGapRatio = axis.categoryGapRatio ?? DEFAULT_CATEGORY_GAP_RATIO; const barGapRatio = axis.barGapRatio ?? DEFAULT_BAR_GAP_RATIO; // Reverse range because ordinal scales are presented from top to bottom on y-axis const scaleRange = axisDirection === 'x' ? range : [range[1], range[0]]; - const zoomedRange = zoomedScaleRange(scaleRange, zoomRange); + const zoomedRange = zoomScaleRange(scaleRange, zoomRange); completeAxis[axis.id] = { categoryGapRatio, barGapRatio, ...axis, + data, scale: scaleBand(axis.data!, zoomedRange) .paddingInner(categoryGapRatio) .paddingOuter(categoryGapRatio / 2), @@ -140,10 +134,11 @@ export function computeValue({ } if (isPointScaleConfig(axis)) { const scaleRange = axisDirection === 'x' ? range : [...range].reverse(); - const zoomedRange = zoomedScaleRange(scaleRange, zoomRange); + const zoomedRange = zoomScaleRange(scaleRange, zoomRange); completeAxis[axis.id] = { ...axis, + data, scale: scalePoint(axis.data!, zoomedRange), tickNumber: axis.data!.length, colorScale: @@ -158,6 +153,7 @@ export function computeValue({ 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`. return; @@ -165,26 +161,26 @@ export function computeValue({ const scaleType = axis.scaleType ?? ('linear' as const); - const extremums = [axis.min ?? minData, axis.max ?? maxData]; - const rawTickNumber = getTickNumber({ ...axis, range, domain: extremums }); + const axisExtremums = [axis.min ?? minData, axis.max ?? maxData]; + const rawTickNumber = getTickNumber({ ...axis, range, domain: axisExtremums }); const tickNumber = rawTickNumber / ((zoomRange[1] - zoomRange[0]) / 100); - const zoomedRange = zoomedScaleRange(range, zoomRange); + const zoomedRange = zoomScaleRange(range, zoomRange); // TODO: move nice to prop? Disable when there is zoom? - const scale = getScale(scaleType, extremums, zoomedRange).nice(rawTickNumber); + const scale = getScale(scaleType, axisExtremums, zoomedRange).nice(rawTickNumber); const [minDomain, maxDomain] = scale.domain(); const domain = [axis.min ?? minDomain, axis.max ?? maxDomain]; completeAxis[axis.id] = { ...axis, + data, scaleType: scaleType as any, scale: scale.domain(domain) as any, tickNumber, colorScale: axis.colorMap && getColorScale(axis.colorMap), }; }); - return { axis: completeAxis, axisIds: allAxis.map(({ id }) => id), diff --git a/packages/x-charts/src/context/CartesianProvider/getAxisExtremum.ts b/packages/x-charts/src/context/CartesianProvider/getAxisExtremum.ts index be7839633c477..eab86f0ee0dba 100644 --- a/packages/x-charts/src/context/CartesianProvider/getAxisExtremum.ts +++ b/packages/x-charts/src/context/CartesianProvider/getAxisExtremum.ts @@ -2,6 +2,7 @@ import { AxisConfig } from '../../models'; import { CartesianChartSeriesType } from '../../models/seriesType/config'; import { FormattedSeries } from '../SeriesProvider'; import { ExtremumGettersConfig, ExtremumGetterResult } from '../PluginProvider'; +import { GetZoomAxisFilters } from './Cartesian.types'; const axisExtremumCallback = ( acc: ExtremumGetterResult, @@ -10,6 +11,7 @@ const axisExtremumCallback = ( getters: ExtremumGettersConfig, isDefaultAxis: boolean, formattedSeries: FormattedSeries, + getFilters?: GetZoomAxisFilters, ): ExtremumGetterResult => { const getter = getters[chartType]; const series = formattedSeries[chartType]?.series ?? {}; @@ -18,18 +20,11 @@ const axisExtremumCallback = ( series, axis, isDefaultAxis, - }) ?? [null, null]; + getFilters, + }) ?? [Infinity, -Infinity]; const [minData, maxData] = acc; - if (minData === null || maxData === null) { - return [minChartTypeData!, maxChartTypeData!]; - } - - if (minChartTypeData === null || maxChartTypeData === null) { - return [minData, maxData]; - } - return [Math.min(minChartTypeData, minData), Math.max(maxChartTypeData, maxData)]; }; @@ -38,12 +33,27 @@ export const getAxisExtremum = ( getters: ExtremumGettersConfig, isDefaultAxis: boolean, formattedSeries: FormattedSeries, + getFilters?: GetZoomAxisFilters, ) => { const charTypes = Object.keys(getters) as CartesianChartSeriesType[]; - return charTypes.reduce( + const extremums = charTypes.reduce( (acc, charType) => - axisExtremumCallback(acc, charType, axis, getters, isDefaultAxis, formattedSeries), - [null, null], + axisExtremumCallback( + acc, + charType, + axis, + getters, + isDefaultAxis, + formattedSeries, + getFilters, + ), + [Infinity, -Infinity], ); + + if (Number.isNaN(extremums[0]) || Number.isNaN(extremums[1])) { + return [Infinity, -Infinity]; + } + + return extremums; }; diff --git a/packages/x-charts/src/context/CartesianProvider/index.ts b/packages/x-charts/src/context/CartesianProvider/index.ts index a1d4875fa7521..e17cfb004cd88 100644 --- a/packages/x-charts/src/context/CartesianProvider/index.ts +++ b/packages/x-charts/src/context/CartesianProvider/index.ts @@ -3,6 +3,7 @@ import { computeValue } from './computeValue'; export * from './CartesianProvider'; export * from './CartesianContext'; export * from './useCartesianContext'; +export * from './Cartesian.types'; const cartesianProviderUtils = { computeValue, diff --git a/packages/x-charts/src/context/CartesianProvider/normalizeAxis.ts b/packages/x-charts/src/context/CartesianProvider/normalizeAxis.ts deleted file mode 100644 index 89f4eefc9518a..0000000000000 --- a/packages/x-charts/src/context/CartesianProvider/normalizeAxis.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { AxisConfig, ScaleName } from '../../models'; -import { ChartsAxisProps } from '../../models/axis'; -import { MakeOptional } from '../../models/helpers'; -import { DatasetType } from '../../models/seriesType/config'; - -export const normalizeAxis = < - T extends ChartsAxisProps, - R extends MakeOptional, 'id'>, ->( - axis: R[] | undefined, - dataset: DatasetType | undefined, - axisName: 'x' | 'y', -): R[] => { - return ( - axis?.map((axisConfig) => { - const dataKey = axisConfig.dataKey; - if (dataKey === undefined || axisConfig.data !== undefined) { - return axisConfig; - } - if (dataset === undefined) { - throw Error(`MUI X: ${axisName}-axis uses \`dataKey\` but no \`dataset\` is provided.`); - } - return { - ...axisConfig, - data: dataset.map((d) => d[dataKey]), - }; - }) ?? [] - ); -}; diff --git a/packages/x-charts/src/context/CartesianProvider/useCartesianContext.ts b/packages/x-charts/src/context/CartesianProvider/useCartesianContext.ts index ee53769587459..17a08ba8d457a 100644 --- a/packages/x-charts/src/context/CartesianProvider/useCartesianContext.ts +++ b/packages/x-charts/src/context/CartesianProvider/useCartesianContext.ts @@ -1,5 +1,6 @@ import * as React from 'react'; -import { CartesianContext, CartesianContextState } from './CartesianContext'; +import { CartesianContext } from './CartesianContext'; +import { CartesianContextState } from './Cartesian.types'; export const useCartesianContext = (): CartesianContextState => { const { data } = React.useContext(CartesianContext); diff --git a/packages/x-charts/src/context/CartesianProvider/zoom.ts b/packages/x-charts/src/context/CartesianProvider/zoom.ts new file mode 100644 index 0000000000000..57ce8e20a4c8d --- /dev/null +++ b/packages/x-charts/src/context/CartesianProvider/zoom.ts @@ -0,0 +1,23 @@ +/** + * Applies the zoom into the scale range. + * It changes the screen coordinates that the scale covers. + * Not the data that is displayed. + * + * @param scaleRange the original range in real screen coordinates. + * @param zoomRange the zoom range in percentage. + * @returns zoomed range in real screen coordinates. + */ +export const zoomScaleRange = ( + scaleRange: [number, number] | number[], + zoomRange: [number, 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]; +}; diff --git a/packages/x-charts/src/context/DrawingProvider.tsx b/packages/x-charts/src/context/DrawingProvider.tsx index 26dee3c5a4147..cf851ba3b3505 100644 --- a/packages/x-charts/src/context/DrawingProvider.tsx +++ b/packages/x-charts/src/context/DrawingProvider.tsx @@ -42,10 +42,18 @@ export type DrawingArea = { * @param {Object} point The point to check. * @param {number} point.x The x coordinate of the point. * @param {number} point.y The y coordinate of the point. - * @param {Element} targetElement The target element if relevant. + * @param {Object} options The options of the check. + * @param {Element} [options.targetElement] The element to check if it is allowed to overflow the drawing area. + * @param {'x' | 'y'} [options.direction] The direction to check. * @returns {boolean} `true` if the point is inside the drawing area, `false` otherwise. */ - isPointInside: (point: { x: number; y: number }, targetElement?: Element) => boolean; + isPointInside: ( + point: { x: number; y: number }, + options?: { + targetElement?: Element; + direction?: 'x' | 'y'; + }, + ) => boolean; }; export const DrawingContext = React.createContext< @@ -87,17 +95,24 @@ export function DrawingProvider(props: DrawingProviderProps) { const chartId = useId(); const isPointInside = React.useCallback( - ({ x, y }, targetElement) => { + ({ x, y }, options) => { // For element allowed to overflow, wrapping them in make them fully part of the drawing area. - if (targetElement && targetElement.closest('[data-drawing-container]')) { + if (options?.targetElement && options?.targetElement.closest('[data-drawing-container]')) { return true; } - return ( - x >= drawingArea.left && - x <= drawingArea.left + drawingArea.width && - y >= drawingArea.top && - y <= drawingArea.top + drawingArea.height - ); + + const isInsideX = x >= drawingArea.left - 1 && x <= drawingArea.left + drawingArea.width; + const isInsideY = y >= drawingArea.top - 1 && y <= drawingArea.top + drawingArea.height; + + if (options?.direction === 'x') { + return isInsideX; + } + + if (options?.direction === 'y') { + return isInsideY; + } + + return isInsideX && isInsideY; }, [drawingArea], ); diff --git a/packages/x-charts/src/context/HighlightedProvider/useHighlighted.test.tsx b/packages/x-charts/src/context/HighlightedProvider/useHighlighted.test.tsx index ac69895cf5e74..29337e8d7191e 100644 --- a/packages/x-charts/src/context/HighlightedProvider/useHighlighted.test.tsx +++ b/packages/x-charts/src/context/HighlightedProvider/useHighlighted.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { ErrorBoundary, createRenderer } from '@mui/internal-test-utils'; +import { ErrorBoundary, createRenderer, screen } from '@mui/internal-test-utils'; import { useHighlighted } from './useHighlighted'; import { HighlightedProvider } from './HighlightedProvider'; import { SeriesProvider } from '../SeriesProvider'; @@ -42,7 +42,7 @@ describe('useHighlighted', () => { }); it('should not throw an error when parent context is present', () => { - const { getByText } = render( + render( @@ -52,6 +52,6 @@ describe('useHighlighted', () => { , ); - expect(getByText('test-id')).toBeVisible(); + expect(screen.getByText('test-id')).toBeVisible(); }); }); diff --git a/packages/x-charts/src/context/InteractionProvider.tsx b/packages/x-charts/src/context/InteractionProvider.tsx index 2c26dc32aea9d..35cee12e71e63 100644 --- a/packages/x-charts/src/context/InteractionProvider.tsx +++ b/packages/x-charts/src/context/InteractionProvider.tsx @@ -10,11 +10,13 @@ export type ItemInteractionData = ChartItemIdentifier export type AxisInteractionData = { x: null | { value: number | Date | string; - index?: number; + // Set to -1 if no index. + index: number; }; y: null | { value: number | Date | string; - index?: number; + // Set to -1 if no index. + index: number; }; }; diff --git a/packages/x-charts/src/context/PluginProvider/ExtremumGetter.types.ts b/packages/x-charts/src/context/PluginProvider/ExtremumGetter.types.ts index a1cda8d39331d..cdbf8e486c422 100644 --- a/packages/x-charts/src/context/PluginProvider/ExtremumGetter.types.ts +++ b/packages/x-charts/src/context/PluginProvider/ExtremumGetter.types.ts @@ -3,7 +3,7 @@ import type { ChartSeries, ChartSeriesType, } from '../../models/seriesType/config'; -import type { AxisConfig } from '../../models/axis'; +import type { AxisConfig, AxisId } from '../../models/axis'; import type { SeriesId } from '../../models/seriesType/common'; export type ExtremumGettersConfig = { @@ -14,10 +14,21 @@ type ExtremumGetterParams = { series: Record>; axis: AxisConfig; isDefaultAxis: boolean; + getFilters?: (params: { + currentAxisId: AxisId | undefined; + seriesXAxisId?: AxisId; + seriesYAxisId?: AxisId; + isDefaultAxis: boolean; + }) => ExtremumFilter; }; -export type ExtremumGetterResult = [number, number] | [null, null]; +export type ExtremumGetterResult = [number, number]; export type ExtremumGetter = ( params: ExtremumGetterParams, ) => ExtremumGetterResult; + +export type ExtremumFilter = ( + value: { x: number | Date | string | null; y: number | Date | string | null }, + dataIndex: number, +) => boolean; diff --git a/packages/x-charts/src/hooks/useAxisEvents.ts b/packages/x-charts/src/hooks/useAxisEvents.ts index f20d26f76dcc3..789f9ea45d54c 100644 --- a/packages/x-charts/src/hooks/useAxisEvents.ts +++ b/packages/x-charts/src/hooks/useAxisEvents.ts @@ -39,7 +39,7 @@ export const useAxisEvents = (disableAxisListener: boolean) => { const value = scale.invert(mouseValue); if (axisData === undefined) { - return { value }; + return { value, index: -1 }; } const valueAsNumber = getAsANumber(value); @@ -109,7 +109,7 @@ export const useAxisEvents = (disableAxisListener: boolean) => { mousePosition.current.x = svgPoint.x; mousePosition.current.y = svgPoint.y; - if (!drawingArea.isPointInside(svgPoint, event.target as SVGElement)) { + if (!drawingArea.isPointInside(svgPoint, { targetElement: event.target as SVGElement })) { if (mousePosition.current.isInChart) { dispatch({ type: 'exitChart' }); mousePosition.current.isInChart = false; diff --git a/packages/x-charts/src/hooks/useReducedMotion.ts b/packages/x-charts/src/hooks/useReducedMotion.ts index b95f28d4a9da5..724e2da21fbed 100644 --- a/packages/x-charts/src/hooks/useReducedMotion.ts +++ b/packages/x-charts/src/hooks/useReducedMotion.ts @@ -1,5 +1,12 @@ import { useIsomorphicLayoutEffect, Globals } from '@react-spring/web'; +const handleMediaChange = (e: { matches: boolean | undefined }) => { + Globals.assign({ + // Modification such the react-spring implementation such that this hook can remove animation but never activate animation. + skipAnimation: e.matches || undefined, + }); +}; + /** * Returns `boolean` or `null`, used to automatically * set skipAnimations to the value of the user's @@ -8,24 +15,18 @@ import { useIsomorphicLayoutEffect, Globals } from '@react-spring/web'; * The return value, post-effect, is the value of their preferred setting */ export const useReducedMotion = () => { - // Taken from: https://github.com/pmndrs/react-spring/blob/02ec877bbfab0df46da0e4a47d5f68d3e731206a/packages/shared/src/hooks/useReducedMotion.ts#L13 + // Taken from: https://github.com/pmndrs/react-spring/blob/fd65b605b85c3a24143c4ce9dd322fdfca9c66be/packages/shared/src/hooks/useReducedMotion.ts useIsomorphicLayoutEffect(() => { - if (!window.matchMedia) { - // skip animation in environments where `window.matchMedia` would not be available (i.e. test/jsdom) - Globals.assign({ - skipAnimation: true, - }); - return () => {}; + // Skip animation test/jsdom + const shouldSkipAnimation = !window?.matchMedia; + + if (shouldSkipAnimation) { + handleMediaChange({ matches: true }); + return undefined; } - const mql = window.matchMedia('(prefers-reduced-motion)'); - const handleMediaChange = (event: MediaQueryListEvent | MediaQueryList) => { - Globals.assign({ - // Modification such the react-spring implementation such that this hook can remove animation but never activate animation. - skipAnimation: event.matches || undefined, - }); - }; + const mql = window.matchMedia('(prefers-reduced-motion)'); handleMediaChange(mql); diff --git a/packages/x-charts/src/hooks/useSeries.test.tsx b/packages/x-charts/src/hooks/useSeries.test.tsx index 7a8d6f15a8f8d..4bbe16ff5cb63 100644 --- a/packages/x-charts/src/hooks/useSeries.test.tsx +++ b/packages/x-charts/src/hooks/useSeries.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { ErrorBoundary, createRenderer } from '@mui/internal-test-utils'; +import { ErrorBoundary, createRenderer, screen } from '@mui/internal-test-utils'; import { useSeries } from './useSeries'; import { SeriesProvider } from '../context/SeriesProvider'; import { PluginProvider } from '../internals'; @@ -41,7 +41,7 @@ describe('useSeries', () => { }); it('should not throw an error when parent context is present', () => { - const { getByText } = render( + render( @@ -49,6 +49,6 @@ describe('useSeries', () => { , ); - expect(getByText('test-id')).toBeVisible(); + expect(screen.getByText('test-id')).toBeVisible(); }); }); diff --git a/packages/x-charts/src/hooks/useSvgRef.test.tsx b/packages/x-charts/src/hooks/useSvgRef.test.tsx index a61113fa1e8d0..ed345538cc148 100644 --- a/packages/x-charts/src/hooks/useSvgRef.test.tsx +++ b/packages/x-charts/src/hooks/useSvgRef.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { ErrorBoundary, createRenderer } from '@mui/internal-test-utils'; +import { ErrorBoundary, createRenderer, screen } from '@mui/internal-test-utils'; import { useSvgRef } from './useSvgRef'; import { DrawingProvider } from '../context/DrawingProvider'; @@ -52,11 +52,11 @@ describe('useSvgRef', () => { ); } - const { findByText, forceUpdate } = render(); + const { forceUpdate } = render(); // Ref is not available on first render. forceUpdate(); - expect(await findByText('test-id')).toBeVisible(); + expect(await screen.findByText('test-id')).toBeVisible(); }); }); diff --git a/packages/x-charts/src/hooks/useTicks.ts b/packages/x-charts/src/hooks/useTicks.ts index 4b41fe74eeab3..3cc08f9a80c6c 100644 --- a/packages/x-charts/src/hooks/useTicks.ts +++ b/packages/x-charts/src/hooks/useTicks.ts @@ -1,6 +1,7 @@ import * as React from 'react'; import { AxisConfig, D3Scale } from '../models/axis'; import { isBandScale } from '../internals/isBandScale'; +import { isInfinity } from '../internals/isInfinity'; export interface TickParams { /** @@ -145,8 +146,10 @@ export function useTicks( })); } - if (scale.domain().length === 0 || scale.domain()[0] === scale.domain()[1]) { - // The axis should not be visible, so ticks should also be hidden. + const domain = scale.domain(); + // Skip axis rendering if no data is available + // - The domains contains Infinity for continuous scales. + if (domain.some(isInfinity)) { return []; } diff --git a/packages/x-charts/src/internals/domUtils.ts b/packages/x-charts/src/internals/domUtils.ts index d7aca59b459f0..e8a1b2e2d119c 100644 --- a/packages/x-charts/src/internals/domUtils.ts +++ b/packages/x-charts/src/internals/domUtils.ts @@ -45,7 +45,7 @@ const STYLE_LIST = [ 'marginTop', 'marginBottom', ]; -const MEASUREMENT_SPAN_ID = 'mui_measurement_span'; +export const MEASUREMENT_SPAN_ID = 'mui_measurement_span'; /** * @@ -97,6 +97,7 @@ export const getStyleString = (style: React.CSSProperties) => '', ); +let domCleanTimeout: NodeJS.Timeout | undefined; /** * * @param text The string to estimate @@ -134,7 +135,6 @@ export const getStringSize = (text: string | number, style: React.CSSProperties return styleKey; }); measurementSpan.textContent = str; - const rect = measurementSpan.getBoundingClientRect(); const result = { width: rect.width, height: rect.height }; @@ -147,8 +147,22 @@ export const getStringSize = (text: string | number, style: React.CSSProperties stringCache.cacheCount += 1; } + if (domCleanTimeout) { + clearTimeout(domCleanTimeout); + } + domCleanTimeout = setTimeout(() => { + // Limit node cleaning to once per render cycle + measurementSpan.textContent = ''; + }, 0); + return result; } catch (e) { return { width: 0, height: 0 }; } }; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export function unstable_cleanupDOM() { + // const measurementSpan = document.getElementById(MEASUREMENT_SPAN_ID); + // measurementSpan?.remove(); +} diff --git a/packages/x-charts/src/internals/index.ts b/packages/x-charts/src/internals/index.ts index f4020f5d09189..e3eca45c3d567 100644 --- a/packages/x-charts/src/internals/index.ts +++ b/packages/x-charts/src/internals/index.ts @@ -21,6 +21,8 @@ export * from './configInit'; export * from './getLabel'; export * from './getSVGPoint'; export * from './isDefined'; +export { unstable_cleanupDOM } from './domUtils'; +export * from './getScale'; // contexts @@ -31,6 +33,7 @@ export * from '../context/SeriesProvider'; export * from '../context/ZAxisContextProvider'; export * from '../context/PluginProvider'; export type * from '../context/context.types'; +export { getAxisExtremum } from '../context/CartesianProvider/getAxisExtremum'; // series configuration export * from '../models/seriesType/config'; diff --git a/packages/x-charts/src/internals/isInfinity.ts b/packages/x-charts/src/internals/isInfinity.ts new file mode 100644 index 0000000000000..d09834c55e40e --- /dev/null +++ b/packages/x-charts/src/internals/isInfinity.ts @@ -0,0 +1,3 @@ +export function isInfinity(v: any): v is number { + return typeof v === 'number' && !Number.isFinite(v); +} diff --git a/packages/x-charts/src/internals/useAnimatedPath.ts b/packages/x-charts/src/internals/useAnimatedPath.ts deleted file mode 100644 index 9f29447fe138c..0000000000000 --- a/packages/x-charts/src/internals/useAnimatedPath.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from 'react'; -import { interpolateString } from '@mui/x-charts-vendor/d3-interpolate'; -import { useSpring, to } from '@react-spring/web'; - -function usePrevious(value: T) { - const ref = React.useRef(null); - React.useEffect(() => { - ref.current = value; - }, [value]); - return ref.current; -} - -// Taken from Nivo -export const useAnimatedPath = (path: string, skipAnimation?: boolean) => { - const previousPath = usePrevious(path); - const interpolator = React.useMemo( - () => (previousPath ? interpolateString(previousPath, path) : () => path), - [previousPath, path], - ); - - const { value } = useSpring({ - from: { value: 0 }, - to: { value: 1 }, - reset: true, - immediate: skipAnimation, - }); - - return to([value], interpolator); -}; diff --git a/packages/x-charts/src/internals/useIsRTL.ts b/packages/x-charts/src/internals/useIsRTL.ts deleted file mode 100644 index 21e7f0fe692d6..0000000000000 --- a/packages/x-charts/src/internals/useIsRTL.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { useTheme } from '@mui/material/styles'; - -export const useIsRTL = () => { - const theme = useTheme(); - return theme.direction === 'rtl'; -}; diff --git a/packages/x-charts/src/internals/useStringInterpolator.ts b/packages/x-charts/src/internals/useStringInterpolator.ts new file mode 100644 index 0000000000000..2b0faa3cd851b --- /dev/null +++ b/packages/x-charts/src/internals/useStringInterpolator.ts @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { interpolateString } from '@mui/x-charts-vendor/d3-interpolate'; + +function usePrevious(value: T) { + const ref = React.useRef<{ currentPath: T; previousPath?: T }>({ + currentPath: value, + previousPath: undefined, + }); + if (ref.current.currentPath !== value) { + ref.current = { + currentPath: value, + previousPath: ref.current.currentPath, + }; + } + + return ref.current; +} + +export const useStringInterpolator = (path: string) => { + const memoryRef = usePrevious(path); + + const interpolator = React.useMemo( + () => + memoryRef.previousPath + ? interpolateString(memoryRef.previousPath, memoryRef.currentPath) + : () => memoryRef.currentPath, + [memoryRef.currentPath, memoryRef.previousPath], + ); + + return interpolator; +}; diff --git a/packages/x-charts/src/models/axis.ts b/packages/x-charts/src/models/axis.ts index 28d946f43b3c2..2d2f49ed71e13 100644 --- a/packages/x-charts/src/models/axis.ts +++ b/packages/x-charts/src/models/axis.ts @@ -9,6 +9,7 @@ import type { ScaleSequential, ScaleThreshold, } from '@mui/x-charts-vendor/d3-scale'; +import { SxProps } from '@mui/system'; import { ChartsAxisClasses } from '../ChartsAxis/axisClasses'; import type { TickParams } from '../hooks/useTicks'; import { ChartsTextProps } from '../ChartsText'; @@ -140,6 +141,7 @@ export interface ChartsAxisProps extends TickParams { * @default {} */ slotProps?: Partial; + sx?: SxProps; } export interface ChartsYAxisProps extends ChartsAxisProps { @@ -214,6 +216,10 @@ export interface AxisScaleConfig { }; } +/** + * Use this type instead of `AxisScaleConfig` when the values + * shouldn't be provided by the user. + */ export interface AxisScaleComputedConfig { band: { colorScale?: @@ -252,6 +258,7 @@ export interface AxisScaleComputedConfig { colorScale?: ScaleSequential | ScaleThreshold; }; } + export type AxisValueFormatterContext = { /** * Location indicates where the value will be displayed. diff --git a/packages/x-charts/src/models/seriesType/line.ts b/packages/x-charts/src/models/seriesType/line.ts index 5e4a6f481da9b..744749d66833b 100644 --- a/packages/x-charts/src/models/seriesType/line.ts +++ b/packages/x-charts/src/models/seriesType/line.ts @@ -82,6 +82,16 @@ export interface LineSeriesType * @default 'none' */ stackOffset?: StackOffsetType; + /** + * The value of the line at the base of the series area. + * + * - `'min'` the area will fill the space **under** the line. + * - `'max'` the area will fill the space **above** the line. + * - `number` the area will fill the space between this value and the line + * + * @default 0 + */ + baseline?: number | 'min' | 'max'; } /** diff --git a/packages/x-charts/src/tests/firePointerEvent.ts b/packages/x-charts/src/tests/firePointerEvent.ts new file mode 100644 index 0000000000000..ae639a28fcfcb --- /dev/null +++ b/packages/x-charts/src/tests/firePointerEvent.ts @@ -0,0 +1,41 @@ +import { fireEvent } from '@mui/internal-test-utils'; + +export function firePointerEvent( + target: Element, + type: 'pointerstart' | 'pointermove' | 'pointerend', + options: Pick, +): void { + const originalGetBoundingClientRect = target.getBoundingClientRect; + target.getBoundingClientRect = () => ({ + x: 0, + y: 0, + bottom: 0, + height: 0, + left: 0, + right: 0, + top: 0, + width: 0, + toJSON() { + return { + x: 0, + y: 0, + bottom: 0, + height: 0, + left: 0, + right: 0, + top: 0, + width: 0, + }; + }, + }); + const event = new window.PointerEvent(type, { + bubbles: true, + cancelable: true, + composed: true, + isPrimary: true, + ...options, + }); + + fireEvent(target, event); + target.getBoundingClientRect = originalGetBoundingClientRect; +} diff --git a/packages/x-charts/src/tests/materialVersion.test.tsx b/packages/x-charts/src/tests/materialVersion.test.tsx new file mode 100644 index 0000000000000..dc0c0a7e6ee01 --- /dev/null +++ b/packages/x-charts/src/tests/materialVersion.test.tsx @@ -0,0 +1,5 @@ +import materialPackageJson from '@mui/material/package.json'; +import { checkMaterialVersion } from 'test/utils/checkMaterialVersion'; +import packageJson from '../../package.json'; + +checkMaterialVersion({ packageJson, materialPackageJson }); diff --git a/packages/x-charts/src/themeAugmentation/components.d.ts b/packages/x-charts/src/themeAugmentation/components.d.ts index f08071da49ac3..3302ca499b7b4 100644 --- a/packages/x-charts/src/themeAugmentation/components.d.ts +++ b/packages/x-charts/src/themeAugmentation/components.d.ts @@ -2,22 +2,19 @@ import { ComponentsProps, ComponentsOverrides } from '@mui/material/styles'; export interface ChartsComponents { MuiChartsAxis?: { - defaultProps?: ComponentsProps['MuiChartsAxis']; styleOverrides?: ComponentsOverrides['MuiChartsAxis']; }; MuiChartsXAxis?: { defaultProps?: ComponentsProps['MuiChartsXAxis']; + styleOverrides?: ComponentsOverrides['MuiChartsXAxis']; }; MuiChartsYAxis?: { defaultProps?: ComponentsProps['MuiChartsYAxis']; + styleOverrides?: ComponentsOverrides['MuiChartsYAxis']; }; MuiChartsAxisHighlight?: { - defaultProps?: ComponentsProps['MuiChartsAxisHighlight']; styleOverrides?: ComponentsOverrides['MuiChartsAxisHighlight']; }; - MuiChartsClipPath?: { - defaultProps?: ComponentsProps['MuiChartsClipPath']; - }; MuiChartsGrid?: { defaultProps?: ComponentsProps['MuiChartsGrid']; styleOverrides?: ComponentsOverrides['MuiChartsGrid']; @@ -32,12 +29,12 @@ export interface ChartsComponents { }; MuiChartsSurface?: { defaultProps?: ComponentsProps['MuiChartsSurface']; + styleOverrides?: ComponentsOverrides['MuiChartsSurface']; }; MuiBarChart?: { defaultProps?: ComponentsProps['MuiBarChart']; }; MuiBarElement?: { - defaultProps?: ComponentsProps['MuiBarElement']; styleOverrides?: ComponentsOverrides['MuiBarElement']; }; MuiBarLabel?: { @@ -48,23 +45,18 @@ export interface ChartsComponents { defaultProps?: ComponentsProps['MuiLineChart']; }; MuiAreaElement?: { - defaultProps?: ComponentsProps['MuiAreaElement']; styleOverrides?: ComponentsOverrides['MuiAreaElement']; }; MuiLineElement?: { - defaultProps?: ComponentsProps['MuiLineElement']; styleOverrides?: ComponentsOverrides['MuiLineElement']; }; MuiMarkElement?: { - defaultProps?: ComponentsProps['MuiMarkElement']; styleOverrides?: ComponentsOverrides['MuiMarkElement']; }; MuiScatterChart?: { defaultProps?: ComponentsProps['MuiScatterChart']; }; - MuiScatter?: { - defaultProps?: ComponentsProps['MuiScatter']; - }; + MuiScatter?: {}; } declare module '@mui/material/styles' { diff --git a/packages/x-charts/src/themeAugmentation/index.ts b/packages/x-charts/src/themeAugmentation/index.ts index 492f27d8f9176..d387cd19110ff 100644 --- a/packages/x-charts/src/themeAugmentation/index.ts +++ b/packages/x-charts/src/themeAugmentation/index.ts @@ -1,3 +1,3 @@ -export * from './overrides'; -export * from './props'; -export * from './components'; +export type * from './overrides'; +export type * from './props'; +export type * from './components'; diff --git a/packages/x-charts/src/themeAugmentation/overrides.d.ts b/packages/x-charts/src/themeAugmentation/overrides.d.ts index c2699e9f67d0a..8049913a70bea 100644 --- a/packages/x-charts/src/themeAugmentation/overrides.d.ts +++ b/packages/x-charts/src/themeAugmentation/overrides.d.ts @@ -1,35 +1,36 @@ import { BarLabelClassKey } from '../BarChart'; import { BarElementClassKey } from '../BarChart/BarElement'; -import { ChartsAxisClassKey } from '../ChartsAxis'; import { ChartsAxisHighlightClassKey } from '../ChartsAxisHighlight'; import { ChartsGridClassKey } from '../ChartsGrid'; -import { ChartsLegendClassKey } from '../ChartsLegend'; import { ChartsTooltipClassKey } from '../ChartsTooltip'; import { AreaElementClassKey, LineElementClassKey, MarkElementClassKey } from '../LineChart'; -// prettier-ignore -export interface PickersComponentNameToClassKey { - MuiChartsAxis: ChartsAxisClassKey; +export interface ChartsComponentNameToClassKey { + MuiChartsAxis: 'root'; // Only the root component of axes is styled. We should probably remove this one in v8 + MuiChartsXAxis: 'root'; // Only the root component of axes is styled + MuiChartsYAxis: 'root'; // Only the root component of axes is styled + MuiChartsAxisHighlight: ChartsAxisHighlightClassKey; + MuiChartsLegend: 'root'; MuiChartsGrid: ChartsGridClassKey; - MuiChartsLegend: ChartsLegendClassKey; MuiChartsTooltip: ChartsTooltipClassKey; + MuiChartsSurface: 'root'; + // BarChart components MuiBarElement: BarElementClassKey; MuiBarLabel: BarLabelClassKey; // LineChart components - MuiAreaElement: AreaElementClassKey; MuiLineElement: LineElementClassKey; MuiMarkElement: MarkElementClassKey; + // ScatterChart components - } declare module '@mui/material/styles' { - interface ComponentNameToClassKey extends PickersComponentNameToClassKey {} + interface ComponentNameToClassKey extends ChartsComponentNameToClassKey {} } // disable automatic export diff --git a/packages/x-charts/src/themeAugmentation/props.d.ts b/packages/x-charts/src/themeAugmentation/props.d.ts index 7dc62f3974005..0fe35a41d87db 100644 --- a/packages/x-charts/src/themeAugmentation/props.d.ts +++ b/packages/x-charts/src/themeAugmentation/props.d.ts @@ -1,26 +1,18 @@ import { BarLabelProps } from '../BarChart/BarLabel'; import { BarChartProps } from '../BarChart/BarChart'; -import { BarElementProps } from '../BarChart/BarElement'; -import { ChartsAxisProps } from '../ChartsAxis'; -import { ChartsAxisHighlightProps } from '../ChartsAxisHighlight'; -import { ChartsClipPathProps } from '../ChartsClipPath'; import { ChartsGridProps } from '../ChartsGrid'; import { ChartsLegendProps } from '../ChartsLegend'; import { ChartsSurfaceProps } from '../ChartsSurface'; import { ChartsTooltipProps } from '../ChartsTooltip'; -import { AreaElementProps, LineElementProps, MarkElementProps } from '../LineChart'; import { LineChartProps } from '../LineChart/LineChart'; -import { ScatterProps } from '../ScatterChart/Scatter'; import { ScatterChartProps } from '../ScatterChart/ScatterChart'; +import { PieChartProps } from '../PieChart/PieChart'; import { ChartsXAxisProps, ChartsYAxisProps } from '../models/axis'; import { ChartSeriesType } from '../models/seriesType/config'; export interface ChartsComponentsPropsList { - MuiChartsAxis: ChartsAxisProps; MuiChartsXAxis: ChartsXAxisProps; MuiChartsYAxis: ChartsYAxisProps; - MuiChartsAxisHighlight: ChartsAxisHighlightProps; - MuiChartsClipPath: ChartsClipPathProps; MuiChartsGrid: ChartsGridProps; MuiChartsLegend: ChartsLegendProps; MuiChartsTooltip: ChartsTooltipProps; @@ -28,16 +20,13 @@ export interface ChartsComponentsPropsList { // BarChart components MuiBarChart: BarChartProps; - MuiBarElement: BarElementProps; MuiBarLabel: BarLabelProps; // LineChart components MuiLineChart: LineChartProps; - MuiAreaElement: AreaElementProps; - MuiLineElement: LineElementProps; - MuiMarkElement: MarkElementProps; // ScatterChart components MuiScatterChart: ScatterChartProps; - MuiScatter: ScatterProps; + // PieChart components + MuiPieChart: PieChartProps; } declare module '@mui/material/styles' { diff --git a/packages/x-charts/src/themeAugmentation/themeAugmentation.spec.ts b/packages/x-charts/src/themeAugmentation/themeAugmentation.spec.ts index 695a934ee6edb..8412c5c669cf7 100644 --- a/packages/x-charts/src/themeAugmentation/themeAugmentation.spec.ts +++ b/packages/x-charts/src/themeAugmentation/themeAugmentation.spec.ts @@ -3,15 +3,10 @@ import { createTheme } from '@mui/material/styles'; createTheme({ components: { MuiChartsAxis: { - defaultProps: { - leftAxis: 'test', - // @ts-expect-error invalid MuiChartsAxis prop - someRandomProp: true, - }, styleOverrides: { root: { backgroundColor: 'red' }, // @ts-expect-error invalid MuiChartsAxis class key - constent: { color: 'red' }, + line: { color: 'red' }, }, }, MuiChartsXAxis: { @@ -20,6 +15,11 @@ createTheme({ // @ts-expect-error invalid MuiChartsXAxis prop someRandomProp: true, }, + styleOverrides: { + root: { backgroundColor: 'red' }, + // @ts-expect-error invalid MuiChartsXAxis class key + line: { color: 'red' }, + }, }, MuiChartsYAxis: { defaultProps: { @@ -27,31 +27,19 @@ createTheme({ // @ts-expect-error invalid MuiChartsYAxis prop someRandomProp: true, }, + styleOverrides: { + root: { backgroundColor: 'red' }, + // @ts-expect-error invalid MuiChartsYAxis class key + line: { color: 'red' }, + }, }, MuiChartsAxisHighlight: { - defaultProps: { - x: 'line', - // @ts-expect-error invalid MuiChartsAxisHighlight prop - someRandomProp: true, - }, styleOverrides: { root: { backgroundColor: 'red' }, // @ts-expect-error invalid MuiChartsAxisHighlight class key constent: { color: 'red' }, }, }, - MuiChartsClipPath: { - defaultProps: { - id: 'test', - // @ts-expect-error invalid MuiChartsClipPath prop - someRandomProp: true, - }, - // styleOverrides: { - // root: { backgroundColor: 'red' }, - // // @ts-expect-error invalid MuiChartsClipPath class key - // constent: { color: 'red' }, - // }, - }, MuiChartsLegend: { defaultProps: { direction: 'row', @@ -61,7 +49,7 @@ createTheme({ styleOverrides: { root: { backgroundColor: 'red' }, // @ts-expect-error invalid MuiChartsLegend class key - constent: { color: 'red' }, + mark: { color: 'red' }, }, }, MuiChartsTooltip: { @@ -82,11 +70,11 @@ createTheme({ // @ts-expect-error invalid MuiChartsSurface prop someRandomProp: true, }, - // styleOverrides: { - // root: { backgroundColor: 'red' }, - // // @ts-expect-error invalid MuiChartsSurface class key - // constent: { color: 'red' }, - // }, + styleOverrides: { + root: { backgroundColor: 'red' }, + // @ts-expect-error invalid MuiChartsSurface class key + constent: { color: 'red' }, + }, }, // BarChart components @@ -103,11 +91,6 @@ createTheme({ // }, }, MuiBarElement: { - defaultProps: { - id: 'toto', - // @ts-expect-error invalid MuiBarElement prop - someRandomProp: true, - }, styleOverrides: { root: { backgroundColor: 'red' }, // @ts-expect-error invalid MuiBarElement class key @@ -128,11 +111,6 @@ createTheme({ // }, }, MuiAreaElement: { - defaultProps: { - id: 'toto', - // @ts-expect-error invalid MuiAreaElement prop - someRandomProp: true, - }, styleOverrides: { root: { backgroundColor: 'red' }, // @ts-expect-error invalid MuiAreaElement class key @@ -140,11 +118,6 @@ createTheme({ }, }, MuiLineElement: { - defaultProps: { - id: 'toto', - // @ts-expect-error invalid MuiLineElement prop - someRandomProp: true, - }, styleOverrides: { root: { backgroundColor: 'red' }, // @ts-expect-error invalid MuiLineElement class key @@ -152,11 +125,6 @@ createTheme({ }, }, MuiMarkElement: { - defaultProps: { - id: 'toto', - // @ts-expect-error invalid MuiMarkElement prop - someRandomProp: true, - }, styleOverrides: { root: { backgroundColor: 'red' }, // @ts-expect-error invalid MuiMarkElement class key @@ -176,17 +144,5 @@ createTheme({ // constent: { color: 'red' }, // }, }, - MuiScatter: { - defaultProps: { - markerSize: 10, - // @ts-expect-error invalid MuiScatter prop - someRandomProp: true, - }, - // styleOverrides: { - // root: { backgroundColor: 'red' }, - // // @ts-expect-error invalid MuiScatter class key - // constent: { color: 'red' }, - // }, - }, }, }); diff --git a/packages/x-codemod/package.json b/packages/x-codemod/package.json index 6bd85dc1f6d6b..aeec4871a4802 100644 --- a/packages/x-codemod/package.json +++ b/packages/x-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-codemod", - "version": "7.11.0", + "version": "7.15.0", "bin": "./codemod.js", "private": false, "author": "MUI Team", @@ -33,8 +33,8 @@ }, "dependencies": { "@babel/core": "^7.25.2", - "@babel/runtime": "^7.25.0", - "@babel/traverse": "^7.25.2", + "@babel/runtime": "^7.25.4", + "@babel/traverse": "^7.25.4", "jscodeshift": "0.16.1", "yargs": "^17.7.2" }, @@ -42,7 +42,7 @@ "@types/jscodeshift": "^0.11.11", "dayjs": "^1.11.11", "moment-timezone": "^0.5.45", - "rimraf": "^5.0.9" + "rimraf": "^5.0.10" }, "sideEffects": false, "publishConfig": { diff --git a/packages/x-data-grid-generator/package.json b/packages/x-data-grid-generator/package.json index 11da83bbec09d..e826b812f28b0 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.12.0", + "version": "7.15.0", "description": "Generate fake data for demo purposes only.", "author": "MUI Team", "main": "src/index.ts", @@ -33,21 +33,33 @@ "directory": "packages/x-data-grid-generator" }, "dependencies": { - "@babel/runtime": "^7.25.0", + "@babel/runtime": "^7.25.4", "@mui/x-data-grid-premium": "workspace:*", "chance": "^1.1.12", "clsx": "^2.1.1", "lru-cache": "^10.4.3" }, "devDependencies": { + "@mui/icons-material": "^5.16.7", + "@mui/material": "^5.16.7", "@types/chance": "^1.1.6", - "rimraf": "^5.0.9" + "rimraf": "^5.0.10" }, "peerDependencies": { - "@mui/icons-material": "^5.4.1", - "@mui/material": "^5.15.14", + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/icons-material": "^5.4.1 || ^6.0.0", + "@mui/material": "^5.15.14 || ^6.0.0", "react": "^17.0.0 || ^18.0.0" }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + }, "engines": { "node": ">=14.0.0" } diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index cf38b1fa3c852..7f16fc41c6472 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -50,7 +50,7 @@ function decodeParams(url: string): GridGetRowsParams { for (const [key, value] of array) { try { - decodedParams[key] = JSON.parse(decodeURIComponent(value)); + decodedParams[key] = JSON.parse(value); } catch (e) { decodedParams[key] = value; } diff --git a/packages/x-data-grid-generator/src/renderer/renderPnl.tsx b/packages/x-data-grid-generator/src/renderer/renderPnl.tsx index 40e8c01d7c355..8246e08241da3 100644 --- a/packages/x-data-grid-generator/src/renderer/renderPnl.tsx +++ b/packages/x-data-grid-generator/src/renderer/renderPnl.tsx @@ -7,11 +7,16 @@ const Value = styled('div')(({ theme }) => ({ width: '100%', fontVariantNumeric: 'tabular-nums', '&.positive': { - color: - theme.palette.mode === 'light' ? theme.palette.success.dark : theme.palette.success.light, + color: theme.palette.success.light, + ...theme.applyStyles('light', { + color: theme.palette.success.dark, + }), }, '&.negative': { - color: theme.palette.mode === 'light' ? theme.palette.error.dark : theme.palette.error.light, + color: theme.palette.error.light, + ...theme.applyStyles('light', { + color: theme.palette.error.dark, + }), }, })); diff --git a/packages/x-data-grid-premium/package.json b/packages/x-data-grid-premium/package.json index 357adba42c5d1..a372866fc33b3 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.12.0", + "version": "7.15.0", "description": "The Premium plan edition of the Data Grid Components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -43,9 +43,8 @@ "directory": "packages/x-data-grid-premium" }, "dependencies": { - "@babel/runtime": "^7.25.0", - "@mui/system": "^5.16.5", - "@mui/utils": "^5.16.5", + "@babel/runtime": "^7.25.4", + "@mui/utils": "^5.16.6", "@mui/x-data-grid": "workspace:*", "@mui/x-data-grid-pro": "workspace:*", "@mui/x-internals": "workspace:*", @@ -54,18 +53,31 @@ "clsx": "^2.1.1", "exceljs": "^4.4.0", "prop-types": "^15.8.1", - "reselect": "^4.1.8" + "reselect": "^5.1.0" }, "peerDependencies": { - "@mui/material": "^5.15.14", + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.5", + "@mui/internal-test-utils": "^1.0.11", + "@mui/material": "^5.16.7", + "@mui/system": "^5.16.7", "@types/prop-types": "^15.7.12", "date-fns": "^2.30.0", - "rimraf": "^5.0.9" + "rimraf": "^5.0.10" }, "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 13c08a00b7e19..459496e6f7455 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -23,9 +23,17 @@ import { } from '../models/dataGridPremiumProps'; import { useDataGridPremiumProps } from './useDataGridPremiumProps'; import { getReleaseInfo } from '../utils/releaseInfo'; +import { useGridAriaAttributes } from '../hooks/utils/useGridAriaAttributes'; +import { useGridRowAriaAttributes } from '../hooks/features/rows/useGridRowAriaAttributes'; export type { GridPremiumSlotsComponent as GridSlots } from '../models'; +const configuration = { + hooks: { + useGridAriaAttributes, + useGridRowAriaAttributes, + }, +}; const releaseInfo = getReleaseInfo(); let dataGridPremiumPropValidators: PropValidator[]; @@ -40,14 +48,13 @@ const DataGridPremiumRaw = React.forwardRef(function DataGridPremium + - `calc(var(--DataGrid-cellOffsetMultiplier) * ${theme.spacing(rowNode.depth)})`, - }} + sx={[ + rootProps.rowGroupingColumnMode === 'multiple' + ? { + ml: 1, + } + : (theme) => ({ + ml: `calc(var(--DataGrid-cellOffsetMultiplier) * var(--depth) * ${theme.spacing(1)})`, + }), + ]} + style={{ '--depth': rowNode.depth } as any} > {props.formattedValue ?? props.value}
diff --git a/packages/x-data-grid-premium/src/components/GridGroupingCriteriaCell.tsx b/packages/x-data-grid-premium/src/components/GridGroupingCriteriaCell.tsx index 0046dc3294a44..07a397c1a0b42 100644 --- a/packages/x-data-grid-premium/src/components/GridGroupingCriteriaCell.tsx +++ b/packages/x-data-grid-premium/src/components/GridGroupingCriteriaCell.tsx @@ -75,13 +75,16 @@ export function GridGroupingCriteriaCell(props: GridGroupingCriteriaCellProps) { return ( - `calc(var(--DataGrid-cellOffsetMultiplier) * ${theme.spacing(rowNode.depth)})`, - }} + sx={[ + rootProps.rowGroupingColumnMode === 'multiple' + ? { + ml: 0, + } + : (theme) => ({ + ml: `calc(var(--DataGrid-cellOffsetMultiplier) * var(--depth) * ${theme.spacing(1)})`, + }), + ]} + style={{ '--depth': rowNode.depth } as any} >
{filteredDescendantCount > 0 && ( diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/gridAggregationInterfaces.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/gridAggregationInterfaces.ts index 82ecbce2fc1c2..212bdef1a0083 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/gridAggregationInterfaces.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/gridAggregationInterfaces.ts @@ -74,7 +74,7 @@ export interface GridAggregationFunction { getCellValue?: (params: GridAggregationGetCellValueParams) => V; } -interface GridAggregationParams { +export interface GridAggregationParams { values: (V | undefined)[]; groupId: GridRowId; field: GridColDef['field']; diff --git a/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts b/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts index 5c4e3020ba616..d36c314e62923 100644 --- a/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts +++ b/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts @@ -146,7 +146,7 @@ export const useGridCellSelection = ( const rowsInRange = visibleRows.rows.slice(finalStartRowIndex, finalEndRowIndex + 1); const columnsInRange = visibleColumns.slice(finalStartColumnIndex, finalEndColumnIndex + 1); - const newModel = keepOtherSelected ? apiRef.current.getCellSelectionModel() : {}; + const newModel = keepOtherSelected ? { ...apiRef.current.getCellSelectionModel() } : {}; rowsInRange.forEach((row) => { if (!newModel[row.id]) { diff --git a/packages/x-data-grid-premium/src/hooks/features/clipboard/useGridClipboardImport.ts b/packages/x-data-grid-premium/src/hooks/features/clipboard/useGridClipboardImport.ts index 7dea10e4f8b26..8311264e1568d 100644 --- a/packages/x-data-grid-premium/src/hooks/features/clipboard/useGridClipboardImport.ts +++ b/packages/x-data-grid-premium/src/hooks/features/clipboard/useGridClipboardImport.ts @@ -209,11 +209,13 @@ function defaultPasteResolver({ apiRef, updateCell, pagination, + paginationMode, }: { pastedData: string[][]; apiRef: React.MutableRefObject; updateCell: CellValueUpdater['updateCell']; pagination: DataGridPremiumProcessedProps['pagination']; + paginationMode: DataGridPremiumProcessedProps['paginationMode']; }) { const isSingleValuePasted = pastedData.length === 1 && pastedData[0].length === 1; @@ -297,9 +299,10 @@ function defaultPasteResolver({ const selectedRowId = selectedCell.id; const selectedRowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(selectedRowId); - const visibleRowIds = pagination - ? gridPaginatedVisibleSortedGridRowIdsSelector(apiRef) - : gridExpandedSortedRowIdsSelector(apiRef); + const visibleRowIds = + pagination && paginationMode === 'client' + ? gridPaginatedVisibleSortedGridRowIdsSelector(apiRef) + : gridExpandedSortedRowIdsSelector(apiRef); const selectedFieldIndex = visibleColumnFields.indexOf(selectedCell.field); pastedData.forEach((rowData, index) => { @@ -342,7 +345,7 @@ export const useGridClipboardImport = ( const splitClipboardPastedText = props.splitClipboardPastedText; - const { pagination, onBeforeClipboardPasteStart } = props; + const { pagination, paginationMode, onBeforeClipboardPasteStart } = props; const handlePaste = React.useCallback>( async (params, event) => { @@ -403,6 +406,7 @@ export const useGridClipboardImport = ( cellUpdater.updateCell(...args); }, pagination, + paginationMode, }); cellUpdater.applyUpdates(); @@ -416,6 +420,7 @@ export const useGridClipboardImport = ( rootEl, splitClipboardPastedText, pagination, + paginationMode, onBeforeClipboardPasteStart, logger, ], diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts index 361c76b069c51..1cdec6a7c6631 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts @@ -83,6 +83,7 @@ export const filterRowTreeFromGroupingColumns = ( ): Omit => { const { apiRef, rowTree, isRowMatchingFilters, filterModel } = params; const filteredRowsLookup: Record = {}; + const filteredChildrenCountLookup: Record = {}; const filteredDescendantCountLookup: Record = {}; const filterCache = {}; @@ -110,6 +111,7 @@ export const filterRowTreeFromGroupingColumns = ( isPassingFiltering = true; } + let filteredChildrenCount = 0; let filteredDescendantCount = 0; if (node.type === 'group') { node.children.forEach((childId) => { @@ -120,6 +122,9 @@ export const filterRowTreeFromGroupingColumns = ( [...ancestorsResults, filterResults], ); filteredDescendantCount += childSubTreeSize; + if (childSubTreeSize > 0) { + filteredChildrenCount += 1; + } }); } @@ -145,6 +150,7 @@ export const filterRowTreeFromGroupingColumns = ( return 0; } + filteredChildrenCountLookup[node.id] = filteredChildrenCount; filteredDescendantCountLookup[node.id] = filteredDescendantCount; if (node.type !== 'group') { @@ -164,6 +170,7 @@ export const filterRowTreeFromGroupingColumns = ( return { filteredRowsLookup, + filteredChildrenCountLookup, filteredDescendantCountLookup, }; }; diff --git a/packages/x-data-grid-premium/src/hooks/features/rows/index.tsx b/packages/x-data-grid-premium/src/hooks/features/rows/index.tsx new file mode 100644 index 0000000000000..bf3c51abb3373 --- /dev/null +++ b/packages/x-data-grid-premium/src/hooks/features/rows/index.tsx @@ -0,0 +1 @@ +export * from './useGridRowAriaAttributes'; diff --git a/packages/x-data-grid-premium/src/hooks/features/rows/useGridRowAriaAttributes.tsx b/packages/x-data-grid-premium/src/hooks/features/rows/useGridRowAriaAttributes.tsx new file mode 100644 index 0000000000000..f844554f633ba --- /dev/null +++ b/packages/x-data-grid-premium/src/hooks/features/rows/useGridRowAriaAttributes.tsx @@ -0,0 +1,12 @@ +import { + useGridRowAriaAttributes as useGridRowAriaAttributesPro, + useGridSelector, +} from '@mui/x-data-grid-pro/internals'; +import { useGridPrivateApiContext } from '../../utils/useGridPrivateApiContext'; +import { gridRowGroupingSanitizedModelSelector } from '../rowGrouping/gridRowGroupingSelector'; + +export const useGridRowAriaAttributes = () => { + const apiRef = useGridPrivateApiContext(); + const gridRowGroupingModel = useGridSelector(apiRef, gridRowGroupingSanitizedModelSelector); + return useGridRowAriaAttributesPro(gridRowGroupingModel.length > 0); +}; diff --git a/packages/x-data-grid-premium/src/hooks/utils/useGridAriaAttributes.tsx b/packages/x-data-grid-premium/src/hooks/utils/useGridAriaAttributes.tsx new file mode 100644 index 0000000000000..8c5023f2af49a --- /dev/null +++ b/packages/x-data-grid-premium/src/hooks/utils/useGridAriaAttributes.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { + useGridAriaAttributes as useGridAriaAttributesPro, + useGridSelector, +} from '@mui/x-data-grid-pro/internals'; +import { gridRowGroupingSanitizedModelSelector } from '../features/rowGrouping/gridRowGroupingSelector'; +import { useGridPrivateApiContext } from './useGridPrivateApiContext'; +import { useGridRootProps } from './useGridRootProps'; + +export const useGridAriaAttributes = (): React.HTMLAttributes => { + const rootProps = useGridRootProps(); + const ariaAttributesPro = useGridAriaAttributesPro(); + const apiRef = useGridPrivateApiContext(); + const gridRowGroupingModel = useGridSelector(apiRef, gridRowGroupingSanitizedModelSelector); + + const ariaAttributesPremium = + rootProps.experimentalFeatures?.ariaV8 && gridRowGroupingModel.length > 0 + ? { role: 'treegrid' } + : {}; + + return { + ...ariaAttributesPro, + ...ariaAttributesPremium, + }; +}; diff --git a/packages/x-data-grid-premium/src/models/dataGridPremiumProps.ts b/packages/x-data-grid-premium/src/models/dataGridPremiumProps.ts index b2f6e79752094..e924af2815adb 100644 --- a/packages/x-data-grid-premium/src/models/dataGridPremiumProps.ts +++ b/packages/x-data-grid-premium/src/models/dataGridPremiumProps.ts @@ -24,7 +24,14 @@ import { GridInitialStatePremium } from './gridStatePremium'; import { GridApiPremium } from './gridApiPremium'; import { GridCellSelectionModel } from '../hooks/features/cellSelection'; -export interface GridExperimentalPremiumFeatures extends GridExperimentalProFeatures {} +export interface GridExperimentalPremiumFeatures extends GridExperimentalProFeatures { + /** + * Enables accessibility improvements that will be enabled by default in V8. + * If you rely on the v7 ARIA attributes (e.g. for CSS selectors), this might be a breaking change. + * @default false + */ + ariaV8: boolean; +} export interface DataGridPremiumPropsWithComplexDefaultValueBeforeProcessing extends Pick { diff --git a/packages/x-data-grid-premium/src/tests/aggregation.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/aggregation.DataGridPremium.test.tsx index 43f054c2635c6..a397d549e0377 100644 --- a/packages/x-data-grid-premium/src/tests/aggregation.DataGridPremium.test.tsx +++ b/packages/x-data-grid-premium/src/tests/aggregation.DataGridPremium.test.tsx @@ -1,14 +1,8 @@ import * as React from 'react'; -import { - createRenderer, - screen, - userEvent, - within, - act, - fireEvent, -} from '@mui/internal-test-utils'; +import { createRenderer, screen, within, act, fireEvent } from '@mui/internal-test-utils'; import { expect } from 'chai'; import { getCell, getColumnHeaderCell, getColumnValues } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; import { SinonSpy, spy } from 'sinon'; import { DataGridPremium, @@ -177,7 +171,7 @@ describe(' - Aggregation', () => { setProps({ columns: [{ ...column, editable: true }] }); fireEvent.doubleClick(cell); expect(cell.querySelector('input')).not.to.equal(null); - userEvent.mousePress(getCell(1, 0)); + fireUserEvent.mousePress(getCell(1, 0)); setProps({ columns: [column] }); fireEvent.doubleClick(cell); @@ -409,14 +403,15 @@ describe(' - Aggregation', () => { act(() => apiRef.current.showColumnMenu('id')); clock.runToLast(); - userEvent.mousePress(screen.getByLabelText('Aggregation')); - userEvent.mousePress( + fireUserEvent.mousePress(screen.getByLabelText('Aggregation')); + fireUserEvent.mousePress( within( screen.getByRole('listbox', { name: 'Aggregation', }), ).getByText('max'), ); + clock.runToLast(); expect(getColumnValues(0)).to.deep.equal(['0', '1', '2', '3', '4', '5', '5' /* Agg */]); }); diff --git a/packages/x-data-grid-premium/src/tests/cellSelection.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/cellSelection.DataGridPremium.test.tsx index 15f398c262c29..1918719266a19 100644 --- a/packages/x-data-grid-premium/src/tests/cellSelection.DataGridPremium.test.tsx +++ b/packages/x-data-grid-premium/src/tests/cellSelection.DataGridPremium.test.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; -import { stub, SinonStub } from 'sinon'; +import { stub, SinonStub, spy } from 'sinon'; import { expect } from 'chai'; import { spyApi, getCell, grid } from 'test/utils/helperFn'; -import { createRenderer, fireEvent, act, userEvent, screen } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, act, screen } from '@mui/internal-test-utils'; import { DataGridPremium, DataGridPremiumProps, @@ -11,6 +11,7 @@ import { gridClasses, } from '@mui/x-data-grid-premium'; import { getBasicGridData } from '@mui/x-data-grid-generator'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; describe(' - Cell selection', () => { const { render } = createRenderer(); @@ -116,9 +117,9 @@ describe(' - Cell selection', () => { expect(document.querySelector('.Mui-selected')).to.equal(null); const cell = getCell(0, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); - userEvent.mousePress(getCell(2, 1), { shiftKey: true }); + fireUserEvent.mousePress(getCell(2, 1), { shiftKey: true }); expect(document.querySelectorAll('.Mui-selected')).to.have.length(3 * 2); // 3 rows with 2 cells each }); @@ -128,9 +129,9 @@ describe(' - Cell selection', () => { const cell = getCell(0, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); - userEvent.mousePress(getCell(2, 1), { shiftKey: true }); + fireUserEvent.mousePress(getCell(2, 1), { shiftKey: true }); expect(spiedSelectCellsBetweenRange.lastCall.args[0]).to.deep.equal({ id: 0, field: 'id' }); expect(spiedSelectCellsBetweenRange.lastCall.args[1]).to.deep.equal({ id: 2, @@ -142,9 +143,9 @@ describe(' - Cell selection', () => { render(); const cell = getCell(0, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); - userEvent.mousePress(getCell(2, 2), { shiftKey: true }); + fireUserEvent.mousePress(getCell(2, 2), { shiftKey: true }); expect(getCell(0, 0)).to.have.class(gridClasses['cell--rangeTop']); expect(getCell(0, 0)).to.have.class(gridClasses['cell--rangeLeft']); @@ -167,7 +168,7 @@ describe(' - Cell selection', () => { const cell = getCell(0, 0); cell.focus(); expect(cell).toHaveFocus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.click(getCell(2, 1), { shiftKey: true }); expect(cell).toHaveFocus(); }); @@ -179,7 +180,7 @@ describe(' - Cell selection', () => { const spiedSelectCellsBetweenRange = spyApi(apiRef.current, 'selectCellRange'); const cell = getCell(0, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.keyDown(cell, { key: 'ArrowDown', shiftKey: true }); expect(spiedSelectCellsBetweenRange.lastCall.args[0]).to.deep.equal({ id: 0, field: 'id' }); @@ -191,7 +192,7 @@ describe(' - Cell selection', () => { const spiedSelectCellsBetweenRange = spyApi(apiRef.current, 'selectCellRange'); const cell = getCell(1, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.keyDown(cell, { key: 'ArrowUp', shiftKey: true }); expect(spiedSelectCellsBetweenRange.lastCall.args[0]).to.deep.equal({ id: 1, field: 'id' }); @@ -203,7 +204,7 @@ describe(' - Cell selection', () => { const spiedSelectCellsBetweenRange = spyApi(apiRef.current, 'selectCellRange'); const cell = getCell(0, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.keyDown(cell, { key: 'ArrowLeft', shiftKey: true }); expect(spiedSelectCellsBetweenRange.lastCall.args[0]).to.deep.equal({ @@ -218,7 +219,7 @@ describe(' - Cell selection', () => { const spiedSelectCellsBetweenRange = spyApi(apiRef.current, 'selectCellRange'); const cell = getCell(0, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.keyDown(cell, { key: 'ArrowRight', shiftKey: true }); expect(spiedSelectCellsBetweenRange.lastCall.args[0]).to.deep.equal({ id: 0, field: 'id' }); @@ -232,13 +233,50 @@ describe(' - Cell selection', () => { render(); const cell = getCell(0, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.keyDown(cell, { key: 'ArrowDown', shiftKey: true }); expect(cell).toHaveFocus(); }); }); + describe('onCellSelectionModelChange', () => { + it('should update the selection state when a cell is selected', () => { + const onCellSelectionModelChange = spy(); + render( + , + ); + fireEvent.click(getCell(0, 0)); + + expect(onCellSelectionModelChange.callCount).to.equal(1); + expect(onCellSelectionModelChange.lastCall.args[0]).to.deep.equal({ '0': { id: true } }); + }); + + // Context: https://github.com/mui/mui-x/issues/14184 + it('should add the new cell selection range to the existing state', () => { + const onCellSelectionModelChange = spy(); + render( + , + ); + + // Add a new cell range to the selection + fireEvent.mouseDown(getCell(2, 0), { ctrlKey: true }); + fireEvent.mouseOver(getCell(3, 0), { ctrlKey: true }); + + expect(onCellSelectionModelChange.lastCall.args[0]).to.deep.equal({ + '0': { id: true }, + '2': { id: true }, + '3': { id: true }, + }); + }); + }); + describe('apiRef', () => { describe('selectCellRange', () => { it('should select all cells within the given arguments if end > start', () => { 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 aad776990c852..6653971f3ae47 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 @@ -6,10 +6,11 @@ import { DataGridPremiumProps, GridColDef, } from '@mui/x-data-grid-premium'; -import { createRenderer, fireEvent, userEvent, waitFor } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, waitFor } from '@mui/internal-test-utils'; import { expect } from 'chai'; import { SinonSpy, spy, stub, SinonStub } from 'sinon'; import { getCell, getColumnValues, sleep } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; import { getBasicGridData } from '@mui/x-data-grid-generator'; describe(' - Clipboard', () => { @@ -72,7 +73,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.click(getCell(2, 2), { shiftKey: true }); @@ -95,7 +96,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.click(getCell(0, 2), { shiftKey: true }); @@ -126,7 +127,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Ctrl' }); fireEvent.click(getCell(1, 0), { ctrlKey: true }); @@ -157,7 +158,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Ctrl' }); fireEvent.click(getCell(1, 0), { ctrlKey: true }); @@ -194,7 +195,7 @@ describe(' - Clipboard', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'v', code: 'KeyV', keyCode: 86, [key]: true }); // Ctrl+V expect(listener.callCount).to.equal(0); }); @@ -207,7 +208,7 @@ describe(' - Clipboard', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'v', code: 'KeyV', keyCode: 86, [key]: true }); // Ctrl+V expect(listener.callCount).to.equal(0); }); @@ -219,7 +220,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.click(getCell(2, 2), { shiftKey: true }); @@ -237,12 +238,55 @@ describe(' - Clipboard', () => { expect(getCell(2, 2)).to.have.text(clipboardData); }); + // Context: https://github.com/mui/mui-x/issues/14233 + it('should paste into cells on the current page when `paginationMode="server"`', async () => { + const rowLength = 4; + + const { setProps } = render( + , + ); + + const clipboardData = '12'; + const cell = getCell(3, 1); // cell in the first row on the next page + + expect(cell).not.to.have.text(clipboardData); + + cell.focus(); + fireUserEvent.mousePress(cell); + paste(cell, clipboardData); + + // no update + await waitFor(() => { + expect(getCell(3, 1)).not.to.have.text(clipboardData); + }); + + // go to the next page + setProps({ paginationModel: { pageSize: 2, page: 1 } }); + + cell.focus(); + fireUserEvent.mousePress(cell); + paste(cell, clipboardData); + + // updated + await waitFor(() => { + expect(getCell(3, 1)).to.have.text(clipboardData); + }); + }); + it('should not paste values outside of the selected cells range', async () => { render(); const cell = getCell(0, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.click(getCell(2, 2), { shiftKey: true }); @@ -279,7 +323,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.click(getCell(2, 2), { shiftKey: true }); @@ -325,7 +369,7 @@ describe(' - Clipboard', () => { const cell = getCell(1, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); const clipboardData = [ ['p11', 'p12', 'p13'], @@ -343,10 +387,10 @@ describe(' - Clipboard', () => { await waitFor(() => { expect(getCell(3, 3).textContent).to.equal('p33'); - expect(getCell(6, 2).textContent).to.equal('p62'); - expect(getCell(7, 1).textContent).to.equal('p71'); - expect(getCell(7, 3).textContent).to.equal('p73'); }); + expect(getCell(6, 2).textContent).to.equal('p62'); + expect(getCell(7, 1).textContent).to.equal('p71'); + expect(getCell(7, 3).textContent).to.equal('p73'); }); }); @@ -356,7 +400,7 @@ describe(' - Clipboard', () => { const cell = getCell(2, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); const clipboardData = ['p01', 'p02', 'p03'].join('\t'); paste(cell, clipboardData); @@ -364,8 +408,8 @@ describe(' - Clipboard', () => { await waitFor(() => { // the last row is not selected and should not be updated expect(getColumnValues(1)).to.deep.equal(['p02', 'p02', 'p02', 'JPYUSD']); - expect(getColumnValues(2)).to.deep.equal(['p03', 'p03', 'p03', '31']); }); + expect(getColumnValues(2)).to.deep.equal(['p03', 'p03', 'p03', '31']); }); it('should paste into selected rows if multiple rows of data are pasted', async () => { @@ -373,7 +417,7 @@ describe(' - Clipboard', () => { const cell = getCell(2, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); const clipboardData = [ ['p01', 'p02', 'p03'].join('\t'), @@ -386,8 +430,8 @@ describe(' - Clipboard', () => { await waitFor(() => { // the last row is not selected and should not be updated expect(getColumnValues(1)).to.deep.equal(['p02', 'p12', 'p22', 'JPYUSD']); - expect(getColumnValues(2)).to.deep.equal(['p03', 'p13', 'p23', '31']); }); + expect(getColumnValues(2)).to.deep.equal(['p03', 'p13', 'p23', '31']); }); it('should ignore row selection when single cell value is pasted', async () => { @@ -395,22 +439,22 @@ describe(' - Clipboard', () => { const cell = getCell(2, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); paste(cell, 'pasted'); await waitFor(() => { // should ignore selected rows and paste into selected cell expect(getColumnValues(1)).to.deep.equal(['USDGBP', 'USDEUR', 'pasted', 'JPYUSD']); - expect(getColumnValues(2)).to.deep.equal(['1', '11', '21', '31']); }); + expect(getColumnValues(2)).to.deep.equal(['1', '11', '21', '31']); }); it('should paste into selected rows when checkbox selection cell is focused', async () => { render(); const checkboxInput = getCell(0, 0).querySelector('input')!; - userEvent.mousePress(checkboxInput!); + fireUserEvent.mousePress(checkboxInput!); const clipboardData = ['p01', 'p02', 'p03'].join('\t'); paste(checkboxInput, clipboardData); @@ -418,8 +462,8 @@ describe(' - Clipboard', () => { await waitFor(() => { // the first column (id) is not editable and won't be updated expect(getCell(0, 2).textContent).to.equal('p02'); - expect(getCell(0, 3).textContent).to.equal('p03'); }); + expect(getCell(0, 3).textContent).to.equal('p03'); }); }); @@ -450,7 +494,7 @@ describe(' - Clipboard', () => { const cell = getCell(1, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); paste(cell, 'Nike'); @@ -468,7 +512,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.click(getCell(1, 2), { shiftKey: true }); @@ -513,7 +557,7 @@ describe(' - Clipboard', () => { const cell = getCell(1, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); paste(cell, '0'); @@ -562,7 +606,7 @@ describe(' - Clipboard', () => { const cell = getCell(1, 2); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); paste(cell, 'John Doe'); @@ -603,7 +647,7 @@ describe(' - Clipboard', () => { const cell = getCell(1, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); paste(cell, 'john doe'); @@ -642,7 +686,7 @@ describe(' - Clipboard', () => { const cell = getCell(1, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.click(getCell(1, 4), { shiftKey: true }); @@ -651,10 +695,10 @@ describe(' - Clipboard', () => { await waitFor(() => { expect(getColumnValues(1)).to.deep.equal(['Nike', 'Nike', 'Puma']); - expect(getColumnValues(2)).to.deep.equal(['Shoes', 'Shoes', 'Shoes']); - expect(getColumnValues(3)).to.deep.equal(['$120', '$100', '$90']); - expect(getColumnValues(4)).to.deep.equal(['4.0', '4.0', '4.9']); }); + expect(getColumnValues(2)).to.deep.equal(['Shoes', 'Shoes', 'Shoes']); + expect(getColumnValues(3)).to.deep.equal(['$120', '$100', '$90']); + expect(getColumnValues(4)).to.deep.equal(['4.0', '4.0', '4.9']); }); it('should call `processRowUpdate` with each row impacted by the paste', async () => { @@ -665,7 +709,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.click(getCell(2, 2), { shiftKey: true }); @@ -708,7 +752,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); paste(cell, '12'); @@ -733,7 +777,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); paste(cell, '12'); @@ -760,7 +804,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); paste(cell, '12'); @@ -789,14 +833,14 @@ describe(' - Clipboard', () => { const cell = getCell(0, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); paste(cell, '12'); await waitFor(() => { expect(onProcessRowUpdateError.callCount).to.equal(1); - expect(onProcessRowUpdateError.args[0][0]).to.equal(error); }); + expect(onProcessRowUpdateError.args[0][0]).to.equal(error); }); it('should emit clipboard paste events', async () => { @@ -821,7 +865,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.click(getCell(0, 2), { shiftKey: true }); @@ -865,13 +909,13 @@ describe(' - Clipboard', () => { } function copyCell(cell: HTMLElement) { - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'c', keyCode: 67, ctrlKey: true }); } function pasteIntoCell(cell: HTMLElement) { cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); paste(cell, clipboardData); } @@ -1086,7 +1130,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Shift' }); fireEvent.click(getCell(2, 2), { shiftKey: true }); @@ -1116,7 +1160,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 1); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); let clipboardData = ['01', '11'].join('\n'); // Add newline at the end @@ -1126,10 +1170,10 @@ describe(' - Clipboard', () => { await waitFor(() => { expect(getCell(0, 1)).to.have.text('01'); - expect(getCell(1, 1)).to.have.text('11'); - // Should not be empty - expect(getCell(2, 1)).to.have.text('GBPEUR'); }); + expect(getCell(1, 1)).to.have.text('11'); + // Should not be empty + expect(getCell(2, 1)).to.have.text('GBPEUR'); }); }); }); diff --git a/packages/x-data-grid-premium/src/tests/materialVersion.test.tsx b/packages/x-data-grid-premium/src/tests/materialVersion.test.tsx new file mode 100644 index 0000000000000..dc0c0a7e6ee01 --- /dev/null +++ b/packages/x-data-grid-premium/src/tests/materialVersion.test.tsx @@ -0,0 +1,5 @@ +import materialPackageJson from '@mui/material/package.json'; +import { checkMaterialVersion } from 'test/utils/checkMaterialVersion'; +import packageJson from '../../package.json'; + +checkMaterialVersion({ packageJson, materialPackageJson }); 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 824d610884a26..9f60eb3694cf7 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 @@ -1,12 +1,5 @@ import * as React from 'react'; -import { - createRenderer, - fireEvent, - screen, - act, - userEvent, - waitFor, -} from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, screen, act, waitFor } from '@mui/internal-test-utils'; import { microtasks, getColumnHeaderCell, @@ -14,7 +7,9 @@ import { getColumnValues, getCell, getSelectByName, + getRow, } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; import { expect } from 'chai'; import { DataGridPremium, @@ -887,7 +882,7 @@ describe(' - Row grouping', () => { />, ); - userEvent.mousePress(getCell(1, 0)); + fireUserEvent.mousePress(getCell(1, 0)); expect(renderIdCell.lastCall.firstArg.field).to.equal('id'); expect(getCell(1, 0)).to.have.text('Focused: true'); }); @@ -2381,6 +2376,45 @@ describe(' - Row grouping', () => { // Corresponds to rows id 0, 1, 2 because of Cat A, ann id 4 because of Cat 1 expect(getColumnValues(1)).to.deep.equal(['', '0', '1', '2', '', '4']); }); + + it('should keep the correct count of the children and descendants in the filter state', () => { + const extendedColumns = [ + ...baselineProps.columns, + { + field: 'value1', + }, + ]; + + const extendedRows = rows.map((row, index) => ({ ...row, value1: `Value${index}` })); + const additionalRows = [ + { id: 5, category1: 'Cat A', category2: 'Cat 2', value1: 'Value5' }, + { id: 6, category1: 'Cat A', category2: 'Cat 2', value1: 'Value6' }, + { id: 7, category1: 'Cat B', category2: 'Cat 1', value1: 'Value7' }, + ]; + + render( + , + ); + + const { filteredChildrenCountLookup, filteredDescendantCountLookup } = + apiRef.current.state.filter; + + expect(filteredChildrenCountLookup['auto-generated-row-category1/Cat A']).to.equal(2); + expect(filteredDescendantCountLookup['auto-generated-row-category1/Cat A']).to.equal(5); + + expect( + filteredChildrenCountLookup['auto-generated-row-category1/Cat A-category2/Cat 2'], + ).to.equal(4); + expect( + filteredDescendantCountLookup['auto-generated-row-category1/Cat A-category2/Cat 2'], + ).to.equal(4); + }); }); describe('prop: rowGroupingColumnMode = "multiple"', () => { @@ -2723,6 +2757,27 @@ describe(' - Row grouping', () => { }); }); + describe('accessibility', () => { + it('should add necessary treegrid aria attributes to the rows', () => { + render( + , + ); + + expect(getRow(0).getAttribute('aria-level')).to.equal('1'); // Cat A + expect(getRow(1).getAttribute('aria-level')).to.equal('2'); // Cat 1 + expect(getRow(1).getAttribute('aria-posinset')).to.equal('1'); + expect(getRow(1).getAttribute('aria-setsize')).to.equal('2'); // Cat A has Cat 1 & Cat 2 + expect(getRow(2).getAttribute('aria-level')).to.equal('3'); // Cat 1 row + expect(getRow(3).getAttribute('aria-posinset')).to.equal('2'); // Cat 2 + expect(getRow(4).getAttribute('aria-posinset')).to.equal('1'); // Cat 2 row + expect(getRow(4).getAttribute('aria-setsize')).to.equal('2'); // Cat 2 has 2 rows + }); + }); + // See https://github.com/mui/mui-x/issues/8626 it('should properly update the rows when they change', async () => { render( diff --git a/packages/x-data-grid-pro/package.json b/packages/x-data-grid-pro/package.json index 97fb042a5ed2d..87b0fe6ca6a32 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.12.0", + "version": "7.15.0", "description": "The Pro plan edition of the Data Grid components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -43,26 +43,38 @@ "directory": "packages/x-data-grid-pro" }, "dependencies": { - "@babel/runtime": "^7.25.0", - "@mui/system": "^5.16.5", - "@mui/utils": "^5.16.5", + "@babel/runtime": "^7.25.4", + "@mui/utils": "^5.16.6", "@mui/x-data-grid": "workspace:*", "@mui/x-internals": "workspace:*", "@mui/x-license": "workspace:*", "@types/format-util": "^1.0.4", "clsx": "^2.1.1", "prop-types": "^15.8.1", - "reselect": "^4.1.8" + "reselect": "^5.1.0" }, "peerDependencies": { - "@mui/material": "^5.15.14", + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.5", + "@mui/internal-test-utils": "^1.0.11", + "@mui/material": "^5.16.7", + "@mui/system": "^5.16.7", "@types/prop-types": "^15.7.12", - "rimraf": "^5.0.9" + "rimraf": "^5.0.10" }, "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 1cacac533b70c..ec3feaa5f00f6 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -16,9 +16,17 @@ import { DataGridProProps } from '../models/dataGridProProps'; import { useDataGridProProps } from './useDataGridProProps'; import { getReleaseInfo } from '../utils/releaseInfo'; import { propValidatorsDataGridPro } from '../internals/propValidation'; +import { useGridAriaAttributes } from '../hooks/utils/useGridAriaAttributes'; +import { useGridRowAriaAttributes } from '../hooks/features/rows/useGridRowAriaAttributes'; export type { GridProSlotsComponent as GridSlots } from '../models'; +const configuration = { + hooks: { + useGridAriaAttributes, + useGridRowAriaAttributes, + }, +}; const releaseInfo = getReleaseInfo(); const DataGridProRaw = React.forwardRef(function DataGridPro( @@ -33,7 +41,7 @@ const DataGridProRaw = React.forwardRef(function DataGridPro + (event: React.MouseEvent) => { @@ -76,7 +76,7 @@ function GridColumnMenuPinningItem(props: GridColumnMenuItemProps) { ); } - if (theme.direction === 'rtl') { + if (isRtl) { return ( {pinToRightMenuItem} diff --git a/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx index eedd1ea4f7d38..79e002211adf2 100644 --- a/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx @@ -8,12 +8,17 @@ import { GridDataSourceGroupNode, useGridSelector, } from '@mui/x-data-grid'; +import { useGridSelectorV8 } from '@mui/x-data-grid/internals'; 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'; +import { + gridDataSourceErrorSelector, + gridDataSourceLoadingIdSelector, +} from '../hooks/features/dataSource/gridDataSourceSelector'; type OwnerState = DataGridProProcessedProps; @@ -50,10 +55,8 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) 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 isDataLoading = useGridSelectorV8(apiRef, gridDataSourceLoadingIdSelector, id); + const error = useGridSelectorV8(apiRef, gridDataSourceErrorSelector, id); const handleClick = (event: React.MouseEvent) => { if (!rowNode.childrenExpanded) { diff --git a/packages/x-data-grid-pro/src/components/GridRowReorderCell.tsx b/packages/x-data-grid-pro/src/components/GridRowReorderCell.tsx index 8cac92c40b116..eb8aa484be6b3 100644 --- a/packages/x-data-grid-pro/src/components/GridRowReorderCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridRowReorderCell.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import PropTypes from 'prop-types'; import composeClasses from '@mui/utils/composeClasses'; import { GridRenderCellParams, @@ -96,6 +97,75 @@ function GridRowReorderCell(params: GridRenderCellParams) { ); } +GridRowReorderCell.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "pnpm proptypes" | + // ---------------------------------------------------------------------- + /** + * GridApi that let you manipulate the grid. + */ + api: PropTypes.object.isRequired, + /** + * The mode of the cell. + */ + cellMode: PropTypes.oneOf(['edit', 'view']).isRequired, + /** + * The column of the row that the current cell belongs to. + */ + colDef: PropTypes.object.isRequired, + /** + * The column field of the cell that triggered the event. + */ + field: PropTypes.string.isRequired, + /** + * A ref allowing to set imperative focus. + * It can be passed to the element that should receive focus. + * @ignore - do not document. + */ + focusElementRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.shape({ + current: PropTypes.shape({ + focus: PropTypes.func.isRequired, + }), + }), + ]), + /** + * The cell value formatted with the column valueFormatter. + */ + formattedValue: PropTypes.any, + /** + * If true, the cell is the active element. + */ + hasFocus: PropTypes.bool.isRequired, + /** + * The grid row id. + */ + id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + /** + * If true, the cell is editable. + */ + isEditable: PropTypes.bool, + /** + * The row model of the row that the current cell belongs to. + */ + row: PropTypes.any.isRequired, + /** + * The node of the row that the current cell belongs to. + */ + rowNode: PropTypes.object.isRequired, + /** + * the tabIndex value. + */ + tabIndex: PropTypes.oneOf([-1, 0]).isRequired, + /** + * The cell value. + * If the column has `valueGetter`, use `params.row` to directly access the fields. + */ + value: PropTypes.any, +} as any; + export { GridRowReorderCell }; export const renderRowReorderCell = (params: GridRenderCellParams) => { diff --git a/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx b/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx index 8a73ef0aab68a..1d22e87939585 100644 --- a/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx +++ b/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx @@ -6,6 +6,7 @@ import { unstable_composeClasses as composeClasses, unstable_capitalize as capitalize, } from '@mui/utils'; +import { fastMemo } from '@mui/x-internals/fastMemo'; import { GridFilterItem, GridFilterOperator, @@ -19,7 +20,6 @@ import { GridPinnedColumnPosition, } from '@mui/x-data-grid'; import { - fastMemo, GridStateColDef, useGridPrivateApiContext, gridHeaderFilteringEditFieldSelector, @@ -313,8 +313,6 @@ const GridHeaderFilterCell = React.forwardRef>(); const ownerState = { classes: props.classes }; const classes = useUtilityClasses(ownerState); - const theme = useTheme(); + const isRtl = useRtl(); React.useEffect(() => { return () => { @@ -219,14 +219,10 @@ export const useGridColumnReorder = ( const cursorMoveDirectionX = getCursorMoveDirectionX(cursorPosition.current, coordinates); const hasMovedLeft = cursorMoveDirectionX === CURSOR_MOVE_DIRECTION_LEFT && - (theme.direction === 'rtl' - ? dragColIndex < targetColIndex - : targetColIndex < dragColIndex); + (isRtl ? dragColIndex < targetColIndex : targetColIndex < dragColIndex); const hasMovedRight = cursorMoveDirectionX === CURSOR_MOVE_DIRECTION_RIGHT && - (theme.direction === 'rtl' - ? targetColIndex < dragColIndex - : dragColIndex < targetColIndex); + (isRtl ? targetColIndex < dragColIndex : dragColIndex < targetColIndex); if (hasMovedLeft || hasMovedRight) { let canBeReordered: boolean; @@ -298,7 +294,7 @@ export const useGridColumnReorder = ( cursorPosition.current = coordinates; } }, - [apiRef, logger, theme.direction], + [apiRef, logger, isRtl], ); const handleDragEnd = React.useCallback>( 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 index a7bee9eec1d98..6b6aa5a9404d9 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/gridDataSourceSelector.ts @@ -3,8 +3,9 @@ import { gridFilterModelSelector, gridSortModelSelector, gridPaginationModelSelector, + GridRowId, } from '@mui/x-data-grid'; -import { createSelector } from '@mui/x-data-grid/internals'; +import { createSelector, createSelectorV8 } from '@mui/x-data-grid/internals'; import { GridStatePro } from '../../../models/gridStatePro'; const computeStartEnd = (paginationModel: GridPaginationModel) => { @@ -37,7 +38,17 @@ export const gridDataSourceLoadingSelector = createSelector( (dataSource) => dataSource.loading, ); +export const gridDataSourceLoadingIdSelector = createSelectorV8( + gridDataSourceStateSelector, + (dataSource, id: GridRowId) => dataSource.loading[id] ?? false, +); + export const gridDataSourceErrorsSelector = createSelector( gridDataSourceStateSelector, (dataSource) => dataSource.errors, ); + +export const gridDataSourceErrorSelector = createSelectorV8( + gridDataSourceStateSelector, + (dataSource, id: GridRowId) => dataSource.errors[id], +); diff --git a/packages/x-data-grid-pro/src/hooks/features/rows/index.tsx b/packages/x-data-grid-pro/src/hooks/features/rows/index.tsx new file mode 100644 index 0000000000000..bf3c51abb3373 --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/rows/index.tsx @@ -0,0 +1 @@ +export * from './useGridRowAriaAttributes'; diff --git a/packages/x-data-grid-pro/src/hooks/features/rows/useGridRowAriaAttributes.tsx b/packages/x-data-grid-pro/src/hooks/features/rows/useGridRowAriaAttributes.tsx new file mode 100644 index 0000000000000..45aada4c4d539 --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/features/rows/useGridRowAriaAttributes.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; +import { + GridTreeNode, + useGridSelector, + gridFilteredTopLevelRowCountSelector, + GRID_ROOT_GROUP_ID, +} from '@mui/x-data-grid'; +import { + useGridRowAriaAttributes as useGridRowAriaAttributesCommunity, + gridFilteredChildrenCountLookupSelector, + gridExpandedSortedRowTreeLevelPositionLookupSelector, +} from '@mui/x-data-grid/internals'; +import { useGridPrivateApiContext } from '../../utils/useGridPrivateApiContext'; +import { useGridRootProps } from '../../utils/useGridRootProps'; + +export const useGridRowAriaAttributes = (addTreeDataAttributes?: boolean) => { + const apiRef = useGridPrivateApiContext(); + const props = useGridRootProps(); + const getRowAriaAttributesCommunity = useGridRowAriaAttributesCommunity(); + + const filteredTopLevelRowCount = useGridSelector(apiRef, gridFilteredTopLevelRowCountSelector); + const filteredChildrenCountLookup = useGridSelector( + apiRef, + gridFilteredChildrenCountLookupSelector, + ); + const sortedVisibleRowPositionsLookup = useGridSelector( + apiRef, + gridExpandedSortedRowTreeLevelPositionLookupSelector, + ); + + return React.useCallback( + (rowNode: GridTreeNode, index: number) => { + const ariaAttributes = getRowAriaAttributesCommunity(rowNode, index); + + if (rowNode === null || !(props.treeData || addTreeDataAttributes)) { + return ariaAttributes; + } + + // pinned and footer rows are not part of the rowgroup and should not get the set specific aria attributes + if (rowNode.type === 'footer' || rowNode.type === 'pinnedRow') { + return ariaAttributes; + } + + ariaAttributes['aria-level'] = rowNode.depth + 1; + + const filteredChildrenCount = filteredChildrenCountLookup[rowNode.id] ?? 0; + // aria-expanded should only be added to the rows that contain children + if (rowNode.type === 'group' && filteredChildrenCount > 0) { + ariaAttributes['aria-expanded'] = Boolean(rowNode.childrenExpanded); + } + + // if the parent is null, set size and position cannot be determined + if (rowNode.parent !== null) { + ariaAttributes['aria-setsize'] = + rowNode.parent === GRID_ROOT_GROUP_ID + ? filteredTopLevelRowCount + : filteredChildrenCountLookup[rowNode.parent]; + ariaAttributes['aria-posinset'] = sortedVisibleRowPositionsLookup[rowNode.id]; + } + + return ariaAttributes; + }, + [ + props.treeData, + addTreeDataAttributes, + filteredTopLevelRowCount, + filteredChildrenCountLookup, + sortedVisibleRowPositionsLookup, + getRowAriaAttributesCommunity, + ], + ); +}; 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 index 6c182e6c04d12..c09bc45b8dedf 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/useGridDataSourceTreeDataPreProcessors.tsx @@ -151,7 +151,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( path: [...parentPath, getGroupKey(params.dataRowIdToModelLookup[rowId])].map( (key): RowTreeBuilderGroupingCriterion => ({ key, field: null }), ), - hasServerChildren: !!count && count !== 0, + serverChildrenCount: count, }; }; 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 index e1314f6aef1d1..677f7210c9274 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts @@ -3,16 +3,19 @@ import { getTreeNodeDescendants } from '@mui/x-data-grid/internals'; export function skipFiltering(rowTree: GridRowTreeConfig) { const filteredRowsLookup: Record = {}; + const filteredChildrenCountLookup: 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; + filteredChildrenCountLookup[node.id] = node.serverChildrenCount ?? 0; } return { filteredRowsLookup, + filteredChildrenCountLookup, filteredDescendantCountLookup, }; } diff --git a/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataUtils.ts b/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataUtils.ts index 9926ef35c3df5..ee8224e89b082 100644 --- a/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataUtils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataUtils.ts @@ -32,6 +32,7 @@ export const filterRowTreeFromTreeData = ( ): Omit => { const { apiRef, rowTree, disableChildrenFiltering, isRowMatchingFilters } = params; const filteredRowsLookup: Record = {}; + const filteredChildrenCountLookup: Record = {}; const filteredDescendantCountLookup: Record = {}; const filterCache = {}; @@ -64,6 +65,7 @@ export const filterRowTreeFromTreeData = ( ); } + let filteredChildrenCount = 0; let filteredDescendantCount = 0; if (node.type === 'group') { node.children.forEach((childId) => { @@ -75,6 +77,9 @@ export const filterRowTreeFromTreeData = ( ); filteredDescendantCount += childSubTreeSize; + if (childSubTreeSize > 0) { + filteredChildrenCount += 1; + } }); } @@ -100,6 +105,7 @@ export const filterRowTreeFromTreeData = ( return 0; } + filteredChildrenCountLookup[node.id] = filteredChildrenCount; filteredDescendantCountLookup[node.id] = filteredDescendantCount; if (node.type === 'footer') { @@ -119,6 +125,7 @@ export const filterRowTreeFromTreeData = ( return { filteredRowsLookup, + filteredChildrenCountLookup, filteredDescendantCountLookup, }; }; diff --git a/packages/x-data-grid-pro/src/hooks/utils/useGridAriaAttributes.tsx b/packages/x-data-grid-pro/src/hooks/utils/useGridAriaAttributes.tsx new file mode 100644 index 0000000000000..1f5507728d8bd --- /dev/null +++ b/packages/x-data-grid-pro/src/hooks/utils/useGridAriaAttributes.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import { useGridAriaAttributes as useGridAriaAttributesCommunity } from '@mui/x-data-grid/internals'; +import { useGridRootProps } from './useGridRootProps'; + +export const useGridAriaAttributes = (): React.HTMLAttributes => { + const ariaAttributesCommunity = useGridAriaAttributesCommunity(); + const rootProps = useGridRootProps(); + + const ariaAttributesPro = rootProps.treeData ? { role: 'treegrid' } : {}; + + return { + ...ariaAttributesCommunity, + ...ariaAttributesPro, + }; +}; diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts index 28b184e2bd42c..c0de9e27064b8 100644 --- a/packages/x-data-grid-pro/src/internals/index.ts +++ b/packages/x-data-grid-pro/src/internals/index.ts @@ -4,8 +4,14 @@ export * from '@mui/x-data-grid/internals'; export { GridColumnHeaders } from '../components/GridColumnHeaders'; export { DATA_GRID_PRO_DEFAULT_SLOTS_COMPONENTS } from '../constants/dataGridProDefaultSlotsComponents'; -// eslint-disable-next-line import/export +/* eslint-disable import/export -- + * x-data-grid-pro internals that are overriding the x-data-grid internals + */ export { useGridColumnHeaders } from '../hooks/features/columnHeaders/useGridColumnHeaders'; +export { useGridAriaAttributes } from '../hooks/utils/useGridAriaAttributes'; +export { useGridRowAriaAttributes } from '../hooks/features/rows/useGridRowAriaAttributes'; +// eslint-enable import/export + export { useGridColumnPinning, columnPinningStateInitializer, @@ -22,6 +28,7 @@ export { } from '../hooks/features/detailPanel/useGridDetailPanel'; export { useGridDetailPanelPreProcessors } from '../hooks/features/detailPanel/useGridDetailPanelPreProcessors'; export { useGridInfiniteLoader } from '../hooks/features/infiniteLoader/useGridInfiniteLoader'; + export { useGridRowReorder } from '../hooks/features/rowReorder/useGridRowReorder'; export { useGridRowReorderPreProcessors } from '../hooks/features/rowReorder/useGridRowReorderPreProcessors'; export { useGridTreeData } from '../hooks/features/treeData/useGridTreeData'; diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index 4d611b57ea069..1af6cbe61f588 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -153,7 +153,7 @@ interface DataGridProRegularProps { * @param {R} row The row from which we want the path. * @returns {string[]} The path to the row. */ - getTreeDataPath?: (row: R) => string[]; + getTreeDataPath?: (row: R) => readonly string[]; } export interface DataGridProPropsWithoutDefaultValue diff --git a/packages/x-data-grid-pro/src/tests/cellEditing.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/cellEditing.DataGridPro.test.tsx index 65611293817fe..e28fd45873d79 100644 --- a/packages/x-data-grid-pro/src/tests/cellEditing.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/cellEditing.DataGridPro.test.tsx @@ -13,8 +13,9 @@ import { GridCellModes, } from '@mui/x-data-grid-pro'; import { getBasicGridData } from '@mui/x-data-grid-generator'; -import { createRenderer, fireEvent, act, userEvent } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, act } from '@mui/internal-test-utils'; import { getCell, spyApi } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; describe(' - Cell editing', () => { const { render, clock } = createRenderer({ clock: 'fake' }); @@ -210,9 +211,7 @@ describe(' - Cell editing', () => { const cell = getCell(0, 1); cell.focus(); - await act(() => { - fireEvent.keyDown(cell, { key: 'Enter' }); - }); + fireEvent.keyDown(cell, { key: 'Enter' }); expect(handleEditCellStop.callCount).to.equal(0); }); @@ -759,7 +758,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: '$' }); expect(listener.lastCall.args[0].reason).to.equal('printableKeyDown'); }); @@ -769,7 +768,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: ' ' }); expect(listener.callCount).to.equal(0); }); @@ -781,7 +780,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: '1' }); expect(listener.lastCall.args[0].reason).to.equal('printableKeyDown'); }); @@ -793,7 +792,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Enter' }); expect(listener.lastCall.args[0].reason).to.equal('enterKeyDown'); }); @@ -803,7 +802,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Enter' }); expect(listener.callCount).to.equal(0); }); @@ -812,7 +811,7 @@ describe(' - Cell editing', () => { render(); const spiedStartCellEditMode = spyApi(apiRef.current, 'startCellEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Enter' }); expect(spiedStartCellEditMode.callCount).to.equal(1); }); @@ -824,7 +823,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Delete' }); expect(listener.lastCall.args[0].reason).to.equal('deleteKeyDown'); }); @@ -834,7 +833,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Delete' }); expect(listener.callCount).to.equal(0); }); @@ -843,7 +842,7 @@ describe(' - Cell editing', () => { render(); const spiedStartCellEditMode = spyApi(apiRef.current, 'startCellEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Delete' }); expect(spiedStartCellEditMode.callCount).to.equal(1); }); @@ -852,7 +851,7 @@ describe(' - Cell editing', () => { render(); const spiedStartCellEditMode = spyApi(apiRef.current, 'startCellEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Delete' }); expect(spiedStartCellEditMode.callCount).to.equal(1); expect(spiedStartCellEditMode.lastCall.args[0]).to.deep.equal({ @@ -868,7 +867,7 @@ describe(' - Cell editing', () => { render(); const spiedStartCellEditMode = spyApi(apiRef.current, 'startCellEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'a' }); // A expect(spiedStartCellEditMode.callCount).to.equal(1); }); @@ -878,7 +877,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'a' }); // A expect(listener.lastCall.args[0].reason).to.equal('printableKeyDown'); }); @@ -888,7 +887,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'a' }); // A expect(listener.callCount).to.equal(0); }); @@ -899,7 +898,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'a', [key]: true }); // for example Ctrl + A, copy expect(listener.callCount).to.equal(0); }); @@ -910,7 +909,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'a', shiftKey: true }); // Print A in uppercase expect(listener.callCount).to.equal(1); }); @@ -920,7 +919,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'v', ctrlKey: true }); // Ctrl+V expect(listener.callCount).to.equal(1); }); @@ -930,7 +929,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Ļ€', altKey: true }); // āŒ„ Option + P expect(listener.callCount).to.equal(1); }); @@ -939,7 +938,7 @@ describe(' - Cell editing', () => { render(); const spiedStartCellEditMode = spyApi(apiRef.current, 'startCellEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'a' }); expect(spiedStartCellEditMode.callCount).to.equal(1); expect(spiedStartCellEditMode.lastCall.args[0]).to.deep.equal({ @@ -991,7 +990,7 @@ describe(' - Cell editing', () => { apiRef.current.subscribeEvent('cellEditStop', listener); fireEvent.doubleClick(getCell(0, 1)); expect(listener.callCount).to.equal(0); - userEvent.mousePress(getCell(1, 1)); + fireUserEvent.mousePress(getCell(1, 1)); expect(listener.lastCall.args[0].reason).to.equal('cellFocusOut'); }); @@ -999,7 +998,7 @@ describe(' - Cell editing', () => { render(); const spiedStopCellEditMode = spyApi(apiRef.current, 'stopCellEditMode'); fireEvent.doubleClick(getCell(0, 1)); - userEvent.mousePress(getCell(1, 1)); + fireUserEvent.mousePress(getCell(1, 1)); expect(spiedStopCellEditMode.callCount).to.equal(1); expect(spiedStopCellEditMode.lastCall.args[0]).to.deep.equal({ id: 0, @@ -1021,7 +1020,7 @@ describe(' - Cell editing', () => { resolve(); }), ); - userEvent.mousePress(getCell(1, 1)); + fireUserEvent.mousePress(getCell(1, 1)); expect(spiedStopCellEditMode.callCount).to.equal(1); expect(spiedStopCellEditMode.lastCall.args[0].ignoreModifications).to.equal(false); }); @@ -1033,7 +1032,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStop', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); expect(listener.callCount).to.equal(0); fireEvent.keyDown(cell, { key: 'Escape' }); @@ -1044,7 +1043,7 @@ describe(' - Cell editing', () => { render(); const spiedStopCellEditMode = spyApi(apiRef.current, 'stopCellEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); fireEvent.keyDown(cell, { key: 'Escape' }); expect(spiedStopCellEditMode.callCount).to.equal(1); @@ -1063,7 +1062,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStop', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); expect(listener.callCount).to.equal(0); fireEvent.keyDown(cell, { key: 'Enter' }); @@ -1074,7 +1073,7 @@ describe(' - Cell editing', () => { render(); const spiedStopCellEditMode = spyApi(apiRef.current, 'stopCellEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); fireEvent.keyDown(cell, { key: 'Enter' }); expect(spiedStopCellEditMode.callCount).to.equal(1); @@ -1091,7 +1090,7 @@ describe(' - Cell editing', () => { render(); const spiedStopCellEditMode = spyApi(apiRef.current, 'stopCellEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); await act( () => @@ -1112,7 +1111,7 @@ describe(' - Cell editing', () => { const listener = spy(); apiRef.current.subscribeEvent('cellEditStop', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); expect(listener.callCount).to.equal(0); fireEvent.keyDown(cell, { key: 'Tab' }); @@ -1123,7 +1122,7 @@ describe(' - Cell editing', () => { render(); const spiedStopCellEditMode = spyApi(apiRef.current, 'stopCellEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); fireEvent.keyDown(cell, { key: 'Tab' }); expect(spiedStopCellEditMode.callCount).to.equal(1); @@ -1140,7 +1139,7 @@ describe(' - Cell editing', () => { render(); const spiedStopCellEditMode = spyApi(apiRef.current, 'stopCellEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); await act( () => diff --git a/packages/x-data-grid-pro/src/tests/clipboard.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/clipboard.DataGridPro.test.tsx index 6287545d7c5b2..e7be578601969 100644 --- a/packages/x-data-grid-pro/src/tests/clipboard.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/clipboard.DataGridPro.test.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { GridApi, useGridApiRef, DataGridPro, DataGridProProps } from '@mui/x-data-grid-pro'; -import { createRenderer, fireEvent, act, userEvent } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, act } from '@mui/internal-test-utils'; import { expect } from 'chai'; import { SinonSpy, spy } from 'sinon'; import { getCell } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); @@ -53,7 +54,7 @@ describe(' - Clipboard', () => { act(() => apiRef.current.selectRows([0, 1])); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'c', keyCode: 67, [key]: true }); expect(writeText.firstCall.args[0]).to.equal(['0\tNike', '1\tAdidas'].join('\r\n')); }); @@ -72,7 +73,7 @@ describe(' - Clipboard', () => { const cell = getCell(0, 0); cell.focus(); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'c', keyCode: 67, ctrlKey: true }); expect(writeText.lastCall.firstArg).to.equal('1 " 1'); @@ -94,7 +95,7 @@ describe(' - Clipboard', () => { act(() => apiRef.current.selectRows([0, 1])); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'c', keyCode: 67, ctrlKey: true }); expect(writeText.firstCall.args[0]).to.equal(['1 " 1', '2'].join('\r\n')); }); 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 7508c219422f9..340fefa5515c8 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 @@ -12,14 +12,7 @@ import { GridColDef, } from '@mui/x-data-grid-pro'; import { useBasicDemoData } from '@mui/x-data-grid-generator'; -import { - createRenderer, - fireEvent, - screen, - createEvent, - act, - userEvent, -} from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, screen, createEvent, act } from '@mui/internal-test-utils'; import { $, $$, @@ -29,6 +22,7 @@ import { getColumnHeadersTextContent, grid, } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; // TODO Move to utils // Fix https://github.com/mui/mui-x/pull/2085/files/058f56ac3c729b2142a9a28b79b5b13535cdb819#diff-db85480a519a5286d7341e9b8957844762cf04cdacd946331ebaaaff287482ec @@ -105,7 +99,7 @@ describe(' - Column pinning', () => { virtualScroller.scrollLeft = 100; act(() => virtualScroller.dispatchEvent(new Event('scroll'))); const cell = getCell(0, 2); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'ArrowLeft' }); expect(virtualScroller.scrollLeft).to.equal(0); }); @@ -119,7 +113,7 @@ describe(' - Column pinning', () => { const virtualScroller = document.querySelector(`.${gridClasses.virtualScroller}`)!; expect(virtualScroller.scrollLeft).to.equal(0); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'ArrowRight' }); expect(virtualScroller.scrollLeft).to.equal(100); }); diff --git a/packages/x-data-grid-pro/src/tests/columnSpanning.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/columnSpanning.DataGridPro.test.tsx index f5f8e93126a23..5c3bd5c7c0cce 100644 --- a/packages/x-data-grid-pro/src/tests/columnSpanning.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/columnSpanning.DataGridPro.test.tsx @@ -1,8 +1,9 @@ import * as React from 'react'; -import { createRenderer, fireEvent, act, userEvent } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, act } from '@mui/internal-test-utils'; import { expect } from 'chai'; import { DataGridPro, GridApi, useGridApiRef, GridColDef, gridClasses } from '@mui/x-data-grid-pro'; import { getActiveCell, getCell, getColumnHeaderCell } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); @@ -102,7 +103,7 @@ describe(' - Column spanning', () => { act(() => apiRef!.current.setColumnIndex('price', 1)); - userEvent.mousePress(getCell(1, 1)); + fireUserEvent.mousePress(getCell(1, 1)); fireEvent.keyDown(getCell(1, 1), { key: 'ArrowRight' }); expect(getActiveCell()).to.equal('1-2'); }); diff --git a/packages/x-data-grid-pro/src/tests/columnsVisibility.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/columnsVisibility.DataGridPro.test.tsx index 884c31719746d..07d621dcb81c7 100644 --- a/packages/x-data-grid-pro/src/tests/columnsVisibility.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/columnsVisibility.DataGridPro.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { spy } from 'sinon'; import { expect } from 'chai'; -import { createRenderer, fireEvent, act } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, act, screen } from '@mui/internal-test-utils'; import { DataGridPro, DataGridProProps, @@ -121,7 +121,7 @@ describe(' - Columns visibility', () => { }); it('should not hide column when resizing a column after hiding it and showing it again', () => { - const { getByRole } = render( + render( - Columns visibility', () => { />, ); - const showHideAllCheckbox = getByRole('checkbox', { name: 'Show/Hide All' }); + const showHideAllCheckbox = screen.getByRole('checkbox', { name: 'Show/Hide All' }); fireEvent.click(showHideAllCheckbox); expect(getColumnHeadersTextContent()).to.deep.equal([]); fireEvent.click(document.querySelector('[role="tooltip"] [name="id"]')!); diff --git a/packages/x-data-grid-pro/src/tests/components.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/components.DataGridPro.test.tsx index 496505757760c..0c5db66554eed 100644 --- a/packages/x-data-grid-pro/src/tests/components.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/components.DataGridPro.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { createRenderer, EventType, fireEvent, userEvent } from '@mui/internal-test-utils'; +import { createRenderer, EventType, fireEvent } from '@mui/internal-test-utils'; import { spy } from 'sinon'; import { expect } from 'chai'; import { @@ -11,6 +11,7 @@ import { } from '@mui/x-data-grid-pro'; import { useBasicDemoData } from '@mui/x-data-grid-generator'; import { getCell, getRow } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; describe(' - Components', () => { const { render } = createRenderer(); @@ -86,7 +87,7 @@ describe(' - Components', () => { expect(propHandler.callCount).to.equal(0); expect(eventHandler.callCount).to.equal(0); - userEvent.mousePress(getCell(0, 0)); + fireUserEvent.mousePress(getCell(0, 0)); fireEvent.keyDown(getCell(0, 0)); expect(propHandler.callCount).to.equal(1); diff --git a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx index e6aa9421dac85..29a8ffe8b2b9c 100644 --- a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx @@ -11,15 +11,9 @@ import { GRID_DETAIL_PANEL_TOGGLE_FIELD, } from '@mui/x-data-grid-pro'; import { useBasicDemoData } from '@mui/x-data-grid-generator'; -import { - createRenderer, - fireEvent, - screen, - waitFor, - act, - userEvent, -} from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, screen, waitFor, act } from '@mui/internal-test-utils'; import { $, $$, grid, getRow, getCell, getColumnValues, microtasks } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); @@ -228,7 +222,7 @@ describe(' - Detail panel', () => { />, ); const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - userEvent.mousePress(getCell(2, 1)); + fireUserEvent.mousePress(getCell(2, 1)); fireEvent.keyDown(getCell(2, 1), { key: 'ArrowDown' }); expect(virtualScroller.scrollTop).to.equal(0); fireEvent.keyDown(getCell(3, 1), { key: 'ArrowDown' }); @@ -259,7 +253,7 @@ describe(' - Detail panel', () => { const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'ArrowRight' }); virtualScroller.dispatchEvent(new Event('scroll')); @@ -278,7 +272,7 @@ describe(' - Detail panel', () => { render(
Detail
} />); expect(screen.queryByText('Detail')).to.equal(null); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: ' ' }); expect(screen.queryByText('Detail')).not.to.equal(null); fireEvent.keyDown(cell, { key: ' ' }); @@ -438,7 +432,7 @@ describe(' - Detail panel', () => { ); expect(screen.queryByText('Detail')).to.equal(null); const cell = getCell(1, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(handleRowSelectionModelChange.callCount).to.equal(0); }); @@ -514,7 +508,7 @@ describe(' - Detail panel', () => { />, ); - userEvent.mousePress(screen.getAllByRole('button', { name: 'Expand' })[0]); + fireUserEvent.mousePress(screen.getAllByRole('button', { name: 'Expand' })[0]); const virtualScroller = document.querySelector(`.${gridClasses.virtualScroller}`)!; virtualScroller.scrollTop = 500; diff --git a/packages/x-data-grid-pro/src/tests/editComponents.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/editComponents.DataGridPro.test.tsx index 016d93c357ef0..4982b8f559b46 100644 --- a/packages/x-data-grid-pro/src/tests/editComponents.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/editComponents.DataGridPro.test.tsx @@ -10,9 +10,10 @@ import { renderEditInputCell, renderEditSingleSelectCell, } from '@mui/x-data-grid-pro'; -import { act, createRenderer, fireEvent, screen, userEvent } from '@mui/internal-test-utils'; +import { act, createRenderer, fireEvent, screen } from '@mui/internal-test-utils'; import { expect } from 'chai'; import { getCell, spyApi } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; import { spy, SinonSpy } from 'sinon'; /** @@ -571,7 +572,7 @@ describe(' - Edit components', () => { const cell = getCell(0, 0); fireEvent.doubleClick(cell); - userEvent.mousePress(document.getElementById('outside-grid')!); + fireUserEvent.mousePress(document.getElementById('outside-grid')!); await act(() => Promise.resolve()); expect(onCellEditStop.callCount).to.equal(1); @@ -590,7 +591,7 @@ describe(' - Edit components', () => { const cell = getCell(0, 0); fireEvent.doubleClick(cell); - userEvent.mousePress(screen.queryAllByRole('option')[1]); + fireUserEvent.mousePress(screen.queryAllByRole('option')[1]); clock.runToLast(); expect(screen.queryByRole('listbox')).to.equal(null); fireEvent.keyDown(screen.getByRole('combobox'), { key: 'Enter' }); diff --git a/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx index 9818caf942050..3f9e9f9a36f6b 100644 --- a/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx @@ -1161,11 +1161,9 @@ describe(' - Filter', () => { openedPanelValue: GridPreferencePanelsValue.filters, }, }; - const { getAllByRole } = render( - , - ); + render(); // For JSDom, the first hidden combo is also found which we are not interested in - const select = getAllByRole('combobox', { name: 'Logic operator' })[isJSDOM ? 1 : 0]; + const select = screen.getAllByRole('combobox', { name: 'Logic operator' })[isJSDOM ? 1 : 0]; expect(select).not.to.have.class('Mui-disabled'); }); diff --git a/packages/x-data-grid-pro/src/tests/materialVersion.test.tsx b/packages/x-data-grid-pro/src/tests/materialVersion.test.tsx new file mode 100644 index 0000000000000..dc0c0a7e6ee01 --- /dev/null +++ b/packages/x-data-grid-pro/src/tests/materialVersion.test.tsx @@ -0,0 +1,5 @@ +import materialPackageJson from '@mui/material/package.json'; +import { checkMaterialVersion } from 'test/utils/checkMaterialVersion'; +import packageJson from '../../package.json'; + +checkMaterialVersion({ packageJson, materialPackageJson }); diff --git a/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx index ff073625b482c..d469b0bb87a29 100644 --- a/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowEditing.DataGridPro.test.tsx @@ -13,8 +13,9 @@ import { } from '@mui/x-data-grid-pro'; import Portal from '@mui/material/Portal'; import { getBasicGridData } from '@mui/x-data-grid-generator'; -import { createRenderer, fireEvent, act, userEvent, screen } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, act, screen } from '@mui/internal-test-utils'; import { getCell, getRow, spyApi } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; describe(' - Row editing', () => { const { render, clock } = createRenderer(); @@ -780,7 +781,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Enter' }); expect(listener.lastCall.args[0].reason).to.equal('enterKeyDown'); }); @@ -790,7 +791,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStart', listener); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Enter' }); expect(listener.callCount).to.equal(0); }); @@ -799,7 +800,7 @@ describe(' - Row editing', () => { render(); const spiedStartRowEditMode = spyApi(apiRef.current, 'startRowEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Enter' }); expect(spiedStartRowEditMode.callCount).to.equal(1); expect(spiedStartRowEditMode.lastCall.args[0]).to.deep.equal({ @@ -815,7 +816,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Delete' }); expect(listener.lastCall.args[0].reason).to.equal('deleteKeyDown'); }); @@ -825,7 +826,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStart', listener); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Delete' }); expect(listener.callCount).to.equal(0); }); @@ -834,7 +835,7 @@ describe(' - Row editing', () => { render(); const spiedStartRowEditMode = spyApi(apiRef.current, 'startRowEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'Delete' }); expect(spiedStartRowEditMode.callCount).to.equal(1); expect(spiedStartRowEditMode.lastCall.args[0]).to.deep.equal({ @@ -851,7 +852,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'a' }); expect(listener.lastCall.args[0].reason).to.equal('printableKeyDown'); }); @@ -861,7 +862,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStart', listener); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'a' }); expect(listener.callCount).to.equal(0); }); @@ -872,7 +873,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'a', [key]: true }); expect(listener.callCount).to.equal(0); }); @@ -883,7 +884,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'a', shiftKey: true }); expect(listener.callCount).to.equal(1); }); @@ -893,7 +894,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: ' ' }); expect(listener.callCount).to.equal(0); }); @@ -903,7 +904,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStart', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'v', ctrlKey: true }); expect(listener.callCount).to.equal(1); }); @@ -912,7 +913,7 @@ describe(' - Row editing', () => { render(); const spiedStartRowEditMode = spyApi(apiRef.current, 'startRowEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'a' }); expect(spiedStartRowEditMode.callCount).to.equal(1); expect(spiedStartRowEditMode.lastCall.args[0]).to.deep.equal({ @@ -966,7 +967,7 @@ describe(' - Row editing', () => { apiRef.current.subscribeEvent('rowEditStop', listener); fireEvent.doubleClick(getCell(0, 1)); expect(listener.callCount).to.equal(0); - userEvent.mousePress(getCell(1, 1)); + fireUserEvent.mousePress(getCell(1, 1)); clock.runToLast(); expect(listener.lastCall.args[0].reason).to.equal('rowFocusOut'); }); @@ -986,7 +987,7 @@ describe(' - Row editing', () => { ); expect(listener.callCount).to.equal(0); - userEvent.mousePress(getCell(1, 1)); + fireUserEvent.mousePress(getCell(1, 1)); clock.runToLast(); expect(listener.callCount).to.equal(0); }); @@ -995,7 +996,7 @@ describe(' - Row editing', () => { render(); const spiedStopRowEditMode = spyApi(apiRef.current, 'stopRowEditMode'); fireEvent.doubleClick(getCell(0, 1)); - userEvent.mousePress(getCell(1, 1)); + fireUserEvent.mousePress(getCell(1, 1)); clock.runToLast(); expect(spiedStopRowEditMode.callCount).to.equal(1); expect(spiedStopRowEditMode.lastCall.args[0]).to.deep.equal({ @@ -1014,7 +1015,7 @@ describe(' - Row editing', () => { act(() => { apiRef.current.setEditCellValue({ id: 0, field: 'currencyPair', value: 'USD GBP' }); }); - userEvent.mousePress(getCell(1, 1)); + fireUserEvent.mousePress(getCell(1, 1)); clock.runToLast(); expect(spiedStopRowEditMode.callCount).to.equal(1); expect(spiedStopRowEditMode.lastCall.args[0].ignoreModifications).to.equal(false); @@ -1027,7 +1028,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStop', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); expect(listener.callCount).to.equal(0); fireEvent.keyDown(cell.querySelector('input')!, { key: 'Escape' }); @@ -1057,7 +1058,7 @@ describe(' - Row editing', () => { render(); const spiedStopRowEditMode = spyApi(apiRef.current, 'stopRowEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); fireEvent.keyDown(cell.querySelector('input')!, { key: 'Escape' }); expect(spiedStopRowEditMode.callCount).to.equal(1); @@ -1076,7 +1077,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStop', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); expect(listener.callCount).to.equal(0); fireEvent.keyDown(cell.querySelector('input')!, { key: 'Enter' }); @@ -1106,7 +1107,7 @@ describe(' - Row editing', () => { render(); const spiedStopRowEditMode = spyApi(apiRef.current, 'stopRowEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); fireEvent.keyDown(cell.querySelector('input')!, { key: 'Enter' }); expect(spiedStopRowEditMode.callCount).to.equal(1); @@ -1123,7 +1124,7 @@ describe(' - Row editing', () => { render(); const spiedStopRowEditMode = spyApi(apiRef.current, 'stopRowEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); act(() => { apiRef.current.setEditCellValue({ id: 0, field: 'currencyPair', value: 'USD GBP' }); @@ -1140,7 +1141,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStop', listener); const cell = getCell(0, 2); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); expect(listener.callCount).to.equal(0); fireEvent.keyDown(cell.querySelector('input')!, { key: 'Tab' }); @@ -1152,7 +1153,7 @@ describe(' - Row editing', () => { const listener = spy(); apiRef.current.subscribeEvent('rowEditStop', listener); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); expect(listener.callCount).to.equal(0); fireEvent.keyDown(cell.querySelector('input')!, { key: 'Tab', shiftKey: true }); @@ -1163,7 +1164,7 @@ describe(' - Row editing', () => { render(); const spiedStopRowEditMode = spyApi(apiRef.current, 'stopRowEditMode'); const cell = getCell(0, 2); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); fireEvent.keyDown(cell.querySelector('input')!, { key: 'Tab' }); expect(spiedStopRowEditMode.callCount).to.equal(1); @@ -1179,7 +1180,7 @@ describe(' - Row editing', () => { render(); const spiedStopRowEditMode = spyApi(apiRef.current, 'stopRowEditMode'); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); fireEvent.keyDown(cell.querySelector('input')!, { key: 'Tab', shiftKey: true }); expect(spiedStopRowEditMode.callCount).to.equal(1); @@ -1196,7 +1197,7 @@ describe(' - Row editing', () => { render(); const spiedStopRowEditMode = spyApi(apiRef.current, 'stopRowEditMode'); const cell = getCell(0, 2); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.doubleClick(cell); await act(() => { apiRef.current.setEditCellValue({ id: 0, field: 'price1M', value: 'USD GBP' }); diff --git a/packages/x-data-grid-pro/src/tests/rowPinning.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowPinning.DataGridPro.test.tsx index bb57b3b1aad88..c4ca6ea072b6b 100644 --- a/packages/x-data-grid-pro/src/tests/rowPinning.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowPinning.DataGridPro.test.tsx @@ -11,14 +11,7 @@ import { GridColDef, } from '@mui/x-data-grid-pro'; import { getBasicGridData } from '@mui/x-data-grid-generator'; -import { - createRenderer, - fireEvent, - screen, - act, - userEvent, - waitFor, -} from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, screen, act, waitFor } from '@mui/internal-test-utils'; import { $, grid, @@ -30,6 +23,7 @@ import { getRows, microtasks, } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); @@ -368,7 +362,7 @@ describe(' - Row pinning', () => { expect(isRowPinned(getRowById(1), 'top')).to.equal(true, '#1 pinned top'); expect(isRowPinned(getRowById(0), 'top')).to.equal(true, '#0 pinned top'); - userEvent.mousePress(getCell(0, 0)); + fireUserEvent.mousePress(getCell(0, 0)); // first top pinned row expect(getActiveCellRowId()).to.equal('1'); @@ -410,7 +404,7 @@ describe(' - Row pinning', () => { expect(isRowPinned(getRowById(0), 'bottom')).to.equal(true, '#0 pinned top'); expect(isRowPinned(getRowById(1), 'bottom')).to.equal(true, '#1 pinned top'); - userEvent.mousePress(getCell(0, 0)); + fireUserEvent.mousePress(getCell(0, 0)); expect(getActiveCellRowId()).to.equal('2'); fireEvent.keyDown(getCell(0, 0), { key: 'ArrowDown' }); @@ -462,7 +456,7 @@ describe(' - Row pinning', () => { expect(isRowPinned(getRowById(0), 'bottom')).to.equal(true, '#0 pinned bottom'); // top-pinned row - userEvent.mousePress(getCell(0, 3)); + fireUserEvent.mousePress(getCell(0, 3)); expect(getActiveCell()).to.equal('0-3'); expect(getActiveCellRowId()).to.equal('1'); @@ -887,7 +881,7 @@ describe(' - Row pinning', () => { await waitFor(() => { expect(getCell(0, 1).textContent).to.equal('Marcus'); - expect(getCell(4, 1).textContent).to.equal('Tom'); }); + expect(getCell(4, 1).textContent).to.equal('Tom'); }); }); diff --git a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx index 3c5eda2423586..8d0af590260d4 100644 --- a/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowSelection.DataGridPro.test.tsx @@ -210,6 +210,49 @@ describe(' - Row selection', () => { }); expect(selectAllCheckbox).to.have.attr('data-indeterminate', 'false'); }); + + it('should allow to select all the current page rows when props.paginationMode="server"', () => { + function TestDataGridSelectionServerSide({ + rowLength = 4, + }: Omit & + Partial> & { rowLength?: number }) { + apiRef = useGridApiRef(); + const paginationModel = { pageSize: 2, page: 1 }; + + const data = React.useMemo(() => getBasicGridData(rowLength, 2), [rowLength]); + + const rows = data.rows.slice( + paginationModel.pageSize * paginationModel.page, + paginationModel.pageSize * (paginationModel.page + 1), + ); + + return ( +
+ +
+ ); + } + render(); + + const selectAllCheckbox = screen.getByRole('checkbox', { + name: /select all rows/i, + }); + + fireEvent.click(selectAllCheckbox); + expect(apiRef.current.getSelectedRows()).to.have.length(2); + }); }); describe('apiRef: getSelectedRows', () => { 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 8e24fde92b3be..3fa3aec057ddd 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 @@ -1,5 +1,5 @@ import * as React from 'react'; -import { createRenderer, fireEvent, act, userEvent } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, act } from '@mui/internal-test-utils'; import { spy } from 'sinon'; import { expect } from 'chai'; import { @@ -13,6 +13,7 @@ import { getRows, getColumnHeaderCell, } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; import { GridRowModel, useGridApiRef, @@ -766,7 +767,7 @@ describe(' - Rows', () => { it('should focus the clicked cell in the state', () => { render(); - userEvent.mousePress(getCell(0, 0)); + fireUserEvent.mousePress(getCell(0, 0)); expect(apiRef.current.state.focus.cell).to.deep.equal({ id: baselineProps.rows[0].id, field: baselineProps.columns[0].field, @@ -784,7 +785,7 @@ describe(' - Rows', () => { it('should not reset focus when removing a row not containing the focus cell', () => { const { setProps } = render(); - userEvent.mousePress(getCell(1, 0)); + fireUserEvent.mousePress(getCell(1, 0)); setProps({ rows: baselineProps.rows.slice(1) }); expect(gridFocusCellSelector(apiRef)).to.deep.equal({ id: baselineProps.rows[1].id, @@ -795,7 +796,7 @@ describe(' - Rows', () => { it('should set the focus when pressing a key inside a cell', () => { render(); const cell = getCell(1, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'a' }); expect(gridFocusCellSelector(apiRef)).to.deep.equal({ id: baselineProps.rows[1].id, @@ -805,12 +806,12 @@ describe(' - Rows', () => { it('should update the focus when clicking from one cell to another', () => { render(); - userEvent.mousePress(getCell(1, 0)); + fireUserEvent.mousePress(getCell(1, 0)); expect(gridFocusCellSelector(apiRef)).to.deep.equal({ id: baselineProps.rows[1].id, field: baselineProps.columns[0].field, }); - userEvent.mousePress(getCell(2, 1)); + fireUserEvent.mousePress(getCell(2, 1)); expect(gridFocusCellSelector(apiRef)).to.deep.equal({ id: baselineProps.rows[2].id, field: baselineProps.columns[1].field, @@ -819,12 +820,12 @@ describe(' - Rows', () => { it('should reset focus when clicking outside the focused cell', () => { render(); - userEvent.mousePress(getCell(1, 0)); + fireUserEvent.mousePress(getCell(1, 0)); expect(gridFocusCellSelector(apiRef)).to.deep.equal({ id: baselineProps.rows[1].id, field: baselineProps.columns[0].field, }); - userEvent.mousePress(document.body); + fireUserEvent.mousePress(document.body); expect(gridFocusCellSelector(apiRef)).to.deep.equal(null); }); @@ -832,9 +833,9 @@ describe(' - Rows', () => { const handleCellFocusOut = spy(); render(); apiRef.current.subscribeEvent('cellFocusOut', handleCellFocusOut); - userEvent.mousePress(getCell(1, 0)); + fireUserEvent.mousePress(getCell(1, 0)); expect(handleCellFocusOut.callCount).to.equal(0); - userEvent.mousePress(document.body); + fireUserEvent.mousePress(document.body); expect(handleCellFocusOut.callCount).to.equal(1); expect(handleCellFocusOut.args[0][0].id).to.equal(baselineProps.rows[1].id); expect(handleCellFocusOut.args[0][0].field).to.equal(baselineProps.columns[0].field); @@ -851,7 +852,7 @@ describe(' - Rows', () => { />, ); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); }).not.to.throw(); }); @@ -877,7 +878,7 @@ describe(' - Rows', () => { />, ); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); const columnHeaderCell = getColumnHeaderCell(0); fireEvent.focus(columnHeaderCell); }).not.to.throw(); diff --git a/packages/x-data-grid-pro/src/tests/treeData.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/treeData.DataGridPro.test.tsx index 9f80a0d0858e7..61a722c634f8e 100644 --- a/packages/x-data-grid-pro/src/tests/treeData.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/treeData.DataGridPro.test.tsx @@ -1,10 +1,12 @@ -import { createRenderer, fireEvent, screen, act, userEvent } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, screen, act } from '@mui/internal-test-utils'; import { getCell, getColumnHeaderCell, getColumnHeadersTextContent, getColumnValues, + getRow, } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; @@ -214,7 +216,7 @@ describe(' - Tree data', () => { ]); setProps({ getTreeDataPath: (row) => [...row.name.split('.').reverse()], - } as DataGridProProps); + } as Pick); expect(getColumnValues(1)).to.deep.equal([ 'A', 'A.A', @@ -398,7 +400,7 @@ describe(' - Tree data', () => { it('should toggle expansion when pressing Space while focusing grouping column', () => { render(); expect(getColumnValues(1)).to.deep.equal(['A', 'B', 'C']); - userEvent.mousePress(getCell(0, 0)); + fireUserEvent.mousePress(getCell(0, 0)); expect(getColumnValues(1)).to.deep.equal(['A', 'B', 'C']); fireEvent.keyDown(getCell(0, 0), { key: ' ' }); expect(getColumnValues(1)).to.deep.equal(['A', 'A.A', 'A.B', 'B', 'C']); @@ -591,6 +593,47 @@ describe(' - Tree data', () => { expect(getColumnValues(0)).to.deep.equal(['B (1)', 'D', 'D (1)', 'A']); }); + + it('should keep the correct count of the children and descendants in the filter state', () => { + render( + , + ); + + const { filteredChildrenCountLookup, filteredDescendantCountLookup } = + apiRef.current.state.filter; + + expect(filteredChildrenCountLookup.A).to.equal(3); + expect(filteredDescendantCountLookup.A).to.equal(5); + + expect(filteredChildrenCountLookup.B).to.equal(1); + expect(filteredDescendantCountLookup.B).to.equal(1); + + expect(filteredChildrenCountLookup.C).to.equal(undefined); + expect(filteredDescendantCountLookup.C).to.equal(undefined); + + act(() => { + apiRef.current.updateRows([{ name: 'A.D' }]); + }); + + expect(apiRef.current.state.filter.filteredChildrenCountLookup.A).to.equal(4); + expect(apiRef.current.state.filter.filteredDescendantCountLookup.A).to.equal(6); + }); }); describe('sorting', () => { @@ -725,6 +768,67 @@ describe(' - Tree data', () => { }); }); + describe('accessibility', () => { + it('should add necessary treegrid aria attributes to the rows', () => { + render(); + + expect(getRow(0).getAttribute('aria-level')).to.equal('1'); // A + expect(getRow(1).getAttribute('aria-level')).to.equal('2'); // A.A + expect(getRow(1).getAttribute('aria-posinset')).to.equal('1'); + expect(getRow(1).getAttribute('aria-setsize')).to.equal('2'); + expect(getRow(2).getAttribute('aria-level')).to.equal('2'); // A.B + expect(getRow(4).getAttribute('aria-posinset')).to.equal('1'); // B.A + }); + + it('should adjust treegrid aria attributes after filtering', () => { + render( + , + ); + + expect(getRow(0).getAttribute('aria-level')).to.equal('1'); // A + expect(getRow(1).getAttribute('aria-level')).to.equal('2'); // A.B + expect(getRow(1).getAttribute('aria-posinset')).to.equal('1'); + expect(getRow(1).getAttribute('aria-setsize')).to.equal('1'); // A.A is filtered out, set size is now 1 + expect(getRow(2).getAttribute('aria-level')).to.equal('1'); // B + expect(getRow(3).getAttribute('aria-posinset')).to.equal('1'); // B.A + expect(getRow(3).getAttribute('aria-setsize')).to.equal('2'); // B.A & B.B + }); + + it('should not add the set specific aria attributes to pinned rows', () => { + render( + , + ); + + expect(getRow(0).getAttribute('aria-rowindex')).to.equal('2'); // header row is 1 + expect(getRow(0).getAttribute('aria-level')).to.equal(null); + expect(getRow(0).getAttribute('aria-posinset')).to.equal(null); + expect(getRow(0).getAttribute('aria-setsize')).to.equal(null); + expect(getRow(1).getAttribute('aria-rowindex')).to.equal('3'); + expect(getRow(1).getAttribute('aria-level')).to.equal('1'); // A + expect(getRow(1).getAttribute('aria-posinset')).to.equal('1'); + expect(getRow(1).getAttribute('aria-setsize')).to.equal('3'); // A, B, C + }); + }); + describe('regressions', () => { // See https://github.com/mui/mui-x/issues/9402 it('should not fail with checkboxSelection', () => { 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 6f79cc1c727c0..081f518c13c61 100644 --- a/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/createRowTree.ts @@ -33,7 +33,7 @@ export const createRowTree = (params: CreateRowTreeParams): GridRowTreeCreationV previousTree: params.previousTree, id: node.id, path: node.path, - hasServerChildren: node.hasServerChildren, + serverChildrenCount: node.serverChildrenCount, onDuplicatePath: params.onDuplicatePath, treeDepths, isGroupExpandedByDefault: params.isGroupExpandedByDefault, 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 86cd27e020261..d006eb177b34e 100644 --- a/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/insertDataRowInTree.ts @@ -59,7 +59,7 @@ interface InsertDataRowInTreeParams { onDuplicatePath?: GridTreePathDuplicateHandler; isGroupExpandedByDefault?: DataGridProProps['isGroupExpandedByDefault']; defaultGroupingExpansionDepth: number; - hasServerChildren?: boolean; + serverChildrenCount?: number; groupsToFetch?: Set; } @@ -79,7 +79,7 @@ export const insertDataRowInTree = ({ onDuplicatePath, isGroupExpandedByDefault, defaultGroupingExpansionDepth, - hasServerChildren, + serverChildrenCount, groupsToFetch, }: InsertDataRowInTreeParams) => { let parentNodeId = GRID_ROOT_GROUP_ID; @@ -99,7 +99,7 @@ export const insertDataRowInTree = ({ // We create a leaf node for the data row. if (existingNodeIdWithPartialPath == null) { let node: GridLeafNode | GridDataSourceGroupNode; - if (hasServerChildren) { + if (serverChildrenCount !== undefined && serverChildrenCount !== 0) { node = { type: 'group', id, @@ -112,7 +112,7 @@ export const insertDataRowInTree = ({ children: [], childrenFromPath: {}, childrenExpanded: false, - hasServerChildren: true, + serverChildrenCount, }; const shouldFetchChildren = checkGroupChildrenExpansion( node, 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 871d3fc86c97f..b9daf689647e8 100644 --- a/packages/x-data-grid-pro/src/utils/tree/models.ts +++ b/packages/x-data-grid-pro/src/utils/tree/models.ts @@ -8,7 +8,7 @@ export interface RowTreeBuilderGroupingCriterion { export interface RowTreeBuilderNode { id: GridRowId; path: RowTreeBuilderGroupingCriterion[]; - hasServerChildren?: boolean; + serverChildrenCount?: number; } /** 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 0c7ddc5144033..e56ca16d74f68 100644 --- a/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts +++ b/packages/x-data-grid-pro/src/utils/tree/updateRowTree.ts @@ -36,7 +36,7 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV : new Set([]); for (let i = 0; i < params.nodes.inserted.length; i += 1) { - const { id, path, hasServerChildren } = params.nodes.inserted[i]; + const { id, path, serverChildrenCount } = params.nodes.inserted[i]; insertDataRowInTree({ previousTree: params.previousTree, @@ -45,7 +45,7 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV updatedGroupsManager, id, path, - hasServerChildren, + serverChildrenCount, onDuplicatePath: params.onDuplicatePath, isGroupExpandedByDefault: params.isGroupExpandedByDefault, defaultGroupingExpansionDepth: params.defaultGroupingExpansionDepth, @@ -65,7 +65,7 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV } for (let i = 0; i < params.nodes.modified.length; i += 1) { - const { id, path, hasServerChildren } = params.nodes.modified[i]; + const { id, path, serverChildrenCount } = params.nodes.modified[i]; const pathInPreviousTree = getNodePathInTree({ tree, id }); const isInSameGroup = isDeepEqual(pathInPreviousTree, path); @@ -84,7 +84,7 @@ export const updateRowTree = (params: UpdateRowTreeParams): GridRowTreeCreationV updatedGroupsManager, id, path, - hasServerChildren, + serverChildrenCount, onDuplicatePath: params.onDuplicatePath, isGroupExpandedByDefault: params.isGroupExpandedByDefault, defaultGroupingExpansionDepth: params.defaultGroupingExpansionDepth, diff --git a/packages/x-data-grid/package.json b/packages/x-data-grid/package.json index 4018bbb601cb0..317df3d87c56f 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.12.0", + "version": "7.15.0", "description": "The Community plan edition of the Data Grid components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -47,25 +47,37 @@ "directory": "packages/x-data-grid" }, "dependencies": { - "@babel/runtime": "^7.25.0", - "@mui/system": "^5.16.5", - "@mui/utils": "^5.16.5", + "@babel/runtime": "^7.25.4", + "@mui/utils": "^5.16.6", "@mui/x-internals": "workspace:*", "clsx": "^2.1.1", "prop-types": "^15.8.1", - "reselect": "^4.1.8" + "reselect": "^5.1.0" }, "peerDependencies": { - "@mui/material": "^5.15.14", + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.5", + "@mui/internal-test-utils": "^1.0.11", "@mui/joy": "^5.0.0-beta.48", + "@mui/material": "^5.16.7", + "@mui/system": "^5.16.7", "@mui/types": "^7.2.15", "@types/prop-types": "^15.7.12", - "rimraf": "^5.0.9" + "rimraf": "^5.0.10" }, "engines": { "node": ">=14.0.0" diff --git a/packages/x-data-grid/src/DataGrid/DataGrid.tsx b/packages/x-data-grid/src/DataGrid/DataGrid.tsx index ab054cbab9179..d428bf358b411 100644 --- a/packages/x-data-grid/src/DataGrid/DataGrid.tsx +++ b/packages/x-data-grid/src/DataGrid/DataGrid.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { GridBody, GridFooterPlaceholder, GridHeader, GridRoot } from '../components'; +import { useGridAriaAttributes } from '../hooks/utils/useGridAriaAttributes'; +import { useGridRowAriaAttributes } from '../hooks/features/rows/useGridRowAriaAttributes'; import { DataGridProcessedProps, DataGridProps } from '../models/props/DataGridProps'; import { GridContextProvider } from '../context/GridContextProvider'; import { useDataGridComponent } from './useDataGridComponent'; @@ -15,6 +17,12 @@ import { export type { GridSlotsComponent as GridSlots } from '../models'; +const configuration = { + hooks: { + useGridAriaAttributes, + useGridRowAriaAttributes, + }, +}; let propValidators: PropValidator[]; if (process.env.NODE_ENV !== 'production') { @@ -45,7 +53,7 @@ const DataGridRaw = React.forwardRef(function DataGrid + (undefined); + +if (process.env.NODE_ENV !== 'production') { + GridConfigurationContext.displayName = 'GridConfigurationContext'; +} diff --git a/packages/x-data-grid/src/components/GridHeaders.tsx b/packages/x-data-grid/src/components/GridHeaders.tsx index b6f2f6b290e01..c2df6d301ce17 100644 --- a/packages/x-data-grid/src/components/GridHeaders.tsx +++ b/packages/x-data-grid/src/components/GridHeaders.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { fastMemo } from '../utils/fastMemo'; +import { fastMemo } from '@mui/x-internals/fastMemo'; import { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContext'; import { useGridSelector } from '../hooks/utils/useGridSelector'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; diff --git a/packages/x-data-grid/src/components/GridRow.tsx b/packages/x-data-grid/src/components/GridRow.tsx index ee26a2b20c1f6..54317be883442 100644 --- a/packages/x-data-grid/src/components/GridRow.tsx +++ b/packages/x-data-grid/src/components/GridRow.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import { fastMemo } from '../utils/fastMemo'; +import { fastMemo } from '@mui/x-internals/fastMemo'; import { GridRowEventLookup } from '../models/events'; import { GridRowId, GridRowModel } from '../models/gridRows'; import { GridEditModes, GridRowModes, GridCellModes } from '../models/gridEditRowModel'; @@ -24,11 +24,11 @@ import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../constants/gridDetailPanelTogg import type { GridDimensions } from '../hooks/features/dimensions'; import { gridSortModelSelector } from '../hooks/features/sorting/gridSortingSelector'; import { gridRowMaximumTreeDepthSelector } from '../hooks/features/rows/gridRowsSelector'; -import { gridColumnGroupsHeaderMaxDepthSelector } from '../hooks/features/columnGrouping/gridColumnGroupsSelector'; import { gridEditRowsStateSelector } from '../hooks/features/editing/gridEditingSelectors'; import { PinnedPosition, gridPinnedColumnPositionLookup } from './cell/GridCell'; import { GridScrollbarFillerCell as ScrollbarFiller } from './GridScrollbarFillerCell'; import { getPinnedCellOffset } from '../internals/utils/getPinnedCellOffset'; +import { useGridConfiguration } from '../hooks/utils/useGridConfiguration'; export interface GridRowProps extends React.HTMLAttributes { row: GridRowModel; @@ -112,12 +112,12 @@ const GridRow = React.forwardRef(function GridRow( ...other } = props; const apiRef = useGridApiContext(); + const configuration = useGridConfiguration(); const ref = React.useRef(null); const rootProps = useGridRootProps(); const currentPage = useGridVisibleRows(apiRef, rootProps); const sortModel = useGridSelector(apiRef, gridSortModelSelector); const treeDepth = useGridSelector(apiRef, gridRowMaximumTreeDepthSelector); - const headerGroupingMaxDepth = useGridSelector(apiRef, gridColumnGroupsHeaderMaxDepthSelector); const columnPositions = useGridSelector(apiRef, gridColumnPositionsSelector); const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); const handleRef = useForkRef(ref, refProp); @@ -137,8 +137,6 @@ const GridRow = React.forwardRef(function GridRow( focusedColumnIndex < visibleColumns.length - pinnedColumns.right.length && focusedColumnIndex >= renderContext.lastColumnIndex; - const ariaRowIndex = index + headerGroupingMaxDepth + 2; // 1 for the header row and 1 as it's 1-based - const classes = composeGridClasses(rootProps.classes, { root: [ 'row', @@ -151,6 +149,7 @@ const GridRow = React.forwardRef(function GridRow( rowHeight === 'auto' && 'row--dynamicHeight', ], }); + const getRowAriaAttributes = configuration.hooks.useGridRowAriaAttributes(); React.useLayoutEffect(() => { if (currentPage.range) { @@ -307,6 +306,7 @@ const GridRow = React.forwardRef(function GridRow( }, [isNotVisible, rowHeight, styleProp, minHeight, sizes, rootProps.rowSpacingType]); const rowClassNames = apiRef.current.unstable_applyPipeProcessors('rowClassName', [], rowId); + const ariaAttributes = rowNode ? getRowAriaAttributes(rowNode, index) : undefined; if (typeof rootProps.getRowClassName === 'function') { const indexRelativeToCurrentPage = index - (currentPage.range?.firstRowIndex || 0); @@ -479,9 +479,8 @@ const GridRow = React.forwardRef(function GridRow( data-rowindex={index} role="row" className={clsx(...rowClassNames, classes.root, className)} - aria-rowindex={ariaRowIndex} - aria-selected={selected} style={style} + {...ariaAttributes} {...eventHandlers} {...other} > diff --git a/packages/x-data-grid/src/components/GridScrollArea.tsx b/packages/x-data-grid/src/components/GridScrollArea.tsx index 9b0cc4e330b1a..fda459d58ab95 100644 --- a/packages/x-data-grid/src/components/GridScrollArea.tsx +++ b/packages/x-data-grid/src/components/GridScrollArea.tsx @@ -5,6 +5,7 @@ import { unstable_useEventCallback as useEventCallback, } from '@mui/utils'; import { styled } from '@mui/system'; +import { fastMemo } from '@mui/x-internals/fastMemo'; import { DataGridProcessedProps } from '../models/props/DataGridProps'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { getDataGridUtilityClass, gridClasses } from '../constants'; @@ -18,7 +19,6 @@ import { GridScrollParams } from '../models/params/gridScrollParams'; import { GridEventListener } from '../models/events'; import { useTimeout } from '../hooks/utils/useTimeout'; import { getTotalHeaderHeight } from '../hooks/features/columns/gridColumnsUtils'; -import { fastMemo } from '../utils/fastMemo'; const CLIFF = 1; const SLOP = 1.5; diff --git a/packages/x-data-grid/src/components/GridScrollbarFillerCell.tsx b/packages/x-data-grid/src/components/GridScrollbarFillerCell.tsx index fc2fd61381ada..c1caf5aabada3 100644 --- a/packages/x-data-grid/src/components/GridScrollbarFillerCell.tsx +++ b/packages/x-data-grid/src/components/GridScrollbarFillerCell.tsx @@ -6,16 +6,19 @@ const classes = { root: gridClasses.scrollbarFiller, header: gridClasses['scrollbarFiller--header'], borderTop: gridClasses['scrollbarFiller--borderTop'], + borderBottom: gridClasses['scrollbarFiller--borderBottom'], pinnedRight: gridClasses['scrollbarFiller--pinnedRight'], }; function GridScrollbarFillerCell({ header, borderTop = true, + borderBottom, pinnedRight, }: { header?: boolean; borderTop?: boolean; + borderBottom?: boolean; pinnedRight?: boolean; }) { return ( @@ -25,6 +28,7 @@ function GridScrollbarFillerCell({ classes.root, header && classes.header, borderTop && classes.borderTop, + borderBottom && classes.borderBottom, pinnedRight && classes.pinnedRight, )} /> diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index 62643bb382f36..365b65bfd82d1 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -171,7 +171,12 @@ const GridSkeletonLoadingOverlay = React.forwardRef< } if (hasScrollbarFiller) { - rowCells.push( 0} />); + rowCells.push( + 0} + />, + ); } } diff --git a/packages/x-data-grid/src/components/cell/GridActionsCell.tsx b/packages/x-data-grid/src/components/cell/GridActionsCell.tsx index 2abc7adc0e456..39c20b5c639e2 100644 --- a/packages/x-data-grid/src/components/cell/GridActionsCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridActionsCell.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import MenuList from '@mui/material/MenuList'; -import { useTheme } from '@mui/material/styles'; +import { useRtl } from '@mui/system/RtlProvider'; import { unstable_useId as useId } from '@mui/utils'; import { GridRenderCellParams } from '../../models/params/gridCellParams'; import { gridClasses } from '../../constants/gridClasses'; @@ -47,7 +47,7 @@ function GridActionsCell(props: GridActionsCellProps) { const buttonRef = React.useRef(null); const ignoreCallToFocus = React.useRef(false); const touchRippleRefs = React.useRef>({}); - const theme = useTheme(); + const isRtl = useRtl(); const menuId = useId(); const buttonId = useId(); const rootProps = useGridRootProps(); @@ -149,7 +149,7 @@ function GridActionsCell(props: GridActionsCellProps) { } // for rtl mode we need to reverse the direction - const rtlMod = theme.direction === 'rtl' ? -1 : 1; + const rtlMod = isRtl ? -1 : 1; const indexMod = (direction === 'left' ? -1 : 1) * rtlMod; // if the button that should receive focus is disabled go one more step diff --git a/packages/x-data-grid/src/components/cell/GridCell.tsx b/packages/x-data-grid/src/components/cell/GridCell.tsx index 2a196dccb5a73..81ddc96ba4fac 100644 --- a/packages/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridCell.tsx @@ -7,8 +7,7 @@ import { unstable_ownerDocument as ownerDocument, unstable_capitalize as capitalize, } from '@mui/utils'; -import type { GridApiCommunity } from '../../internals'; -import { fastMemo } from '../../utils/fastMemo'; +import { fastMemo } from '@mui/x-internals/fastMemo'; import { doesSupportPreventScroll } from '../../utils/doesSupportPreventScroll'; import { getDataGridUtilityClass, gridClasses } from '../../constants/gridClasses'; import { @@ -75,11 +74,7 @@ export type GridCellProps = { [x: string]: any; // TODO v7: remove this - it breaks type safety }; -type CellParamsWithAPI = GridCellParams & { - api: GridApiCommunity; -}; - -const EMPTY_CELL_PARAMS: CellParamsWithAPI = { +const EMPTY_CELL_PARAMS: GridCellParams = { id: -1, field: '__unset__', row: {}, @@ -183,19 +178,17 @@ const GridCell = React.forwardRef(function GridCe const field = column.field; - const cellParamsWithAPI = useGridSelector( + const cellParams = useGridSelector( apiRef, () => { // This is required because `.getCellParams` tries to get the `state.rows.tree` entry // associated with `rowId`/`fieldId`, but this selector runs after the state has been // updated, while `rowId`/`fieldId` reference an entry in the old state. try { - const cellParams = apiRef.current.getCellParams( + const result = apiRef.current.getCellParams( rowId, field, ); - - const result = cellParams as CellParamsWithAPI; result.api = apiRef.current; return result; } catch (e) { @@ -215,7 +208,7 @@ const GridCell = React.forwardRef(function GridCe }), ); - const { cellMode, hasFocus, isEditable = false, value } = cellParamsWithAPI; + const { cellMode, hasFocus, isEditable = false, value } = cellParams; const canManageOwnFocus = column.type === 'actions' && @@ -223,7 +216,7 @@ const GridCell = React.forwardRef(function GridCe .getActions?.(apiRef.current.getRowParams(rowId)) .some((action) => !action.props.disabled); const tabIndex = - (cellMode === 'view' || !isEditable) && !canManageOwnFocus ? cellParamsWithAPI.tabIndex : -1; + (cellMode === 'view' || !isEditable) && !canManageOwnFocus ? cellParams.tabIndex : -1; const { classes: rootClasses, getCellClassName } = rootProps; @@ -243,7 +236,7 @@ const GridCell = React.forwardRef(function GridCe if (column.cellClassName) { classNames.push( typeof column.cellClassName === 'function' - ? column.cellClassName(cellParamsWithAPI) + ? column.cellClassName(cellParams) : column.cellClassName, ); } @@ -253,10 +246,10 @@ const GridCell = React.forwardRef(function GridCe } if (getCellClassName) { - classNames.push(getCellClassName(cellParamsWithAPI)); + classNames.push(getCellClassName(cellParams)); } - const valueToRender = cellParamsWithAPI.formattedValue ?? value; + const valueToRender = cellParams.formattedValue ?? value; const cellRef = React.useRef(null); const handleRef = useForkRef(ref, cellRef); const focusElementRef = React.useRef(null); @@ -373,7 +366,7 @@ const GridCell = React.forwardRef(function GridCe } }, [hasFocus, cellMode, apiRef]); - if (cellParamsWithAPI === EMPTY_CELL_PARAMS) { + if (cellParams === EMPTY_CELL_PARAMS) { return null; } @@ -411,7 +404,7 @@ const GridCell = React.forwardRef(function GridCe let title: string | undefined; if (editCellState === null && column.renderCell) { - children = column.renderCell(cellParamsWithAPI); + children = column.renderCell(cellParams); } if (editCellState !== null && column.renderEditCell) { @@ -422,10 +415,10 @@ const GridCell = React.forwardRef(function GridCe const formattedValue = column.valueFormatter ? column.valueFormatter(editCellState.value as never, updatedRow, column, apiRef) - : cellParamsWithAPI.formattedValue; + : cellParams.formattedValue; const params: GridRenderEditCellParams = { - ...cellParamsWithAPI, + ...cellParams, row: updatedRow, formattedValue, ...editCellStateRest, diff --git a/packages/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx b/packages/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx index 328bd01109699..baab9cfbdcd14 100644 --- a/packages/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridEditSingleSelectCell.tsx @@ -4,7 +4,6 @@ import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils'; import { SelectProps, SelectChangeEvent } from '@mui/material/Select'; import { GridCellEditStopReasons } from '../../models/params/gridEditCellParams'; import { GridRenderEditCellParams } from '../../models/params/gridCellParams'; -import { isEscapeKey } from '../../utils/keyboardUtils'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { GridEditModes } from '../../models/gridEditRowModel'; import { @@ -111,13 +110,14 @@ function GridEditSingleSelectCell(props: GridEditSingleSelectCellProps) { setOpen(false); return; } - if (reason === 'backdropClick' || isEscapeKey(event.key)) { + if (reason === 'backdropClick' || event.key === 'Escape') { const params = apiRef.current.getCellParams(id, field); apiRef.current.publishEvent('cellEditStop', { ...params, - reason: isEscapeKey(event.key) - ? GridCellEditStopReasons.escapeKeyDown - : GridCellEditStopReasons.cellFocusOut, + reason: + event.key === 'Escape' + ? GridCellEditStopReasons.escapeKeyDown + : GridCellEditStopReasons.cellFocusOut, }); } }; diff --git a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx index 2441f413d65cf..79fb1e5d79397 100644 --- a/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridSkeletonCell.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; import Skeleton from '@mui/material/Skeleton'; import { unstable_composeClasses as composeClasses, unstable_capitalize as capitalize, } from '@mui/utils'; -import clsx from 'clsx'; -import { fastMemo } from '../../utils/fastMemo'; +import { fastMemo } from '@mui/x-internals/fastMemo'; import { createRandomNumberGenerator } from '../../utils/utils'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; diff --git a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx index 920c197c6da7f..a0746768bdd65 100644 --- a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; import { unstable_composeClasses as composeClasses, unstable_useId as useId } from '@mui/utils'; -import { fastMemo } from '../../utils/fastMemo'; +import { fastMemo } from '@mui/x-internals/fastMemo'; import { GridStateColDef } from '../../models/colDef/gridColDef'; import { GridSortDirection } from '../../models/gridSortModel'; import { useGridPrivateApiContext } from '../../hooks/utils/useGridPrivateApiContext'; diff --git a/packages/x-data-grid/src/components/columnHeaders/GridGenericColumnHeaderItem.tsx b/packages/x-data-grid/src/components/columnHeaders/GridGenericColumnHeaderItem.tsx index f7a9978c767e4..6e397e2aa4086 100644 --- a/packages/x-data-grid/src/components/columnHeaders/GridGenericColumnHeaderItem.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/GridGenericColumnHeaderItem.tsx @@ -104,8 +104,6 @@ const GridGenericColumnHeaderItem = React.forwardRef(function GridGenericColumnH ...style, height, width, - minWidth: width, - maxWidth: width, }} role="columnheader" tabIndex={tabIndex} diff --git a/packages/x-data-grid/src/components/columnSelection/GridHeaderCheckbox.tsx b/packages/x-data-grid/src/components/columnSelection/GridHeaderCheckbox.tsx index 71e12c5db38be..f968f9108669e 100644 --- a/packages/x-data-grid/src/components/columnSelection/GridHeaderCheckbox.tsx +++ b/packages/x-data-grid/src/components/columnSelection/GridHeaderCheckbox.tsx @@ -129,11 +129,16 @@ const GridHeaderCheckbox = React.forwardRef
, ); - userEvent.mousePress(getCell(0, 3)); + fireUserEvent.mousePress(getCell(0, 3)); expect(getActiveCell()).to.equal('0-3'); fireEvent.keyDown(getCell(0, 3), { key: 'PageDown' }); @@ -200,7 +201,7 @@ describe(' - Column spanning', () => { , ); - userEvent.mousePress(getCell(2, 1)); + fireUserEvent.mousePress(getCell(2, 1)); expect(getActiveCell()).to.equal('2-1'); fireEvent.keyDown(getCell(2, 1), { key: 'PageUp' }); @@ -220,7 +221,7 @@ describe(' - Column spanning', () => { , ); - userEvent.mousePress(getCell(1, 3)); + fireUserEvent.mousePress(getCell(1, 3)); expect(getActiveCell()).to.equal('1-3'); // start editing @@ -239,7 +240,7 @@ describe(' - Column spanning', () => { , ); - userEvent.mousePress(getCell(1, 1)); + fireUserEvent.mousePress(getCell(1, 1)); expect(getActiveCell()).to.equal('1-1'); // start editing @@ -257,7 +258,7 @@ describe(' - Column spanning', () => { , ); - userEvent.mousePress(getCell(0, 2)); + fireUserEvent.mousePress(getCell(0, 2)); expect(getActiveCell()).to.equal('0-2'); // start editing @@ -318,7 +319,7 @@ describe(' - Column spanning', () => { , ); - userEvent.mousePress(getCell(1, 1)); + fireUserEvent.mousePress(getCell(1, 1)); expect(getActiveCell()).to.equal('1-1'); fireEvent.keyDown(getCell(1, 1), { key: 'ArrowDown' }); @@ -352,7 +353,7 @@ describe(' - Column spanning', () => { , ); - userEvent.mousePress(getCell(0, 0)); + fireUserEvent.mousePress(getCell(0, 0)); fireEvent.keyDown(getCell(0, 0), { key: 'ArrowRight' }); document.querySelector(`.${gridClasses.virtualScroller}`)!.dispatchEvent(new Event('scroll')); @@ -428,7 +429,7 @@ describe(' - Column spanning', () => { , ); - userEvent.mousePress(getCell(0, 0)); + fireUserEvent.mousePress(getCell(0, 0)); expect(getActiveCell()).to.equal('0-0'); fireEvent.keyDown(getCell(0, 0), { key: 'ArrowDown' }); @@ -459,7 +460,7 @@ describe(' - Column spanning', () => { , ); - userEvent.mousePress(getCell(0, 0)); + fireUserEvent.mousePress(getCell(0, 0)); const virtualScroller = document.querySelector( `.${gridClasses.virtualScroller}`, diff --git a/packages/x-data-grid/src/tests/columnsVisibility.DataGrid.test.tsx b/packages/x-data-grid/src/tests/columnsVisibility.DataGrid.test.tsx index 3771593c4f902..0adf925558ef2 100644 --- a/packages/x-data-grid/src/tests/columnsVisibility.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/columnsVisibility.DataGrid.test.tsx @@ -126,12 +126,12 @@ describe(' - Columns visibility', () => { /> ); } - const { getByRole } = render(); + render(); expect(getColumnHeadersTextContent()).to.deep.equal(['id']); - fireEvent.click(getByRole('button', { name: 'Select columns' })); - const showHideAllCheckbox = getByRole('checkbox', { name: 'Show/Hide All' }); + fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); + const showHideAllCheckbox = screen.getByRole('checkbox', { name: 'Show/Hide All' }); // Hide all fireEvent.click(showHideAllCheckbox); @@ -149,7 +149,7 @@ describe(' - Columns visibility', () => { // Fixes (1) and (2) in https://github.com/mui/mui-x/issues/7393#issuecomment-1372129661 it('should not show hidden non hideable columns when "Show/Hide All" is clicked', () => { - const { getByRole } = render( + render( - Columns visibility', () => { />, ); - fireEvent.click(getByRole('button', { name: 'Select columns' })); - const showHideAllCheckbox = getByRole('checkbox', { name: 'Show/Hide All' }); + fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); + const showHideAllCheckbox = screen.getByRole('checkbox', { name: 'Show/Hide All' }); // Hide all fireEvent.click(showHideAllCheckbox); expect(getColumnHeadersTextContent()).to.deep.equal([]); @@ -267,7 +267,7 @@ describe(' - Columns visibility', () => { }); it('should hide `Show/Hide all` in columns management when `disableShowHideToggle` is `true`', () => { - const { setProps, getByRole, queryByRole } = render( + const { setProps } = render( - Columns visibility', () => { fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); // check if `Show/Hide all` checkbox is present initially - expect(getByRole('checkbox', { name: 'Show/Hide All' })).not.to.equal(null); + expect(screen.getByRole('checkbox', { name: 'Show/Hide All' })).not.to.equal(null); setProps({ slotProps: { columnsManagement: { @@ -287,11 +287,11 @@ describe(' - Columns visibility', () => { }); // check if `Show/Hide All` checkbox is not present after setting `slotProps` - expect(queryByRole('checkbox', { name: 'Show/Hide All' })).to.equal(null); + expect(screen.queryByRole('checkbox', { name: 'Show/Hide All' })).to.equal(null); }); it('should hide `Reset` in columns panel when `disableResetButton` is `true`', () => { - const { setProps, getByRole, queryByRole } = render( + const { setProps } = render( - Columns visibility', () => { fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); // check if Reset button is present initially - expect(getByRole('button', { name: 'Reset' })).not.to.equal(null); + expect(screen.getByRole('button', { name: 'Reset' })).not.to.equal(null); setProps({ slotProps: { columnsManagement: { @@ -310,11 +310,11 @@ describe(' - Columns visibility', () => { }, }); // check if Reset button is not present after setting slotProps - expect(queryByRole('button', { name: 'Reset' })).to.equal(null); + expect(screen.queryByRole('button', { name: 'Reset' })).to.equal(null); }); it('should reset the columns to initial columns state when `Reset` button is clicked in columns management panel', () => { - const { getByRole } = render( + render( - Columns visibility', () => { expect(getColumnHeadersTextContent()).to.deep.equal(['id', 'idBis']); fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); - const resetButton = getByRole('button', { name: 'Reset' }); + const resetButton = screen.getByRole('button', { name: 'Reset' }); expect(resetButton).to.have.attribute('disabled'); // Hide `idBis` column @@ -363,7 +363,7 @@ describe(' - Columns visibility', () => { it('should avoid toggling columns provided by `getTogglableColumns` prop on `Show/Hide All`', () => { const getTogglableColumns = (cols: GridColDef[]) => cols.filter((column) => column.field !== 'idBis').map((column) => column.field); - const { getByRole } = render( + render( - Columns visibility', () => { />, ); - fireEvent.click(getByRole('button', { name: 'Select columns' })); - const showHideAllCheckbox = getByRole('checkbox', { name: 'Show/Hide All' }); + fireEvent.click(screen.getByRole('button', { name: 'Select columns' })); + const showHideAllCheckbox = screen.getByRole('checkbox', { name: 'Show/Hide All' }); fireEvent.click(showHideAllCheckbox); expect(getColumnHeadersTextContent()).to.deep.equal(['idBis']); diff --git a/packages/x-data-grid/src/tests/density.DataGrid.test.tsx b/packages/x-data-grid/src/tests/density.DataGrid.test.tsx index e1a2e2b3148fa..aaad84e1c4fe2 100644 --- a/packages/x-data-grid/src/tests/density.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/density.DataGrid.test.tsx @@ -106,13 +106,13 @@ describe(' - Density', () => { ); } - const { setProps, getByText } = render(); + const { setProps } = render(); expectHeight(rowHeight); - fireEvent.click(getByText('Density')); + fireEvent.click(screen.getByText('Density')); clock.tick(100); - fireEvent.click(getByText('Compact')); + fireEvent.click(screen.getByText('Compact')); // Not updated because of the controlled prop expectHeight(rowHeight); @@ -138,9 +138,9 @@ describe(' - Density', () => { ); } - const { getByText } = render(); - fireEvent.click(getByText('Density')); - fireEvent.click(getByText('Comfortable')); + render(); + fireEvent.click(screen.getByText('Density')); + fireEvent.click(screen.getByText('Comfortable')); expect(onDensityChange.callCount).to.equal(1); expect(onDensityChange.firstCall.args[0]).to.equal('comfortable'); }); @@ -149,7 +149,7 @@ describe(' - Density', () => { describe('density selection menu', () => { it('should increase grid density when selecting compact density', () => { const rowHeight = 30; - const { getByText } = render( + render(
- Density', () => {
, ); - fireEvent.click(getByText('Density')); + fireEvent.click(screen.getByText('Density')); clock.tick(100); - fireEvent.click(getByText('Compact')); + fireEvent.click(screen.getByText('Compact')); expectHeight(rowHeight * COMPACT_DENSITY_FACTOR); }); it('should decrease grid density when selecting comfortable density', () => { const rowHeight = 30; - const { getByText } = render( + render(
- Density', () => {
, ); - fireEvent.click(getByText('Density')); - fireEvent.click(getByText('Comfortable')); + fireEvent.click(screen.getByText('Density')); + fireEvent.click(screen.getByText('Comfortable')); expectHeight(rowHeight * COMFORTABLE_DENSITY_FACTOR); }); diff --git a/packages/x-data-grid/src/tests/export.DataGrid.test.tsx b/packages/x-data-grid/src/tests/export.DataGrid.test.tsx index 76852db959277..fd39af7dd1ad2 100644 --- a/packages/x-data-grid/src/tests/export.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/export.DataGrid.test.tsx @@ -117,6 +117,31 @@ describe(' - Export', () => { ].join('\r\n'), ); }); + + it('should export `undefined` and `null` values as blank', async () => { + render( +
+ +
, + ); + fireEvent.click(screen.getByRole('button', { name: 'Export' })); + clock.runToLast(); + expect(screen.queryByRole('menu')).not.to.equal(null); + fireEvent.click(screen.getByRole('menuitem', { name: 'Download as CSV' })); + expect(spyCreateObjectURL.callCount).to.equal(1); + const csv = await spyCreateObjectURL.lastCall.firstArg.text(); + + expect(csv).to.equal(['name', 'Name', '', '', '1234'].join('\r\n')); + }); }); describe('component: GridToolbarExport', () => { diff --git a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx index 27c196256ecde..c27dbaf589426 100644 --- a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { createRenderer, fireEvent, screen, act, userEvent } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, screen, act } from '@mui/internal-test-utils'; import { spy } from 'sinon'; import { expect } from 'chai'; import { @@ -10,6 +10,7 @@ import { getColumnValues, getRow, } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; import { DataGrid, DataGridProps, @@ -74,7 +75,7 @@ describe(' - Keyboard', () => { it('should move to cell below when pressing "ArrowDown" on a cell on the 1st page', () => { render(); const cell = getCell(8, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'ArrowDown' }); expect(getActiveCell()).to.equal('9-1'); @@ -85,7 +86,7 @@ describe(' - Keyboard', () => { it('should move to cell below when pressing "ArrowDown" on a cell on the 2nd page', () => { render(); const cell = getCell(18, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('18-1'); fireEvent.keyDown(document.activeElement!, { key: 'ArrowDown' }); expect(getActiveCell()).to.equal('19-1'); @@ -96,7 +97,7 @@ describe(' - Keyboard', () => { it('should move to the cell below when pressing "ArrowDown" on the checkbox selection cell', () => { render(); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('0-0'); fireEvent.keyDown(cell.querySelector('input')!, { key: 'ArrowDown' }); expect(getActiveCell()).to.equal('1-0'); @@ -107,7 +108,7 @@ describe(' - Keyboard', () => { it('should move to the cell above when pressing "ArrowUp" on a cell on the 1st page', () => { render(); const cell = getCell(1, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('1-1'); fireEvent.keyDown(document.activeElement!, { key: 'ArrowUp' }); expect(getActiveCell()).to.equal('0-1'); @@ -118,7 +119,7 @@ describe(' - Keyboard', () => { it('should move to the cell above when pressing "ArrowUp" on a cell on the 2nd page', () => { render(); const cell = getCell(11, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('11-1'); fireEvent.keyDown(document.activeElement!, { key: 'ArrowUp' }); expect(getActiveCell()).to.equal('10-1'); @@ -129,7 +130,7 @@ describe(' - Keyboard', () => { it('should move to the cell right when pressing "ArrowRight" on a cell', () => { render(); const cell = getCell(1, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('1-1'); fireEvent.keyDown(document.activeElement!, { key: 'ArrowRight' }); expect(getActiveCell()).to.equal('1-2'); @@ -140,7 +141,7 @@ describe(' - Keyboard', () => { it('should move to the cell right when pressing "ArrowRight" on the checkbox selection cell', () => { render(); const cell = getCell(1, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('1-0'); fireEvent.keyDown(document.activeElement!, { key: 'ArrowRight' }); expect(getActiveCell()).to.equal('1-1'); @@ -149,7 +150,7 @@ describe(' - Keyboard', () => { it('should move to the cell left when pressing "ArrowLeft" on a cell', () => { render(); const cell = getCell(1, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('1-1'); fireEvent.keyDown(document.activeElement!, { key: 'ArrowLeft' }); expect(getActiveCell()).to.equal('1-0'); @@ -165,7 +166,7 @@ describe(' - Keyboard', () => { render(); const cell = getCell(1, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('1-1'); fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); expect(getActiveCell()).to.equal(`6-1`); @@ -181,7 +182,7 @@ describe(' - Keyboard', () => { render(); const cell = getCell(1, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('1-1'); fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); expect(getActiveCell()).to.equal(`6-1`); @@ -197,7 +198,7 @@ describe(' - Keyboard', () => { render(); const cell = getCell(8, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); expect(getActiveCell()).to.equal(`3-1`); @@ -211,7 +212,7 @@ describe(' - Keyboard', () => { render(); const cell = getCell(3, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('3-1'); fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); @@ -233,7 +234,7 @@ describe(' - Keyboard', () => { fireEvent.click(screen.getByRole('button', { name: /next page/i })); const cell = getCell(13, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('13-1'); fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); @@ -247,7 +248,7 @@ describe(' - Keyboard', () => { it('should navigate to the 1st cell of the current row when pressing "Home"', () => { render(); const cell = getCell(8, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home' }); expect(getActiveCell()).to.equal('8-0'); @@ -259,17 +260,17 @@ describe(' - Keyboard', () => { render(); const cell = getCell(8, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', ctrlKey: true }); expect(getActiveCell()).to.equal('0-0'); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', metaKey: true }); expect(getActiveCell()).to.equal('0-0'); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'Home', shiftKey: true }); expect(getActiveCell()).to.equal('0-0'); @@ -278,7 +279,7 @@ describe(' - Keyboard', () => { it('should navigate to the last cell of the current row when pressing "End"', () => { render(); const cell = getCell(8, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(cell, { key: 'End' }); expect(getActiveCell()).to.equal('8-2'); @@ -290,17 +291,17 @@ describe(' - Keyboard', () => { render(); const cell = getCell(8, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'End', ctrlKey: true }); expect(getActiveCell()).to.equal('9-2'); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'End', metaKey: true }); expect(getActiveCell()).to.equal('9-2'); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('8-1'); fireEvent.keyDown(document.activeElement!, { key: 'End', shiftKey: true }); expect(getActiveCell()).to.equal('9-2'); @@ -404,7 +405,7 @@ describe(' - Keyboard', () => { getColumnHeaderCell(1).querySelector(`button[title="Sort"]`)!; // Simulate click on this button - userEvent.mousePress(columnMenuButton); + fireUserEvent.mousePress(columnMenuButton); columnMenuButton.focus(); fireEvent.keyDown(document.activeElement!, { key: 'ArrowDown' }); @@ -434,7 +435,7 @@ describe(' - Keyboard', () => { , ); const input = screen.getByTestId('custom-input'); - userEvent.mousePress(input); + fireUserEvent.mousePress(input); input.focus(); // Verify that the event is not prevented during the bubbling. @@ -632,7 +633,7 @@ describe(' - Keyboard', () => { , ); const firstCell = getCell(0, 0); - userEvent.mousePress(firstCell); + fireUserEvent.mousePress(firstCell); fireEvent.keyDown(firstCell, { key: 'ArrowRight' }); expect(handleKeyDown.returnValues).to.deep.equal([true]); }); @@ -675,7 +676,7 @@ describe(' - Keyboard', () => { it('should select a row when pressing Space key + shiftKey', () => { render(); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); expect(getActiveCell()).to.equal('0-0'); fireEvent.keyDown(cell, { key: ' ', shiftKey: true }); const row = getRow(0); @@ -715,7 +716,7 @@ describe(' - Keyboard', () => { const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; const firstCell = getCell(0, 0); - userEvent.mousePress(firstCell); + fireUserEvent.mousePress(firstCell); expect(virtualScroller.scrollLeft).to.equal(0); fireEvent.keyDown(firstCell, { key: 'ArrowDown' }); @@ -747,7 +748,7 @@ describe(' - Keyboard', () => { ); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'ArrowLeft' }); expect(getActiveCell()).to.equal(`0-0`); @@ -781,7 +782,7 @@ describe(' - Keyboard', () => { ); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'ArrowLeft' }); expect(getActiveCell()).to.equal(`0-0`); @@ -815,7 +816,7 @@ describe(' - Keyboard', () => { ); const cell = getCell(0, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'ArrowLeft' }); expect(getActiveCell()).to.equal(`0-0`); 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 4f003a3548662..b9840172ee731 100644 --- a/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx @@ -277,13 +277,13 @@ describe(' - Layout & warnings', () => { }, ]; - const { getAllByRole } = render( + render(
, ); - getAllByRole('columnheader').forEach((col: HTMLElement) => { + screen.getAllByRole('columnheader').forEach((col: HTMLElement) => { expect(col).toHaveInlineStyle({ width: '100px' }); }); }); @@ -313,13 +313,13 @@ describe(' - Layout & warnings', () => { }, ]; - const { getAllByRole } = render( + render(
, ); - getAllByRole('columnheader').forEach((col: HTMLElement, index: number) => { + screen.getAllByRole('columnheader').forEach((col: HTMLElement, index: number) => { expect(col).toHaveInlineStyle({ width: `${colWidthValues[index]}px` }); }); }); @@ -887,7 +887,7 @@ describe(' - Layout & warnings', () => { describe('localeText', () => { it('should replace the density selector button label text to "Size"', () => { - const { getByText } = render( + render(
- Layout & warnings', () => {
, ); - expect(getByText('Size')).not.to.equal(null); + expect(screen.getByText('Size')).not.to.equal(null); }); it('should support translations in the theme', () => { @@ -927,23 +927,21 @@ describe(' - Layout & warnings', () => { ); } - const { setProps, getByText } = render( - , - ); - expect(getByText('Density')).not.to.equal(null); + const { setProps } = render(); + expect(screen.getByText('Density')).not.to.equal(null); setProps({ localeText: { toolbarDensity: 'Densidade' } }); - expect(getByText('Densidade')).not.to.equal(null); + expect(screen.getByText('Densidade')).not.to.equal(null); }); }); describe('non-strict mode', () => { - const renderer = createRenderer({ strict: false }); + const { render: innerRender } = createRenderer({ strict: false }); it('should render in JSDOM', function test() { if (!/jsdom/.test(window.navigator.userAgent)) { this.skip(); // Only run in JSDOM } - renderer.render( + innerRender(
, diff --git a/packages/x-data-grid/src/tests/materialVersion.test.tsx b/packages/x-data-grid/src/tests/materialVersion.test.tsx new file mode 100644 index 0000000000000..dc0c0a7e6ee01 --- /dev/null +++ b/packages/x-data-grid/src/tests/materialVersion.test.tsx @@ -0,0 +1,5 @@ +import materialPackageJson from '@mui/material/package.json'; +import { checkMaterialVersion } from 'test/utils/checkMaterialVersion'; +import packageJson from '../../package.json'; + +checkMaterialVersion({ packageJson, materialPackageJson }); diff --git a/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx b/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx index 15693031a3be9..94421d70f768b 100644 --- a/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { spy, stub, SinonStub, SinonSpy } from 'sinon'; import { expect } from 'chai'; -import { createRenderer, fireEvent, screen, userEvent, waitFor } from '@mui/internal-test-utils'; +import { createRenderer, fireEvent, screen, waitFor } from '@mui/internal-test-utils'; import { DataGrid, DataGridProps, @@ -13,6 +13,7 @@ import { } from '@mui/x-data-grid'; import { useBasicDemoData } from '@mui/x-data-grid-generator'; import { getCell, getColumnValues, getRows } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); @@ -631,7 +632,7 @@ describe(' - Pagination', () => { pageSizeOptions={[1]} />, ); - userEvent.mousePress(getCell(0, 0)); + fireUserEvent.mousePress(getCell(0, 0)); fireEvent.click(screen.getByRole('button', { name: /next page/i })); expect(getCell(1, 0)).to.have.attr('tabindex', '0'); }); diff --git a/packages/x-data-grid/src/tests/quickFiltering.DataGrid.test.tsx b/packages/x-data-grid/src/tests/quickFiltering.DataGrid.test.tsx index dec9719987131..44d608d5602fd 100644 --- a/packages/x-data-grid/src/tests/quickFiltering.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/quickFiltering.DataGrid.test.tsx @@ -480,23 +480,27 @@ describe(' - Quick filter', () => { } it('should not ignore diacritics by default', () => { - let renderer = render(); + const { unmount } = render(); expect(getColumnValues(0)).to.deep.equal([]); - renderer.unmount(); + unmount(); - renderer = render(); + const { unmount: unmount2 } = render(); expect(getColumnValues(0)).to.deep.equal(['Apă']); - renderer.unmount(); + unmount2(); }); it('should ignore diacritics when `ignoreDiacritics` is enabled', () => { - let renderer = render(); + const { unmount } = render( + , + ); expect(getColumnValues(0)).to.deep.equal(['Apă']); - renderer.unmount(); + unmount(); - renderer = render(); + const { unmount: unmount2 } = render( + , + ); expect(getColumnValues(0)).to.deep.equal(['Apă']); - renderer.unmount(); + unmount2(); }); }); }); diff --git a/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx b/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx index 4cfe95e32b9ca..45ddd727ca212 100644 --- a/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx @@ -6,8 +6,8 @@ import { fireEvent, screen, act, - userEvent, waitFor, + flushMicrotasks, } from '@mui/internal-test-utils'; import { DataGrid, @@ -28,6 +28,7 @@ import { getActiveCell, grid, } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; import { getBasicGridData } from '@mui/x-data-grid-generator'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); @@ -67,9 +68,9 @@ describe(' - Row selection', () => { describe('prop: checkboxSelection = false (single selection)', () => { it('should select one row at a time on click WITHOUT ctrl or meta pressed', () => { render(); - userEvent.mousePress(getCell(0, 0)); + fireUserEvent.mousePress(getCell(0, 0)); expect(getSelectedRowIds()).to.deep.equal([0]); - userEvent.mousePress(getCell(1, 0)); + fireUserEvent.mousePress(getCell(1, 0)); expect(getSelectedRowIds()).to.deep.equal([1]); }); @@ -113,12 +114,12 @@ describe(' - Row selection', () => { render(); const cell0 = getCell(0, 0); - userEvent.mousePress(cell0); + fireUserEvent.mousePress(cell0); fireEvent.keyDown(cell0, { key: ' ', shiftKey: true }); expect(getSelectedRowIds()).to.deep.equal([0]); const cell1 = getCell(1, 0); - userEvent.mousePress(cell1); + fireUserEvent.mousePress(cell1); fireEvent.keyDown(cell1, { key: ' ', shiftKey: true }); expect(getSelectedRowIds()).to.deep.equal([1]); }); @@ -144,7 +145,7 @@ describe(' - Row selection', () => { expect(onCellEditStart.callCount).to.equal(0); const cell01 = getCell(0, 1); - userEvent.mousePress(cell01); + fireUserEvent.mousePress(cell01); fireEvent.keyDown(cell01, { key: ' ', shiftKey: true }); @@ -152,7 +153,7 @@ describe(' - Row selection', () => { expect(getSelectedRowIds()).to.deep.equal([0]); const cell11 = getCell(1, 1); - userEvent.mousePress(cell11); + fireUserEvent.mousePress(cell11); fireEvent.keyDown(cell11, { key: ' ', shiftKey: true }); expect(onCellEditStart.callCount).to.equal(0); @@ -163,7 +164,7 @@ describe(' - Row selection', () => { it(`should deselect the selected row on Shift + Space`, () => { render(); const cell00 = getCell(0, 0); - userEvent.mousePress(cell00); + fireUserEvent.mousePress(cell00); fireEvent.keyDown(cell00, { key: ' ', shiftKey: true }); expect(getSelectedRowIds()).to.deep.equal([0]); @@ -175,7 +176,7 @@ describe(' - Row selection', () => { it('should not select a range with shift pressed', () => { render(); const cell00 = getCell(0, 0); - userEvent.mousePress(cell00); + fireUserEvent.mousePress(cell00); fireEvent.keyDown(cell00, { key: ' ', shiftKey: true }); expect(getSelectedRowIds()).to.deep.equal([0]); @@ -227,16 +228,18 @@ describe(' - Row selection', () => { expect(getRow(0).querySelector('input')).to.have.property('checked', false); }); - it('should set focus on the cell when clicking the checkbox', () => { + it('should set focus on the cell when clicking the checkbox', async () => { render(); expect(getActiveCell()).to.equal(null); // simulate click const checkboxInput = getCell(0, 0).querySelector('input'); - userEvent.mousePress(checkboxInput!); + fireUserEvent.mousePress(checkboxInput!); - expect(getActiveCell()).to.equal('0-0'); + await waitFor(() => { + expect(getActiveCell()).to.equal('0-0'); + }); }); it('should select all visible rows regardless of pagination', () => { @@ -380,8 +383,8 @@ describe(' - Row selection', () => { fireEvent.click(selectAllCheckbox); await waitFor(() => { expect(getSelectedRowIds()).to.deep.equal([0, 1, 2, 3]); - expect(grid('selectedRowCount')?.textContent).to.equal('4 rows selected'); }); + expect(grid('selectedRowCount')?.textContent).to.equal('4 rows selected'); fireEvent.change(screen.getByRole('spinbutton', { name: 'Value' }), { target: { value: 1 }, @@ -389,19 +392,39 @@ describe(' - Row selection', () => { await waitFor(() => { // Previous selection remains, but only one row is visible expect(getSelectedRowIds()).to.deep.equal([1]); - expect(grid('selectedRowCount')?.textContent).to.equal('4 rows selected'); }); + expect(grid('selectedRowCount')?.textContent).to.equal('4 rows selected'); fireEvent.click(selectAllCheckbox); // Unselect all await waitFor(() => { expect(getSelectedRowIds()).to.deep.equal([]); - expect(grid('selectedRowCount')).to.equal(null); }); + expect(grid('selectedRowCount')).to.equal(null); fireEvent.click(selectAllCheckbox); // Select all filtered rows await waitFor(() => { expect(getSelectedRowIds()).to.deep.equal([1]); - expect(grid('selectedRowCount')?.textContent).to.equal('1 row selected'); + }); + expect(grid('selectedRowCount')?.textContent).to.equal('1 row selected'); + }); + + describe('prop: indeterminateCheckboxAction = "select"', () => { + it('should select all the rows when clicking on "Select All" checkbox in indeterminate state', () => { + render(); + const selectAllCheckbox = screen.getByRole('checkbox', { name: 'Select all rows' }); + fireEvent.click(screen.getAllByRole('checkbox', { name: /select row/i })[0]); + fireEvent.click(selectAllCheckbox); + expect(getSelectedRowIds()).to.deep.equal([0, 1, 2, 3]); + }); + }); + + describe('prop: indeterminateCheckboxAction = "deselect"', () => { + it('should deselect all the rows when clicking on "Select All" checkbox in indeterminate state', () => { + render(); + const selectAllCheckbox = screen.getByRole('checkbox', { name: 'Select all rows' }); + fireEvent.click(screen.getAllByRole('checkbox', { name: /select row/i })[0]); + fireEvent.click(selectAllCheckbox); + expect(getSelectedRowIds()).to.deep.equal([]); }); }); }); @@ -409,7 +432,7 @@ describe(' - Row selection', () => { describe('prop: checkboxSelection = true (multi selection), with keyboard events', () => { it('should select row below when pressing "ArrowDown" + shiftKey', () => { render(); - userEvent.mousePress(getCell(2, 1)); + fireUserEvent.mousePress(getCell(2, 1)); expect(getSelectedRowIds()).to.deep.equal([2]); fireEvent.keyDown(getCell(2, 1), { key: 'ArrowDown', shiftKey: true }); expect(getSelectedRowIds()).to.deep.equal([2, 3]); @@ -419,10 +442,10 @@ describe(' - Row selection', () => { it('should unselect previous row when pressing "ArrowDown" + shiftKey', () => { render(); - userEvent.mousePress(getCell(3, 1)); + fireUserEvent.mousePress(getCell(3, 1)); expect(getSelectedRowIds()).to.deep.equal([3]); - userEvent.mousePress(getCell(1, 1), { shiftKey: true }); + fireUserEvent.mousePress(getCell(1, 1), { shiftKey: true }); expect(getSelectedRowIds()).to.deep.equal([1, 2, 3]); fireEvent.keyDown(getCell(1, 1), { key: 'ArrowDown', shiftKey: true }); expect(getSelectedRowIds()).to.deep.equal([2, 3]); @@ -430,9 +453,9 @@ describe(' - Row selection', () => { it('should not unselect row above when pressing "ArrowDown" + shiftKey', () => { render(); - userEvent.mousePress(getCell(1, 1)); + fireUserEvent.mousePress(getCell(1, 1)); expect(getSelectedRowIds()).to.deep.equal([1]); - userEvent.mousePress(getCell(2, 1), { shiftKey: true }); + fireUserEvent.mousePress(getCell(2, 1), { shiftKey: true }); expect(getSelectedRowIds()).to.deep.equal([1, 2]); fireEvent.keyDown(getCell(2, 1), { key: 'ArrowDown', shiftKey: true }); expect(getSelectedRowIds()).to.deep.equal([1, 2, 3]); @@ -442,9 +465,9 @@ describe(' - Row selection', () => { it('should unselect previous row when pressing "ArrowUp" + shiftKey', () => { render(); - userEvent.mousePress(getCell(2, 1)); + fireUserEvent.mousePress(getCell(2, 1)); expect(getSelectedRowIds()).to.deep.equal([2]); - userEvent.mousePress(getCell(3, 1), { shiftKey: true }); + fireUserEvent.mousePress(getCell(3, 1), { shiftKey: true }); expect(getSelectedRowIds()).to.deep.equal([2, 3]); fireEvent.keyDown(getCell(3, 1), { key: 'ArrowUp', shiftKey: true }); expect(getSelectedRowIds()).to.deep.equal([2]); @@ -456,7 +479,7 @@ describe(' - Row selection', () => { expect(getSelectedRowIds()).to.deep.equal([]); const cell21 = getCell(2, 1); - userEvent.mousePress(cell21); + fireUserEvent.mousePress(cell21); fireEvent.keyDown(cell21, { key: ' ', shiftKey: true, @@ -465,7 +488,7 @@ describe(' - Row selection', () => { expect(getSelectedRowIds()).to.deep.equal([2]); const cell11 = getCell(1, 1); - userEvent.mousePress(cell11); + fireUserEvent.mousePress(cell11); fireEvent.keyDown(cell11, { key: ' ', shiftKey: true, @@ -480,7 +503,7 @@ describe(' - Row selection', () => { const data = getBasicGridData(20, 1); render(); const checkboxes = screen.queryAllByRole('checkbox', { name: /select row/i }); - userEvent.mousePress(checkboxes[0]); + fireUserEvent.mousePress(checkboxes[0]); expect(checkboxes[0]).toHaveFocus(); fireEvent.keyDown(checkboxes[0], { key: 'ArrowDown' }); fireEvent.keyDown(checkboxes[1], { key: 'ArrowDown' }); @@ -500,7 +523,7 @@ describe(' - Row selection', () => { expect(checkboxCell).to.have.attribute('tabindex', '-1'); expect(secondCell).to.have.attribute('tabindex', '-1'); - userEvent.mousePress(secondCell); + fireUserEvent.mousePress(secondCell); expect(secondCell).to.have.attribute('tabindex', '0'); fireEvent.keyDown(secondCell, { key: 'ArrowLeft' }); @@ -533,7 +556,7 @@ describe(' - Row selection', () => { describe('ripple', () => { clock.withFakeTimers(); - it('should keep only one ripple visible when navigating between checkboxes', function test() { + it('should keep only one ripple visible when navigating between checkboxes', async function test() { if (isJSDOM) { // JSDOM doesn't fire "blur" when .focus is called in another element // FIXME Firefox doesn't show any ripple @@ -541,10 +564,11 @@ describe(' - Row selection', () => { } render(); const cell = getCell(1, 1); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); fireEvent.keyDown(cell, { key: 'ArrowLeft' }); fireEvent.keyDown(getCell(1, 0).querySelector('input')!, { key: 'ArrowUp' }); clock.runToLast(); // Wait for transition + await flushMicrotasks(); expect(document.querySelectorAll('.MuiTouchRipple-rippleVisible')).to.have.length(1); }); }); @@ -826,7 +850,7 @@ describe(' - Row selection', () => { it('should not select rows with Shift + Space', () => { render(); const cell0 = getCell(0, 0); - userEvent.mousePress(cell0); + fireUserEvent.mousePress(cell0); fireEvent.keyDown(cell0, { key: ' ', shiftKey: true }); expect(getSelectedRowIds()).to.deep.equal([]); }); @@ -837,6 +861,27 @@ describe(' - Row selection', () => { }); }); + describe('accessibility', () => { + it('should add aria-selected attributes to the selectable rows', () => { + render(); + + // Select the first row + fireUserEvent.mousePress(getCell(0, 0)); + expect(getRow(0).getAttribute('aria-selected')).to.equal('true'); + expect(getRow(1).getAttribute('aria-selected')).to.equal('false'); + }); + + it('should not add aria-selected attributes if the row selection is disabled', () => { + render(); + expect(getRow(0).getAttribute('aria-selected')).to.equal(null); + + // Try to select the first row + fireUserEvent.mousePress(getCell(0, 0)); + // nothing should change + expect(getRow(0).getAttribute('aria-selected')).to.equal(null); + }); + }); + describe('performance', () => { it('should not rerender unrelated nodes', () => { // Couldn't use because we need to track multiple components diff --git a/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx b/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx index 4cc711d378218..2efc26c7c9c34 100644 --- a/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx @@ -4,7 +4,6 @@ import { fireEvent, screen, act, - userEvent, ErrorBoundary, waitFor, } from '@mui/internal-test-utils'; @@ -33,6 +32,7 @@ import { getCell, microtasks, } from 'test/utils/helperFn'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; import Dialog from '@mui/material/Dialog'; import { COMPACT_DENSITY_FACTOR } from '../hooks/features/density/densitySelector'; @@ -374,7 +374,7 @@ describe(' - Rows', () => { />, ); const moreButton = screen.getByRole('menuitem', { name: 'more' }); - userEvent.mousePress(moreButton); + fireUserEvent.mousePress(moreButton); await waitFor(() => { const printButton = screen.queryByRole('menuitem', { name: 'print' }); @@ -1146,7 +1146,7 @@ describe(' - Rows', () => { ); const cell = getCell(0, 0); - userEvent.mousePress(cell); + fireUserEvent.mousePress(cell); const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; virtualScroller.scrollTop = 1000; diff --git a/packages/x-data-grid/src/tests/sorting.DataGrid.test.tsx b/packages/x-data-grid/src/tests/sorting.DataGrid.test.tsx index 4fec48edbd187..59bd49145729e 100644 --- a/packages/x-data-grid/src/tests/sorting.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/sorting.DataGrid.test.tsx @@ -714,9 +714,9 @@ describe(' - Sorting', () => { setProps({ columns: [{ field: 'id' }] }); await waitFor(() => { expect(getColumnValues(0)).to.deep.equal(['0', '1', '2']); - expect(onSortModelChange.callCount).to.equal(1); - expect(onSortModelChange.lastCall.firstArg).to.deep.equal([]); }); + expect(onSortModelChange.callCount).to.equal(1); + expect(onSortModelChange.lastCall.firstArg).to.deep.equal([]); }); // See https://github.com/mui/mui-x/issues/9204 @@ -749,8 +749,8 @@ describe(' - Sorting', () => { setProps({ columns: [{ field: 'id' }], sortModel: [{ field: 'id', sort: 'desc' }] }); await waitFor(() => { expect(getColumnValues(0)).to.deep.equal(['2', '1', '0']); - expect(onSortModelChange.callCount).to.equal(0); }); + expect(onSortModelChange.callCount).to.equal(0); }); describe('getSortComparator', () => { diff --git a/packages/x-data-grid/src/tests/toolbar.DataGrid.test.tsx b/packages/x-data-grid/src/tests/toolbar.DataGrid.test.tsx index 12e1c495bd55f..92cea1015317f 100644 --- a/packages/x-data-grid/src/tests/toolbar.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/toolbar.DataGrid.test.tsx @@ -37,7 +37,7 @@ describe(' - Toolbar', () => { describe('column selector', () => { it('should hide "id" column when hiding it from the column selector', () => { - const { getByText } = render( + render(
- Toolbar', () => { expect(getColumnHeadersTextContent()).to.deep.equal(['id', 'brand']); - fireEvent.click(getByText('Columns')); + fireEvent.click(screen.getByText('Columns')); fireEvent.click(screen.getByRole('tooltip').querySelector('[name="id"]')!); expect(getColumnHeadersTextContent()).to.deep.equal(['brand']); @@ -66,7 +66,7 @@ describe(' - Toolbar', () => { }, ]; - const { getByText, getByRole } = render( + render(
- Toolbar', () => {
, ); - fireEvent.click(getByText('Columns')); - const showHideAllCheckbox = getByRole('checkbox', { name: 'Show/Hide All' }); + fireEvent.click(screen.getByText('Columns')); + const showHideAllCheckbox = screen.getByRole('checkbox', { name: 'Show/Hide All' }); fireEvent.click(showHideAllCheckbox); expect(getColumnHeadersTextContent()).to.deep.equal(['id', 'brand']); fireEvent.click(showHideAllCheckbox); @@ -135,7 +135,7 @@ describe(' - Toolbar', () => { ); }; - const { getByText } = render( + render(
- Toolbar', () => {
, ); - fireEvent.click(getByText('Columns')); + fireEvent.click(screen.getByText('Columns')); const searchInput = document.querySelector('input[type="text"]')!; fireEvent.change(searchInput, { target: { value: 'test' } }); diff --git a/packages/x-data-grid/src/utils/createSelector.ts b/packages/x-data-grid/src/utils/createSelector.ts index 647ed42838532..194090510c942 100644 --- a/packages/x-data-grid/src/utils/createSelector.ts +++ b/packages/x-data-grid/src/utils/createSelector.ts @@ -1,16 +1,35 @@ import * as React from 'react'; -import { createSelector as reselectCreateSelector, Selector, SelectorResultArray } from 'reselect'; +import { lruMemoize, createSelectorCreator, Selector, SelectorResultArray } from 'reselect'; import type { GridCoreApi } from '../models/api/gridCoreApi'; import { warnOnce } from '../internals/utils/warning'; type CacheKey = { id: number }; +const reselectCreateSelector = createSelectorCreator({ + memoize: lruMemoize, + memoizeOptions: { + maxSize: 1, + equalityCheck: Object.is, + }, +}); + +// TODO v8: Remove this type export interface OutputSelector { (apiRef: React.MutableRefObject<{ state: State; instanceId: GridCoreApi['instanceId'] }>): Result; (state: State, instanceId: GridCoreApi['instanceId']): Result; acceptsApiRef: boolean; } +// TODO v8: Rename this type to `OutputSelector` +export interface OutputSelectorV8 { + ( + apiRef: React.MutableRefObject<{ state: State; instanceId: GridCoreApi['instanceId'] }>, + args: Args, + ): Result; + (state: State, instanceId: GridCoreApi['instanceId']): Result; + acceptsApiRef: boolean; +} + type StateFromSelector = T extends (first: infer F, ...args: any[]) => any ? F extends { state: infer F2 } ? F2 @@ -26,16 +45,38 @@ type StateFromSelectorList = Selectors extends : StateFromSelectorList : {}; +// TODO v8: Remove this type type SelectorArgs>, Result> = // Input selectors as a separate array | [selectors: [...Selectors], combiner: (...args: SelectorResultArray) => Result] // Input selectors as separate inline arguments | [...Selectors, (...args: SelectorResultArray) => Result]; +type SelectorResultArrayWithArgs>, Args> = [ + ...SelectorResultArray, + Args, +]; + +// TODO v8: Rename this type to `SelectorArgs` +type SelectorArgsV8>, Args, Result> = + // Input selectors as a separate array + | [ + selectors: [...Selectors], + combiner: (...args: SelectorResultArrayWithArgs) => Result, + ] + // Input selectors as separate inline arguments + | [...Selectors, (...args: SelectorResultArrayWithArgs) => Result]; + +// TODO v8: Remove this type type CreateSelectorFunction = >, Result>( ...items: SelectorArgs ) => OutputSelector, Result>; +// TODO v8: Rename this type to `CreateSelectorFunction` +type CreateSelectorFunctionV8 = >, Args, Result>( + ...items: SelectorArgsV8 +) => OutputSelectorV8, Args, Result>; + const cache = new WeakMap>(); function checkIsAPIRef(value: any) { @@ -44,6 +85,7 @@ function checkIsAPIRef(value: any) { const DEFAULT_INSTANCE_ID = { id: 'default' }; +// TODO v8: Remove this function export const createSelector = (( a: Function, b: Function, @@ -125,6 +167,89 @@ export const createSelector = (( return selector; }) as unknown as CreateSelectorFunction; +// TODO v8: Rename this function to `createSelector` +export const createSelectorV8 = (( + a: Function, + b: Function, + c?: Function, + d?: Function, + e?: Function, + f?: Function, + ...other: any[] +) => { + if (other.length > 0) { + throw new Error('Unsupported number of selectors'); + } + + let selector: any; + + if (a && b && c && d && e && f) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + const vb = b(state, args, instanceId); + const vc = c(state, args, instanceId); + const vd = d(state, args, instanceId); + const ve = e(state, args, instanceId); + return f(va, vb, vc, vd, ve, args); + }; + } else if (a && b && c && d && e) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + const vb = b(state, args, instanceId); + const vc = c(state, args, instanceId); + const vd = d(state, args, instanceId); + return e(va, vb, vc, vd, args); + }; + } else if (a && b && c && d) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + const vb = b(state, args, instanceId); + const vc = c(state, args, instanceId); + return d(va, vb, vc, args); + }; + } else if (a && b && c) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + const vb = b(state, args, instanceId); + return c(va, vb, args); + }; + } else if (a && b) { + selector = (stateOrApiRef: any, args: any, instanceIdParam: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const instanceId = + instanceIdParam ?? (isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + const va = a(state, args, instanceId); + return b(va, args); + }; + } else { + throw new Error('Missing arguments'); + } + + // We use this property to detect if the selector was created with createSelector + // or it's only a simple function the receives the state and returns part of it. + selector.acceptsApiRef = true; + + return selector; +}) as unknown as CreateSelectorFunctionV8; + +// TODO v8: Remove this function export const createSelectorMemoized: CreateSelectorFunction = (...args: any) => { const selector = (stateOrApiRef: any, instanceId?: any) => { const isAPIRef = checkIsAPIRef(stateOrApiRef); @@ -168,3 +293,48 @@ export const createSelectorMemoized: CreateSelectorFunction = (...args: any) => return selector; }; + +// TODO v8: Rename this function to `createSelectorMemoized` +export const createSelectorMemoizedV8: CreateSelectorFunctionV8 = (...args: any) => { + const selector = (stateOrApiRef: any, selectorArgs: any, instanceId?: any) => { + const isAPIRef = checkIsAPIRef(stateOrApiRef); + const cacheKey = isAPIRef + ? stateOrApiRef.current.instanceId + : (instanceId ?? DEFAULT_INSTANCE_ID); + const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef; + + if (process.env.NODE_ENV !== 'production') { + if (cacheKey.id === 'default') { + warnOnce([ + 'MUI X: A selector was called without passing the instance ID, which may impact the performance of the grid.', + 'To fix, call it with `apiRef`, for example `mySelector(apiRef)`, or pass the instance ID explicitly, for example `mySelector(state, apiRef.current.instanceId)`.', + ]); + } + } + + const cacheArgsInit = cache.get(cacheKey); + const cacheArgs = cacheArgsInit ?? new Map(); + const cacheFn = cacheArgs?.get(args); + + if (cacheArgs && cacheFn) { + // We pass the cache key because the called selector might have as + // dependency another selector created with this `createSelector`. + return cacheFn(state, selectorArgs, cacheKey); + } + + const fn = reselectCreateSelector(...args); + + if (!cacheArgsInit) { + cache.set(cacheKey, cacheArgs); + } + cacheArgs.set(args, fn); + + return fn(state, selectorArgs, cacheKey); + }; + + // We use this property to detect if the selector was created with createSelector + // or it's only a simple function the receives the state and returns part of it. + selector.acceptsApiRef = true; + + return selector; +}; diff --git a/packages/x-data-grid/src/utils/keyboardUtils.ts b/packages/x-data-grid/src/utils/keyboardUtils.ts index 358005ebf3063..c70617d75671b 100644 --- a/packages/x-data-grid/src/utils/keyboardUtils.ts +++ b/packages/x-data-grid/src/utils/keyboardUtils.ts @@ -1,15 +1,5 @@ import * as React from 'react'; -/** - * @deprecated there is no meaninfuly logic abstracted, use event.key directly. - */ -export const isEscapeKey = (key: string): boolean => key === 'Escape'; - -/** - * @deprecated there is no meaninfuly logic abstracted, use event.key directly. - */ -export const isTabKey = (key: string): boolean => key === 'Tab'; - // Non printable keys have a name, for example "ArrowRight", see the whole list: // https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values // So event.key.length === 1 is often enough. @@ -50,7 +40,7 @@ export const isNavigationKey = (key: string) => export const isKeyboardEvent = (event: any): event is React.KeyboardEvent => !!event.key; -export const isHideMenuKey = (key: React.KeyboardEvent['key']) => isTabKey(key) || isEscapeKey(key); +export const isHideMenuKey = (key: React.KeyboardEvent['key']) => key === 'Tab' || key === 'Escape'; // In theory, on macOS, ctrl + v doesn't trigger a paste, so the function should return false. // However, maybe it's overkill to fix, so let's be lazy. diff --git a/packages/x-date-pickers-pro/package.json b/packages/x-date-pickers-pro/package.json index 9ab8869c92cab..3ee69b37aed96 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.12.0", + "version": "7.15.0", "description": "The Pro plan edition of the Date and Time Picker components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -42,9 +42,8 @@ "directory": "packages/x-date-pickers-pro" }, "dependencies": { - "@babel/runtime": "^7.25.0", - "@mui/system": "^5.16.5", - "@mui/utils": "^5.16.5", + "@babel/runtime": "^7.25.4", + "@mui/utils": "^5.16.6", "@mui/x-date-pickers": "workspace:*", "@mui/x-license": "workspace:*", "clsx": "^2.1.1", @@ -54,7 +53,8 @@ "peerDependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", "date-fns": "^2.25.0 || ^3.2.0", "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0", "dayjs": "^1.10.7", @@ -95,15 +95,17 @@ } }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.5", + "@mui/internal-test-utils": "^1.0.11", + "@mui/material": "^5.16.7", + "@mui/system": "^5.16.7", "@types/luxon": "^3.4.2", "@types/prop-types": "^15.7.12", "date-fns": "^2.30.0", "date-fns-jalali": "^2.30.0-0", "dayjs": "^1.11.11", - "luxon": "^3.4.4", + "luxon": "^3.5.0", "moment": "^2.30.1", - "rimraf": "^5.0.9" + "rimraf": "^5.0.10" }, "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 1836534d02821..6c4f54626d5a0 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx @@ -1,13 +1,7 @@ import * as React from 'react'; import { spy } from 'sinon'; import { expect } from 'chai'; -import { - screen, - fireEvent, - getByRole, - fireTouchChangedEvent, - userEvent, -} from '@mui/internal-test-utils'; +import { screen, fireEvent, within, fireTouchChangedEvent } from '@mui/internal-test-utils'; import { adapterToUse, buildPickerDragInteractions, @@ -24,7 +18,7 @@ import { describeConformance } from 'test/utils/describeConformance'; import { RangePosition } from '../models'; const getPickerDay = (name: string, picker = 'January 2018') => - getByRole(screen.getByRole('grid', { name: picker }), 'gridcell', { name }); + within(screen.getByRole('grid', { name: picker })).getByRole('gridcell', { name }); const dynamicShouldDisableDate = (date, position: RangePosition) => { if (position === 'end') { @@ -540,7 +534,7 @@ describe('', () => { ); const renderCountBeforeChange = RenderCount.callCount; - userEvent.mousePress(getPickerDay('2')); + fireEvent.click(getPickerDay('2')); expect(RenderCount.callCount - renderCountBeforeChange).to.equal(2); // 2 render * 1 day }); @@ -555,10 +549,10 @@ describe('', () => { />, ); - userEvent.mousePress(getPickerDay('2')); + fireEvent.click(getPickerDay('2')); const renderCountBeforeChange = RenderCount.callCount; - userEvent.mousePress(getPickerDay('4')); + fireEvent.click(getPickerDay('4')); expect(RenderCount.callCount - renderCountBeforeChange).to.equal(6); // 2 render * 3 day }); }); diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.test.tsx index c6b3c1487e6d0..2d6094f4db182 100644 --- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.test.tsx @@ -23,14 +23,14 @@ describe('', () => { it('should not open mobile picker dialog when clicked on input', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + const { unmount } = renderWithProps({ enableAccessibleFieldDOMStructure: true }); fireEvent.click(getFieldInputRoot()); clock.runToLast(); expect(screen.queryByRole('tooltip')).not.to.equal(null); expect(screen.queryByRole('dialog')).to.equal(null); - v7Response.unmount(); + unmount(); // Test with v6 input renderWithProps({ enableAccessibleFieldDOMStructure: false }); diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx index 6137c60421f0b..8ec9617073430 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { screen, fireEvent, userEvent, act, getByRole } from '@mui/internal-test-utils'; +import { screen, fireEvent, act, within } from '@mui/internal-test-utils'; import { createTheme, ThemeProvider } from '@mui/material/styles'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { DesktopDateRangePicker } from '@mui/x-date-pickers-pro/DesktopDateRangePicker'; @@ -19,7 +19,7 @@ import { const isJSDOM = /jsdom/.test(window.navigator.userAgent); const getPickerDay = (name: string, picker = 'January 2018') => - getByRole(screen.getByRole('grid', { name: picker }), 'gridcell', { name }); + within(screen.getByRole('grid', { name: picker })).getByRole('gridcell', { name }); describe('', () => { const { render, clock } = createPickerRenderer({ @@ -47,7 +47,7 @@ describe('', () => { expect(screen.getByText('May 2019')).toBeVisible(); }); - it(`should not crash when opening picker with invalid date value`, async () => { + it(`should not crash when opening picker with invalid date value`, () => { render( ', () => { describe('Field slot: SingleInputDateRangeField', () => { it('should add focused class to the field when it is focused', () => { // test v7 behavior - const response = render( + const { unmount } = render( ', () => { expect(sectionsContainer.parentElement).to.have.class('Mui-focused'); - response.unmount(); + unmount(); // test v6 behavior render(); @@ -118,7 +118,7 @@ describe('', () => { it('should render the input with a given `name` when `SingleInputDateRangeField` is used', () => { // Test with v7 input - const v7Response = render( + const { unmount } = render( ', () => { ); expect(screen.getByRole('textbox', { hidden: true }).name).to.equal('test'); - v7Response.unmount(); + unmount(); // Test with v6 input render(); @@ -244,13 +244,13 @@ describe('', () => { expect(onClose.callCount).to.equal(0); // Change the start date - userEvent.mousePress(getPickerDay('3')); + fireEvent.click(getPickerDay('3')); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.args[0][0]).toEqualDateTime(new Date(2018, 0, 3)); expect(onChange.lastCall.args[0][1]).toEqualDateTime(defaultValue[1]); // Change the end date - userEvent.mousePress(getPickerDay('5')); + fireEvent.click(getPickerDay('5')); expect(onChange.callCount).to.equal(2); expect(onChange.lastCall.args[0][0]).toEqualDateTime(new Date(2018, 0, 3)); expect(onChange.lastCall.args[0][1]).toEqualDateTime(new Date(2018, 0, 5)); @@ -287,7 +287,7 @@ describe('', () => { expect(onClose.callCount).to.equal(0); // Change the end date - userEvent.mousePress(getPickerDay('3')); + fireEvent.click(getPickerDay('3')); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.args[0][0]).toEqualDateTime(defaultValue[0]); expect(onChange.lastCall.args[0][1]).toEqualDateTime(new Date(2018, 0, 3)); @@ -318,7 +318,7 @@ describe('', () => { openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); // Change the end date - userEvent.mousePress(getPickerDay('3')); + fireEvent.click(getPickerDay('3')); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -346,7 +346,7 @@ describe('', () => { openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); // Change the start date (already tested) - userEvent.mousePress(getPickerDay('3')); + fireEvent.click(getPickerDay('3')); // Dismiss the picker // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target -- don't care @@ -380,12 +380,12 @@ describe('', () => { // Dismiss the picker const input = document.getElementById('test-id')!; + fireEvent.mouseDown(input); act(() => { - fireEvent.mouseDown(input); input.focus(); - fireEvent.mouseUp(input); - clock.runToLast(); }); + fireEvent.mouseUp(input); + clock.runToLast(); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); @@ -417,17 +417,17 @@ describe('', () => { openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); // Change the start date (already tested) - userEvent.mousePress(getPickerDay('3')); + fireEvent.click(getPickerDay('3')); clock.runToLast(); // Dismiss the picker const input = document.getElementById('test-id')!; + fireEvent.mouseDown(input); act(() => { - fireEvent.mouseDown(input); input.focus(); - fireEvent.mouseUp(input); }); + fireEvent.mouseUp(input); clock.runToLast(); @@ -453,7 +453,7 @@ describe('', () => { ); // Dismiss the picker - userEvent.mousePress(document.body); + fireEvent.click(document.body); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); @@ -521,7 +521,7 @@ describe('', () => { expect(screen.getByRole('tooltip')).toBeVisible(); // Change the start date (already tested) - userEvent.mousePress(getPickerDay('3')); + fireEvent.click(getPickerDay('3')); clock.runToLast(); act(() => { @@ -559,7 +559,7 @@ describe('', () => { openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); // Clear the date - userEvent.mousePress(screen.getByText(/clear/i)); + fireEvent.click(screen.getByText(/clear/i)); expect(onChange.callCount).to.equal(1); // Start date change expect(onChange.lastCall.args[0]).to.deep.equal([null, null]); expect(onAccept.callCount).to.equal(1); @@ -586,7 +586,7 @@ describe('', () => { openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); // Clear the date - userEvent.mousePress(screen.getByText(/clear/i)); + fireEvent.click(screen.getByText(/clear/i)); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(1); 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 ff1a2bf8b2c17..640ac3b6c5f3d 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 @@ -1,5 +1,5 @@ import * as React from 'react'; -import { screen, userEvent } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { adapterToUse, createPickerRenderer, @@ -86,7 +86,7 @@ describe(' - Describes', () => { } if (isOpened) { - userEvent.mousePress( + fireEvent.click( screen.getAllByRole('gridcell', { name: adapterToUse.getDate(newValue[setEndDate ? 1 : 0]).toString(), })[0], @@ -148,7 +148,7 @@ describe(' - Describes', () => { } if (isOpened) { - userEvent.mousePress( + fireEvent.click( screen.getAllByRole('gridcell', { name: adapterToUse.getDate(newValue[setEndDate ? 1 : 0]).toString(), })[0], diff --git a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx index f3d52bc90d1f0..069e4d66cc750 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx @@ -173,7 +173,7 @@ const DesktopDateTimeRangePicker = React.forwardRef(function DesktopDateTimeRang ...defaultizedProps, views, viewRenderers, - format: resolveDateTimeFormat(utils, defaultizedProps), + format: resolveDateTimeFormat(utils, defaultizedProps, true), // 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/DesktopDateTimeRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx index f39f90bdb0d19..078ffbc61c0b9 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/tests/DesktopDateTimeRangePicker.test.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { screen, userEvent } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { createPickerRenderer, adapterToUse, @@ -23,16 +23,16 @@ describe('', () => { openPicker({ type: 'date-time-range', variant: 'desktop', initialFocus: 'start' }); // select start date range - userEvent.mousePress(screen.getByRole('gridcell', { name: '11' })); - userEvent.mousePress(screen.getByRole('option', { name: '4 hours' })); - userEvent.mousePress(screen.getByRole('option', { name: '5 minutes' })); - userEvent.mousePress(screen.getByRole('option', { name: 'PM' })); + fireEvent.click(screen.getByRole('gridcell', { name: '11' })); + fireEvent.click(screen.getByRole('option', { name: '4 hours' })); + fireEvent.click(screen.getByRole('option', { name: '5 minutes' })); + fireEvent.click(screen.getByRole('option', { name: 'PM' })); // select end date range on the same day - userEvent.mousePress(screen.getByRole('gridcell', { name: '11' })); - userEvent.mousePress(screen.getByRole('option', { name: '5 hours' })); - userEvent.mousePress(screen.getByRole('option', { name: '10 minutes' })); - userEvent.mousePress(screen.getByRole('option', { name: 'PM' })); + fireEvent.click(screen.getByRole('gridcell', { name: '11' })); + fireEvent.click(screen.getByRole('option', { name: '5 hours' })); + fireEvent.click(screen.getByRole('option', { name: '10 minutes' })); + fireEvent.click(screen.getByRole('option', { name: 'PM' })); const startSectionsContainer = getFieldSectionsContainer(0); const endSectionsContainer = getFieldSectionsContainer(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 25518f4a44f34..d9b960a960376 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 @@ -1,5 +1,5 @@ import * as React from 'react'; -import { describeConformance, screen, userEvent } from '@mui/internal-test-utils'; +import { describeConformance, fireEvent, screen } from '@mui/internal-test-utils'; import { createPickerRenderer, adapterToUse, @@ -101,7 +101,7 @@ describe(' - Describes', () => { ]; } if (isOpened) { - userEvent.mousePress( + fireEvent.click( screen.getByRole('gridcell', { name: adapterToUse.getDate(newValue[setEndDate ? 1 : 0]).toString(), }), @@ -112,8 +112,8 @@ describe(' - Describes', () => { hasMeridiem ? 'hours12h' : 'hours24h', ); const hoursNumber = adapterToUse.getHours(newValue[setEndDate ? 1 : 0]); - userEvent.mousePress(screen.getByRole('option', { name: `${parseInt(hours, 10)} hours` })); - userEvent.mousePress( + fireEvent.click(screen.getByRole('option', { name: `${parseInt(hours, 10)} hours` })); + fireEvent.click( screen.getByRole('option', { name: `${adapterToUse.getMinutes(newValue[setEndDate ? 1 : 0])} minutes`, }), @@ -121,9 +121,7 @@ describe(' - Describes', () => { if (hasMeridiem) { // meridiem is an extra view on `DesktopDateTimeRangePicker` // we need to click it to finish selection - userEvent.mousePress( - screen.getByRole('option', { name: hoursNumber >= 12 ? 'PM' : 'AM' }), - ); + fireEvent.click(screen.getByRole('option', { name: hoursNumber >= 12 ? 'PM' : 'AM' })); } } else { selectSection('day'); diff --git a/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/MobileDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/MobileDateRangePicker.test.tsx index e27cb9084aa31..76383fe45d87a 100644 --- a/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/MobileDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/MobileDateRangePicker/tests/MobileDateRangePicker.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { spy } from 'sinon'; import { expect } from 'chai'; -import { screen, userEvent, fireEvent } from '@mui/internal-test-utils'; +import { screen, fireEvent } from '@mui/internal-test-utils'; import { MobileDateRangePicker } from '@mui/x-date-pickers-pro/MobileDateRangePicker'; import { createPickerRenderer, @@ -13,12 +13,12 @@ import { DateRange } from '@mui/x-date-pickers-pro/models'; import { SingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDateRangeField'; describe('', () => { - const { render } = createPickerRenderer({ clock: 'fake' }); + const { render } = createPickerRenderer(); describe('Field slot: SingleInputDateRangeField', () => { it('should render the input with a given `name` when `SingleInputDateRangeField` is used', () => { // Test with v7 input - const v7Response = render( + const { unmount } = render( ', () => { ); expect(screen.getByRole('textbox', { hidden: true }).name).to.equal('test'); - v7Response.unmount(); + unmount(); // Test with v6 input render(); @@ -36,29 +36,43 @@ describe('', () => { }); describe('picker state', () => { - it('should open when focusing the start input', () => { + it('should open when focusing the start input', async () => { const onOpen = spy(); - render(); + const { user } = render( + , + ); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); + await openPicker({ + type: 'date-range', + variant: 'mobile', + initialFocus: 'start', + click: user.click, + }); expect(onOpen.callCount).to.equal(1); expect(screen.queryByRole('dialog')).toBeVisible(); }); - it('should open when focusing the end input', () => { + it('should open when focusing the end input', async () => { const onOpen = spy(); - render(); + const { user } = render( + , + ); - openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'end' }); + await openPicker({ + type: 'date-range', + variant: 'mobile', + initialFocus: 'end', + click: user.click, + }); expect(onOpen.callCount).to.equal(1); expect(screen.queryByRole('dialog')).toBeVisible(); }); - it('should call onChange with updated start date then call onChange with updated end date when opening from start input', () => { + it('should call onChange with updated start date then call onChange with updated end date when opening from start input', async () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); @@ -67,7 +81,7 @@ describe('', () => { adapterToUse.date('2018-01-06'), ]; - render( + const { user } = render( ', () => { expect(onClose.callCount).to.equal(0); // Change the start date - userEvent.mousePress(screen.getByRole('gridcell', { name: '3' })); + await user.click(screen.getByRole('gridcell', { name: '3' })); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.args[0][0]).toEqualDateTime(new Date(2018, 0, 3)); expect(onChange.lastCall.args[0][1]).toEqualDateTime(defaultValue[1]); // Change the end date - userEvent.mousePress(screen.getByRole('gridcell', { name: '5' })); + await user.click(screen.getByRole('gridcell', { name: '5' })); expect(onChange.callCount).to.equal(2); expect(onChange.lastCall.args[0][0]).toEqualDateTime(new Date(2018, 0, 3)); expect(onChange.lastCall.args[0][1]).toEqualDateTime(new Date(2018, 0, 5)); @@ -99,7 +113,7 @@ describe('', () => { expect(onClose.callCount).to.equal(0); }); - it('should call onChange with updated end date when opening from end input', () => { + it('should call onChange with updated end date when opening from end input', async () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); @@ -108,7 +122,7 @@ describe('', () => { adapterToUse.date('2018-01-06'), ]; - render( + const { user } = render( ', () => { expect(onClose.callCount).to.equal(0); // Change the end date - userEvent.mousePress(screen.getByRole('gridcell', { name: '3' })); + await user.click(screen.getByRole('gridcell', { name: '3' })); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.args[0][0]).toEqualDateTime(defaultValue[0]); expect(onChange.lastCall.args[0][1]).toEqualDateTime(new Date(2018, 0, 3)); @@ -133,7 +147,7 @@ describe('', () => { expect(onClose.callCount).to.equal(0); }); - it('should call onClose and onAccept when selecting the end date if props.closeOnSelect = true', () => { + it('should call onClose and onAccept when selecting the end date if props.closeOnSelect = true', async () => { const onAccept = spy(); const onClose = spy(); const defaultValue: DateRange = [ @@ -141,7 +155,7 @@ describe('', () => { adapterToUse.date('2018-01-06'), ]; - render( + const { user } = render( ', () => { openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'end' }); // Change the end date - userEvent.mousePress(screen.getByRole('gridcell', { name: '3' })); + await user.click(screen.getByRole('gridcell', { name: '3' })); expect(onAccept.callCount).to.equal(1); expect(onAccept.lastCall.args[0][0]).toEqualDateTime(defaultValue[0]); @@ -162,7 +176,7 @@ describe('', () => { expect(onClose.callCount).to.equal(1); }); - it('should call onClose and onChange with the initial value when clicking "Cancel" button', () => { + it('should call onClose and onChange with the initial value when clicking "Cancel" button', async () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); @@ -171,7 +185,7 @@ describe('', () => { adapterToUse.date('2018-01-06'), ]; - render( + const { user } = render( ', () => { openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); // Change the start date (already tested) - userEvent.mousePress(screen.getByRole('gridcell', { name: '3' })); + await user.click(screen.getByRole('gridcell', { name: '3' })); // Cancel the modifications - userEvent.mousePress(screen.getByText(/cancel/i)); + await user.click(screen.getByText(/cancel/i)); expect(onChange.callCount).to.equal(2); // Start date change + reset expect(onChange.lastCall.args[0][0]).toEqualDateTime(defaultValue[0]); expect(onChange.lastCall.args[0][1]).toEqualDateTime(defaultValue[1]); @@ -196,7 +210,7 @@ describe('', () => { expect(onClose.callCount).to.equal(1); }); - it('should call onClose and onAccept with the live value and onAccept with the live value when clicking the "OK"', () => { + it('should call onClose and onAccept with the live value and onAccept with the live value when clicking the "OK"', async () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); @@ -205,7 +219,7 @@ describe('', () => { adapterToUse.date('2018-01-06'), ]; - render( + const { user } = render( ', () => { openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); // Change the start date (already tested) - userEvent.mousePress(screen.getByRole('gridcell', { name: '3' })); + await user.click(screen.getByRole('gridcell', { name: '3' })); // Accept the modifications - userEvent.mousePress(screen.getByText(/ok/i)); + await user.click(screen.getByText(/ok/i)); expect(onChange.callCount).to.equal(1); // Start date change expect(onAccept.callCount).to.equal(1); expect(onAccept.lastCall.args[0][0]).toEqualDateTime(new Date(2018, 0, 3)); @@ -229,7 +243,7 @@ describe('', () => { expect(onClose.callCount).to.equal(1); }); - it('should call onClose, onChange with empty value and onAccept with empty value when pressing the "Clear" button', () => { + it('should call onClose, onChange with empty value and onAccept with empty value when pressing the "Clear" button', async () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); @@ -238,7 +252,7 @@ describe('', () => { adapterToUse.date('2018-01-06'), ]; - render( + const { user } = render( ', () => { openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); // Clear the date - userEvent.mousePress(screen.getByText(/clear/i)); + await user.click(screen.getByText(/clear/i)); expect(onChange.callCount).to.equal(1); // Start date change expect(onChange.lastCall.args[0]).to.deep.equal([null, null]); expect(onAccept.callCount).to.equal(1); @@ -260,12 +274,12 @@ describe('', () => { expect(onClose.callCount).to.equal(1); }); - it('should not call onChange or onAccept when pressing "Clear" button with an already null value', () => { + it('should not call onChange or onAccept when pressing "Clear" button with an already null value', async () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); - render( + const { user } = render( ', () => { openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); // Clear the date - userEvent.mousePress(screen.getByText(/clear/i)); + await user.click(screen.getByText(/clear/i)); expect(onChange.callCount).to.equal(0); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(1); 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 eea31d3a3659e..3410aec62f209 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 @@ -1,5 +1,5 @@ import * as React from 'react'; -import { screen, userEvent, fireDiscreteEvent } from '@mui/internal-test-utils'; +import { screen, fireDiscreteEvent, fireEvent } from '@mui/internal-test-utils'; import { MobileDateRangePicker } from '@mui/x-date-pickers-pro/MobileDateRangePicker'; import { adapterToUse, @@ -86,7 +86,7 @@ describe(' - Describes', () => { openPicker({ type: 'date-range', variant: 'mobile', initialFocus: 'start' }); } - userEvent.mousePress( + fireEvent.click( screen.getAllByRole('gridcell', { name: adapterToUse.getDate(newValue[setEndDate ? 1 : 0]).toString(), })[0], diff --git a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/MobileDateTimeRangePicker.tsx b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/MobileDateTimeRangePicker.tsx index 504484d7f3442..27036c430d8e7 100644 --- a/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/MobileDateTimeRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/MobileDateTimeRangePicker/MobileDateTimeRangePicker.tsx @@ -172,7 +172,7 @@ const MobileDateTimeRangePicker = React.forwardRef(function MobileDateTimeRangeP const props = { ...defaultizedProps, viewRenderers, - format: resolveDateTimeFormat(utils, defaultizedProps), + format: resolveDateTimeFormat(utils, defaultizedProps, true), // 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 4aadcac52f6d7..06f204cc0cf60 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 @@ -1,5 +1,5 @@ import * as React from 'react'; -import { describeConformance, fireEvent, screen, userEvent } from '@mui/internal-test-utils'; +import { describeConformance, fireEvent, screen } from '@mui/internal-test-utils'; import { createPickerRenderer, adapterToUse, @@ -108,12 +108,12 @@ describe(' - Describes', () => { // if we want to set the end date, we firstly need to switch to end date "range position" if (setEndDate) { - userEvent.mousePress( + fireEvent.click( screen.getByRole('button', { name: adapterToUse.format(value[1], 'shortDate') }), ); } - userEvent.mousePress( + fireEvent.click( screen.getByRole('gridcell', { name: adapterToUse.getDate(newValue[setEndDate ? 1 : 0]).toString(), }), @@ -124,8 +124,8 @@ describe(' - Describes', () => { hasMeridiem ? 'hours12h' : 'hours24h', ); const hoursNumber = adapterToUse.getHours(newValue[setEndDate ? 1 : 0]); - userEvent.mousePress(screen.getByRole('option', { name: `${parseInt(hours, 10)} hours` })); - userEvent.mousePress( + fireEvent.click(screen.getByRole('option', { name: `${parseInt(hours, 10)} hours` })); + fireEvent.click( screen.getByRole('option', { name: `${adapterToUse.getMinutes(newValue[setEndDate ? 1 : 0])} minutes`, }), @@ -133,7 +133,7 @@ describe(' - Describes', () => { if (hasMeridiem) { // meridiem is an extra view on `MobileDateTimeRangePicker` // we need to click it to finish selection - userEvent.mousePress(screen.getByRole('option', { name: hoursNumber >= 12 ? 'PM' : 'AM' })); + fireEvent.click(screen.getByRole('option', { name: hoursNumber >= 12 ? 'PM' : 'AM' })); } // Close the picker if (!isOpened) { @@ -142,7 +142,7 @@ describe(' - Describes', () => { clock.runToLast(); } else { // return to the start date view in case we'd like to repeat the selection process - userEvent.mousePress( + fireEvent.click( screen.getByRole('button', { name: adapterToUse.format(newValue[0], 'shortDate') }), ); } diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/tests/editing.SingleInputDateRangeField.test.tsx b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/tests/editing.SingleInputDateRangeField.test.tsx index 33b138e7262da..b392a9d809845 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/tests/editing.SingleInputDateRangeField.test.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/tests/editing.SingleInputDateRangeField.test.tsx @@ -13,31 +13,31 @@ describe(' - Editing', () => { describeAdapters(`key: Delete`, SingleInputDateRangeField, ({ adapter, renderWithProps }) => { it('should clear all the sections when all sections are selected and all sections are completed', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: [adapter.date(), adapter.addYears(adapter.date(), 1)], format: `${adapter.formats.month} ${adapter.formats.year}`, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - fireEvent.keyDown(v7Response.getSectionsContainer(), { key: 'Delete' }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM YYYY ā€“ MMMM YYYY'); + fireEvent.keyDown(view.getSectionsContainer(), { key: 'Delete' }); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM YYYY ā€“ MMMM YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ defaultValue: [adapter.date(), adapter.addYears(adapter.date(), 1)], format: `${adapter.formats.month} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Select all sections fireEvent.keyDown(input, { key: 'a', ctrlKey: true }); @@ -48,33 +48,33 @@ describe(' - Editing', () => { it('should clear all the sections when all sections are selected and not all sections are completed', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Set a value for the "month" section - fireEvent.input(v7Response.getActiveSection(0), { target: { innerHTML: 'j' } }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'January YYYY ā€“ MMMM YYYY'); + fireEvent.input(view.getActiveSection(0), { target: { innerHTML: 'j' } }); + expectFieldValueV7(view.getSectionsContainer(), 'January YYYY ā€“ MMMM YYYY'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - fireEvent.keyDown(v7Response.getSectionsContainer(), { key: 'Delete' }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM YYYY ā€“ MMMM YYYY'); + fireEvent.keyDown(view.getSectionsContainer(), { key: 'Delete' }); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM YYYY ā€“ MMMM YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ format: `${adapter.formats.month} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Set a value for the "month" section fireEvent.change(input, { @@ -93,33 +93,33 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - fireEvent.keyDown(v7Response.getSectionsContainer(), { key: 'Delete' }); + fireEvent.keyDown(view.getSectionsContainer(), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(0); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ format: `${adapter.formats.month} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, onChange: onChangeV6, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Select all sections fireEvent.keyDown(input, { key: 'a', ctrlKey: true }); @@ -132,52 +132,52 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: [adapter.date(), adapter.addYears(adapter.date(), 1)], onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Start date - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'Delete' }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(1); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowRight' }); - fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'Delete' }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(1), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(1); - fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowRight' }); - fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'Delete' }); + fireEvent.keyDown(view.getActiveSection(1), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(2), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(2); expect(onChangeV7.lastCall.firstArg[0]).to.equal(null); expect(onChangeV7.lastCall.firstArg[1]).toEqualDateTime(adapter.addYears(adapter.date(), 1)); // End date - fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowRight' }); - fireEvent.keyDown(v7Response.getActiveSection(3), { key: 'Delete' }); + fireEvent.keyDown(view.getActiveSection(2), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(3), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(3); - fireEvent.keyDown(v7Response.getActiveSection(3), { key: 'ArrowRight' }); - fireEvent.keyDown(v7Response.getActiveSection(4), { key: 'Delete' }); + fireEvent.keyDown(view.getActiveSection(3), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(4), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(3); - fireEvent.keyDown(v7Response.getActiveSection(4), { key: 'ArrowRight' }); - fireEvent.keyDown(v7Response.getActiveSection(5), { key: 'Delete' }); + fireEvent.keyDown(view.getActiveSection(4), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(5), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(4); expect(onChangeV7.lastCall.firstArg[0]).to.equal(null); expect(onChangeV7.lastCall.firstArg[1]).to.equal(null); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, defaultValue: [adapter.date(), adapter.addYears(adapter.date(), 1)], onChange: onChangeV6, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Start date fireEvent.keyDown(input, { key: 'Delete' }); @@ -209,27 +209,27 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: [adapter.date(), adapter.addYears(adapter.date(), 1)], onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'Delete' }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(1); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'Delete' }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(1); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: [adapter.date(), adapter.addYears(adapter.date(), 1)], @@ -237,7 +237,7 @@ describe(' - Editing', () => { }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); fireEvent.keyDown(input, { key: 'Delete' }); expect(onChangeV6.callCount).to.equal(1); @@ -253,31 +253,31 @@ describe(' - Editing', () => { ({ adapter, renderWithProps }) => { it('should clear all the sections when all sections are selected and all sections are completed (Backspace)', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: [adapter.date(), adapter.addYears(adapter.date(), 1)], format: `${adapter.formats.month} ${adapter.formats.year}`, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - v7Response.pressKey(null, ''); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM YYYY ā€“ MMMM YYYY'); + view.pressKey(null, ''); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM YYYY ā€“ MMMM YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ defaultValue: [adapter.date(), adapter.addYears(adapter.date(), 1)], format: `${adapter.formats.month} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Select all sections fireEvent.keyDown(input, { key: 'a', ctrlKey: true }); @@ -288,33 +288,33 @@ describe(' - Editing', () => { it('should clear all the sections when all sections are selected and not all sections are completed (Backspace)', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Set a value for the "month" section - fireEvent.input(v7Response.getActiveSection(0), { target: { innerHTML: 'j' } }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'January YYYY ā€“ MMMM YYYY'); + fireEvent.input(view.getActiveSection(0), { target: { innerHTML: 'j' } }); + expectFieldValueV7(view.getSectionsContainer(), 'January YYYY ā€“ MMMM YYYY'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - v7Response.pressKey(null, ''); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM YYYY ā€“ MMMM YYYY'); + view.pressKey(null, ''); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM YYYY ā€“ MMMM YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ format: `${adapter.formats.month} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Set a value for the "month" section fireEvent.change(input, { @@ -333,33 +333,33 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - v7Response.pressKey(null, ''); + view.pressKey(null, ''); expect(onChangeV7.callCount).to.equal(0); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ format: `${adapter.formats.month} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, onChange: onChangeV6, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Select all sections fireEvent.keyDown(input, { key: 'a', ctrlKey: true }); @@ -372,22 +372,22 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: [adapter.date(), adapter.addYears(adapter.date(), 1)], onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Start date - v7Response.pressKey(0, ''); + view.pressKey(0, ''); expect(onChangeV7.callCount).to.equal(1); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowRight' }); - v7Response.pressKey(1, ''); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowRight' }); + view.pressKey(1, ''); expect(onChangeV7.callCount).to.equal(1); - fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowRight' }); - v7Response.pressKey(2, ''); + fireEvent.keyDown(view.getActiveSection(1), { key: 'ArrowRight' }); + view.pressKey(2, ''); expect(onChangeV7.callCount).to.equal(2); expect(onChangeV7.lastCall.firstArg[0]).to.equal(null); expect(onChangeV7.lastCall.firstArg[1]).toEqualDateTime( @@ -395,31 +395,31 @@ describe(' - Editing', () => { ); // End date - fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowRight' }); - v7Response.pressKey(3, ''); + fireEvent.keyDown(view.getActiveSection(2), { key: 'ArrowRight' }); + view.pressKey(3, ''); expect(onChangeV7.callCount).to.equal(3); - fireEvent.keyDown(v7Response.getActiveSection(3), { key: 'ArrowRight' }); - v7Response.pressKey(4, ''); + fireEvent.keyDown(view.getActiveSection(3), { key: 'ArrowRight' }); + view.pressKey(4, ''); expect(onChangeV7.callCount).to.equal(3); - fireEvent.keyDown(v7Response.getActiveSection(4), { key: 'ArrowRight' }); - v7Response.pressKey(5, ''); + fireEvent.keyDown(view.getActiveSection(4), { key: 'ArrowRight' }); + view.pressKey(5, ''); expect(onChangeV7.callCount).to.equal(4); expect(onChangeV7.lastCall.firstArg[0]).to.equal(null); expect(onChangeV7.lastCall.firstArg[1]).to.equal(null); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, defaultValue: [adapter.date(), adapter.addYears(adapter.date(), 1)], onChange: onChangeV6, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Start date fireEvent.change(input, { target: { value: '/15/2022 ā€“ 06/15/2023' } }); @@ -453,27 +453,27 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: [adapter.date(), adapter.addYears(adapter.date(), 1)], onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); - v7Response.pressKey(0, ''); + view.pressKey(0, ''); expect(onChangeV7.callCount).to.equal(1); - v7Response.pressKey(0, ''); + view.pressKey(0, ''); expect(onChangeV7.callCount).to.equal(1); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: [adapter.date(), adapter.addYears(adapter.date(), 1)], @@ -481,7 +481,7 @@ describe(' - Editing', () => { }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); fireEvent.change(input, { target: { value: ' 2022 ā€“ June 2023' } }); expect(onChangeV6.callCount).to.equal(1); diff --git a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/tests/selection.SingleInputDateRangeField.test.tsx b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/tests/selection.SingleInputDateRangeField.test.tsx index 26f9fe2ceedd8..ce9852d7fd25d 100644 --- a/packages/x-date-pickers-pro/src/SingleInputDateRangeField/tests/selection.SingleInputDateRangeField.test.tsx +++ b/packages/x-date-pickers-pro/src/SingleInputDateRangeField/tests/selection.SingleInputDateRangeField.test.tsx @@ -22,14 +22,14 @@ describe(' - Selection', () => { describe('Focus', () => { it('should select 1st section (v7) / all sections (v6) on mount focus (`autoFocus = true`)', () => { // Test with v7 input - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, autoFocus: true, }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY ā€“ MM/DD/YYYY'); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY ā€“ MM/DD/YYYY'); expect(getCleanedSelectedContent()).to.equal('MM'); - v7Response.unmount(); + view.unmount(); // Test with v6 input renderWithProps({ autoFocus: true, enableAccessibleFieldDOMStructure: false }); @@ -57,89 +57,89 @@ describe(' - Selection', () => { describe('Click', () => { it('should select the clicked selection when the input is already focused', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: [null, adapterToUse.date('2022-02-24')], }); // Start date - v7Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); - v7Response.selectSection('month'); + view.selectSection('month'); expect(getCleanedSelectedContent()).to.equal('MM'); // End date - v7Response.selectSection('month', 'last'); + view.selectSection('month', 'last'); expect(getCleanedSelectedContent()).to.equal('02'); - v7Response.selectSection('day', 'last'); + view.selectSection('day', 'last'); expect(getCleanedSelectedContent()).to.equal('24'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, value: [null, adapterToUse.date('2022-02-24')], }); // Start date - v6Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); - v6Response.selectSection('month'); + view.selectSection('month'); expect(getCleanedSelectedContent()).to.equal('MM'); // End date - v6Response.selectSection('month', 'last'); + view.selectSection('month', 'last'); expect(getCleanedSelectedContent()).to.equal('02'); - v6Response.selectSection('day', 'last'); + view.selectSection('day', 'last'); expect(getCleanedSelectedContent()).to.equal('24'); }); it('should not change the selection when clicking on the only already selected section', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: [null, adapterToUse.date('2022-02-24')], }); // Start date - v7Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); - v7Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); // End date - v7Response.selectSection('day', 'last'); + view.selectSection('day', 'last'); expect(getCleanedSelectedContent()).to.equal('24'); - v7Response.selectSection('day', 'last'); + view.selectSection('day', 'last'); expect(getCleanedSelectedContent()).to.equal('24'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, value: [null, adapterToUse.date('2022-02-24')], }); // Start date - v6Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); - v6Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); // End date - v6Response.selectSection('day', 'last'); + view.selectSection('day', 'last'); expect(getCleanedSelectedContent()).to.equal('24'); - v6Response.selectSection('day', 'last'); + view.selectSection('day', 'last'); expect(getCleanedSelectedContent()).to.equal('24'); }); }); @@ -147,33 +147,33 @@ describe(' - Selection', () => { describe('key: ArrowRight', () => { it('should allow to move from left to right with ArrowRight', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - v7Response.selectSection('month'); + view.selectSection('month'); expect(getCleanedSelectedContent()).to.equal('MM'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowRight' }); expect(getCleanedSelectedContent()).to.equal('DD'); - fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(1), { key: 'ArrowRight' }); expect(getCleanedSelectedContent()).to.equal('YYYY'); - fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(2), { key: 'ArrowRight' }); expect(getCleanedSelectedContent()).to.equal('MM'); - fireEvent.keyDown(v7Response.getActiveSection(3), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(3), { key: 'ArrowRight' }); expect(getCleanedSelectedContent()).to.equal('DD'); - fireEvent.keyDown(v7Response.getActiveSection(4), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(4), { key: 'ArrowRight' }); expect(getCleanedSelectedContent()).to.equal('YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); expect(getCleanedSelectedContent()).to.equal('MM'); fireEvent.keyDown(input, { key: 'ArrowRight' }); @@ -194,20 +194,20 @@ describe(' - Selection', () => { it('should stay on the current section when the last section is selected', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - v7Response.selectSection('year', 'last'); + view.selectSection('year', 'last'); expect(getCleanedSelectedContent()).to.equal('YYYY'); - fireEvent.keyDown(v7Response.getActiveSection(5), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(5), { key: 'ArrowRight' }); expect(getCleanedSelectedContent()).to.equal('YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); - v6Response.selectSection('year', 'last'); + view.selectSection('year', 'last'); expect(getCleanedSelectedContent()).to.equal('YYYY'); fireEvent.keyDown(input, { key: 'ArrowRight' }); expect(getCleanedSelectedContent()).to.equal('YYYY'); @@ -217,32 +217,32 @@ describe(' - Selection', () => { describe('key: ArrowLeft', () => { it('should allow to move from right to left with ArrowLeft', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - v7Response.selectSection('year', 'last'); + view.selectSection('year', 'last'); expect(getCleanedSelectedContent()).to.equal('YYYY'); - fireEvent.keyDown(v7Response.getActiveSection(5), { key: 'ArrowLeft' }); + fireEvent.keyDown(view.getActiveSection(5), { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('DD'); - fireEvent.keyDown(v7Response.getActiveSection(4), { key: 'ArrowLeft' }); + fireEvent.keyDown(view.getActiveSection(4), { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('MM'); - fireEvent.keyDown(v7Response.getActiveSection(3), { key: 'ArrowLeft' }); + fireEvent.keyDown(view.getActiveSection(3), { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('YYYY'); - fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowLeft' }); + fireEvent.keyDown(view.getActiveSection(2), { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('DD'); - fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowLeft' }); + fireEvent.keyDown(view.getActiveSection(1), { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('MM'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); - v6Response.selectSection('year', 'last'); + view.selectSection('year', 'last'); expect(getCleanedSelectedContent()).to.equal('YYYY'); fireEvent.keyDown(input, { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('DD'); @@ -262,20 +262,20 @@ describe(' - Selection', () => { it('should stay on the current section when the first section is selected', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - v7Response.selectSection('month'); + view.selectSection('month'); expect(getCleanedSelectedContent()).to.equal('MM'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowLeft' }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('MM'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); expect(getCleanedSelectedContent()).to.equal('MM'); fireEvent.keyDown(input, { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('MM'); diff --git a/packages/x-date-pickers-pro/src/tests/materialVersion.test.tsx b/packages/x-date-pickers-pro/src/tests/materialVersion.test.tsx new file mode 100644 index 0000000000000..dc0c0a7e6ee01 --- /dev/null +++ b/packages/x-date-pickers-pro/src/tests/materialVersion.test.tsx @@ -0,0 +1,5 @@ +import materialPackageJson from '@mui/material/package.json'; +import { checkMaterialVersion } from 'test/utils/checkMaterialVersion'; +import packageJson from '../../package.json'; + +checkMaterialVersion({ packageJson, materialPackageJson }); diff --git a/packages/x-date-pickers-pro/src/themeAugmentation/index.ts b/packages/x-date-pickers-pro/src/themeAugmentation/index.ts index 7141a607d90bf..5c6664875f638 100644 --- a/packages/x-date-pickers-pro/src/themeAugmentation/index.ts +++ b/packages/x-date-pickers-pro/src/themeAugmentation/index.ts @@ -1,4 +1,4 @@ export * from '@mui/x-date-pickers/themeAugmentation'; -export * from './overrides'; -export * from './props'; -export * from './components'; +export type * from './overrides'; +export type * from './props'; +export type * from './components'; diff --git a/packages/x-date-pickers/package.json b/packages/x-date-pickers/package.json index 7c82a56b19af6..ff4acff4b728e 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.12.0", + "version": "7.15.0", "description": "The community edition of the Date and Time Picker components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -45,10 +45,9 @@ "directory": "packages/x-date-pickers" }, "dependencies": { - "@babel/runtime": "^7.25.0", - "@mui/system": "^5.16.5", - "@mui/utils": "^5.16.5", - "@types/react-transition-group": "^4.4.10", + "@babel/runtime": "^7.25.4", + "@mui/utils": "^5.16.6", + "@types/react-transition-group": "^4.4.11", "clsx": "^2.1.1", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" @@ -56,7 +55,8 @@ "peerDependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", "date-fns": "^2.25.0 || ^3.2.0", "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0", "dayjs": "^1.10.7", @@ -97,7 +97,9 @@ } }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.5", + "@mui/internal-test-utils": "^1.0.11", + "@mui/material": "^5.16.7", + "@mui/system": "^5.16.7", "@types/luxon": "^3.4.2", "@types/moment-hijri": "^2.1.4", "@types/moment-jalaali": "^0.7.9", @@ -105,12 +107,12 @@ "date-fns": "^2.30.0", "date-fns-jalali": "^2.30.0-0", "dayjs": "^1.11.11", - "luxon": "^3.4.4", + "luxon": "^3.5.0", "moment": "^2.30.1", "moment-hijri": "^2.30.0", "moment-jalaali": "^0.10.1", "moment-timezone": "^0.5.45", - "rimraf": "^5.0.9" + "rimraf": "^5.0.10" }, "engines": { "node": ">=14.0.0" diff --git a/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.test.tsx b/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.test.tsx index 6f8301a14602e..3f51bd19e5d0d 100644 --- a/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.test.tsx +++ b/packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.test.tsx @@ -117,21 +117,18 @@ describe('', () => { }); it('should have correct placeholder', () => { - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - expectFieldValueV7( - v7Response.getSectionsContainer(), - localizedTexts[localeKey].placeholder, - ); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].placeholder); }); it('should have well formatted value', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: adapter.date(testDate), }); - expectFieldValueV7(v7Response.getSectionsContainer(), localizedTexts[localeKey].value); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].value); }); }); }); diff --git a/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.test.tsx b/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.test.tsx index b88d9390fced8..03caf784f4f13 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.test.tsx +++ b/packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.test.tsx @@ -78,21 +78,18 @@ describe('', () => { }); it('should have correct placeholder', () => { - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - expectFieldValueV7( - v7Response.getSectionsContainer(), - localizedTexts[localeKey].placeholder, - ); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].placeholder); }); it('should have well formatted value', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: adapter.date(testDate), }); - expectFieldValueV7(v7Response.getSectionsContainer(), localizedTexts[localeKey].value); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].value); }); }); }); diff --git a/packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.test.tsx b/packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.test.tsx index 4a35a3d2558a6..f97f309af0fd3 100644 --- a/packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.test.tsx +++ b/packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.test.tsx @@ -64,21 +64,18 @@ describe('', () => { }); it('should have correct placeholder', () => { - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - expectFieldValueV7( - v7Response.getSectionsContainer(), - localizedTexts[localeKey].placeholder, - ); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].placeholder); }); it('should have well formatted value', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: adapter.date(testDate), }); - expectFieldValueV7(v7Response.getSectionsContainer(), localizedTexts[localeKey].value); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].value); }); }); }); diff --git a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.test.tsx b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.test.tsx index dda1f5c56ab9b..9095ecc60cc63 100644 --- a/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.test.tsx +++ b/packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.test.tsx @@ -148,21 +148,18 @@ describe('', () => { }); it('should have correct placeholder', () => { - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - expectFieldValueV7( - v7Response.getSectionsContainer(), - localizedTexts[localeKey].placeholder, - ); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].placeholder); }); it('should have well formatted value', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: adapter.date(testDate), }); - expectFieldValueV7(v7Response.getSectionsContainer(), localizedTexts[localeKey].value); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].value); }); }); }); diff --git a/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.test.tsx b/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.test.tsx index 8c68148e8620b..372dbfab75f60 100644 --- a/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.test.tsx +++ b/packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.test.tsx @@ -107,21 +107,18 @@ describe('', () => { }); it('should have correct placeholder', () => { - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - expectFieldValueV7( - v7Response.getSectionsContainer(), - localizedTexts[localeKey].placeholder, - ); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].placeholder); }); it('should have well formatted value', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: adapter.date(testDate), }); - expectFieldValueV7(v7Response.getSectionsContainer(), localizedTexts[localeKey].value); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].value); }); }); }); @@ -166,25 +163,22 @@ describe('', () => { }); it('should have correct placeholder', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: 'DD', }); - expectFieldValueV7( - v7Response.getSectionsContainer(), - localizedTexts[localeKey].placeholder, - ); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].placeholder); }); it('should have well formatted value', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: adapter.date(testDate), format: 'DD', }); - expectFieldValueV7(v7Response.getSectionsContainer(), localizedTexts[localeKey].value); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].value); }); }); }); diff --git a/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.test.tsx b/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.test.tsx index 895710889ae51..fa684d96e8608 100644 --- a/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.test.tsx +++ b/packages/x-date-pickers/src/AdapterMoment/AdapterMoment.test.tsx @@ -161,21 +161,18 @@ describe('', () => { }); it('should have correct placeholder', () => { - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - expectFieldValueV7( - v7Response.getSectionsContainer(), - localizedTexts[localeKey].placeholder, - ); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].placeholder); }); it('should have well formatted value', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: adapter.date(testDate), }); - expectFieldValueV7(v7Response.getSectionsContainer(), localizedTexts[localeKey].value); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].value); }); }); }); diff --git a/packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.test.tsx b/packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.test.tsx index de006376e2827..bd86f319abe60 100644 --- a/packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.test.tsx +++ b/packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.test.tsx @@ -75,21 +75,18 @@ describe('', () => { }); it('should have correct placeholder', () => { - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - expectFieldValueV7( - v7Response.getSectionsContainer(), - localizedTexts[localeKey].placeholder, - ); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].placeholder); }); it('should have well formatted value', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: adapter.date(testDate), }); - expectFieldValueV7(v7Response.getSectionsContainer(), localizedTexts[localeKey].value); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].value); }); }); }); diff --git a/packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.test.tsx b/packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.test.tsx index 1d326ff9e3257..9a30e357c05d0 100644 --- a/packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.test.tsx +++ b/packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.test.tsx @@ -82,21 +82,18 @@ describe('', () => { }); it('should have correct placeholder', () => { - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - expectFieldValueV7( - v7Response.getSectionsContainer(), - localizedTexts[localeKey].placeholder, - ); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].placeholder); }); it('should have well formatted value', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: adapter.date(testDate), }); - expectFieldValueV7(v7Response.getSectionsContainer(), localizedTexts[localeKey].value); + expectFieldValueV7(view.getSectionsContainer(), localizedTexts[localeKey].value); }); }); }); diff --git a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx index 5d4ff9b31eedc..bf13515b44c83 100644 --- a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx +++ b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { fireEvent, userEvent, screen } from '@mui/internal-test-utils'; +import { fireEvent, screen, waitFor } from '@mui/internal-test-utils'; import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; import { PickersDay } from '@mui/x-date-pickers/PickersDay'; import { createPickerRenderer, adapterToUse } from 'test/utils/pickers'; @@ -9,31 +9,28 @@ import { createPickerRenderer, adapterToUse } from 'test/utils/pickers'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); describe('', () => { - const { render, clock } = createPickerRenderer({ - clock: 'fake', - clockConfig: new Date('2019-01-02'), - }); + const { render } = createPickerRenderer(); - it('switches between views uncontrolled', () => { + it('switches between views uncontrolled', async () => { const handleViewChange = spy(); - render( + const { user } = render( , ); - fireEvent.click(screen.getByLabelText(/switch to year view/i)); + await user.click(screen.getByLabelText(/switch to year view/i)); expect(handleViewChange.callCount).to.equal(1); expect(screen.queryByLabelText(/switch to year view/i)).to.equal(null); expect(screen.getByLabelText('year view is open, switch to calendar view')).toBeVisible(); }); - it('should allow month and view changing, but not selection when readOnly prop is passed', () => { + it('should allow month and view changing, but not selection when readOnly prop is passed', async () => { const onChangeMock = spy(); const onMonthChangeMock = spy(); - render( + const { user } = render( ', () => { />, ); - fireEvent.click(screen.getByTitle('Previous month')); + await user.click(screen.getByTitle('Previous month')); expect(onMonthChangeMock.callCount).to.equal(1); - fireEvent.click(screen.getByTitle('Next month')); + await user.click(screen.getByTitle('Next month')); expect(onMonthChangeMock.callCount).to.equal(2); - clock.runToLast(); + await waitFor(() => expect(screen.getAllByRole('rowgroup').length).to.equal(1)); - fireEvent.click(screen.getByRole('gridcell', { name: '5' })); + await user.click(screen.getByRole('gridcell', { name: '5' })); expect(onChangeMock.callCount).to.equal(0); - fireEvent.click(screen.getByText('January 2019')); + await user.click(screen.getByText('January 2019')); expect(screen.queryByLabelText('year view is open, switch to calendar view')).toBeVisible(); }); - it('should not allow interaction when disabled prop is passed', () => { + it('should not allow interaction when disabled prop is passed', async () => { const onChangeMock = spy(); const onMonthChangeMock = spy(); - render( + const { user } = render( ', () => { />, ); - fireEvent.click(screen.getByText('January 2019')); + await user.click(screen.getByText('January 2019')); expect(screen.queryByText('January 2019')).toBeVisible(); expect(screen.queryByLabelText('year view is open, switch to calendar view')).to.equal(null); - fireEvent.click(screen.getByTitle('Previous month')); + await user.setup({ pointerEventsCheck: 0 }).click(screen.getByTitle('Previous month')); expect(onMonthChangeMock.callCount).to.equal(0); - fireEvent.click(screen.getByTitle('Next month')); + await user.setup({ pointerEventsCheck: 0 }).click(screen.getByTitle('Next month')); expect(onMonthChangeMock.callCount).to.equal(0); - fireEvent.click(screen.getByRole('gridcell', { name: '5' })); + await user.setup({ pointerEventsCheck: 0 }).click(screen.getByRole('gridcell', { name: '5' })); expect(onChangeMock.callCount).to.equal(0); }); @@ -130,28 +127,35 @@ describe('', () => { expect(screen.getAllByRole('rowheader').length).to.equal(5); }); - // test: https://github.com/mui/mui-x/issues/12373 - it('should not reset day to `startOfDay` if value already exists when finding the closest enabled date', () => { - const onChange = spy(); - const defaultDate = adapterToUse.date('2019-01-02T11:12:13.550Z'); - render(); - - userEvent.mousePress( - screen.getByRole('button', { name: 'calendar view is open, switch to year view' }), - ); - userEvent.mousePress(screen.getByRole('radio', { name: '2020' })); + describe('with fake timers', () => { + const { render: renderWithFakeTimers, clock } = createPickerRenderer({ + clock: 'fake', + clockConfig: new Date('2019-01-02'), + }); + // test: https://github.com/mui/mui-x/issues/12373 + it('should not reset day to `startOfDay` if value already exists when finding the closest enabled date', () => { + const onChange = spy(); + const defaultDate = adapterToUse.date('2019-01-02T11:12:13.550Z'); + renderWithFakeTimers( + , + ); - // Finish the transition to the day view - clock.runToLast(); + fireEvent.click( + screen.getByRole('button', { name: 'calendar view is open, switch to year view' }), + ); + fireEvent.click(screen.getByRole('radio', { name: '2020' })); + // Finish the transition to the day view + clock.runToLast(); - userEvent.mousePress(screen.getByRole('gridcell', { name: '1' })); - userEvent.mousePress( - screen.getByRole('button', { name: 'calendar view is open, switch to year view' }), - ); - // select the current year with a date in the past to trigger "findClosestEnabledDate" - userEvent.mousePress(screen.getByRole('radio', { name: '2019' })); + fireEvent.click(screen.getByRole('gridcell', { name: '1' })); + fireEvent.click( + screen.getByRole('button', { name: 'calendar view is open, switch to year view' }), + ); + // select the current year with a date in the past to trigger "findClosestEnabledDate" + fireEvent.click(screen.getByRole('radio', { name: '2019' })); - expect(onChange.lastCall.firstArg).toEqualDateTime(defaultDate); + expect(onChange.lastCall.firstArg).toEqualDateTime(defaultDate); + }); }); describe('Slot: calendarHeader', () => { @@ -181,10 +185,10 @@ describe('', () => { ).to.have.text('1'); }); - it('should use `referenceDate` when no value defined', () => { + it('should use `referenceDate` when no value defined', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { // should make the reference day firstly focusable expect(screen.getByRole('gridcell', { name: '17' })).to.have.attribute('tabindex', '0'); - userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); + await user.click(screen.getByRole('gridcell', { name: '2' })); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2022, 3, 2, 12, 30)); }); - it('should not use `referenceDate` when a value is defined', () => { + it('should not use `referenceDate` when a value is defined', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { />, ); - userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); + await user.click(screen.getByRole('gridcell', { name: '2' })); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 0, 2, 12, 20)); }); - it('should not use `referenceDate` when a defaultValue is defined', () => { + it('should not use `referenceDate` when a defaultValue is defined', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { />, ); - userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); + await user.click(screen.getByRole('gridcell', { name: '2' })); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 0, 2, 12, 20)); }); - it('should keep the time of the currently provided date', () => { + it('should keep the time of the currently provided date', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { />, ); - userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); + await user.click(screen.getByRole('gridcell', { name: '2' })); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.firstArg).toEqualDateTime(adapterToUse.date('2018-01-02T11:11:11')); + expect(onChange.lastCall.firstArg).toEqualDateTime( + adapterToUse.date('2018-01-02T11:11:11.111'), + ); }); it('should complete weeks when showDaysOutsideCurrentMonth=true', () => { @@ -287,10 +293,10 @@ describe('', () => { }); describe('view: month', () => { - it('should select the closest enabled date in the month if the current date is disabled', () => { + it('should select the closest enabled date in the month if the current date is disabled', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const april = screen.getByText('Apr', { selector: 'button' }); - fireEvent.click(april); + await user.click(april); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 3, 6)); }); - it('should respect minDate when selecting closest enabled date', () => { + it('should respect minDate when selecting closest enabled date', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const april = screen.getByText('Apr', { selector: 'button' }); - fireEvent.click(april); + await user.click(april); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 3, 7)); }); - it('should respect maxDate when selecting closest enabled date', () => { + it('should respect maxDate when selecting closest enabled date', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const april = screen.getByText('Apr', { selector: 'button' }); - fireEvent.click(april); + await user.click(april); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 3, 22)); }); - it('should go to next view without changing the date when no date of the new month is enabled', () => { + it('should go to next view without changing the date when no date of the new month is enabled', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const april = screen.getByText('Apr', { selector: 'button' }); - fireEvent.click(april); - clock.runToLast(); + await user.click(april); expect(onChange.callCount).to.equal(0); expect(screen.getByMuiTest('calendar-month-and-year-text')).to.have.text('April 2019'); }); - it('should use `referenceDate` when no value defined', () => { + it('should use `referenceDate` when no value defined', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const april = screen.getByText('Apr', { selector: 'button' }); - fireEvent.click(april); + await user.click(april); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2018, 3, 1, 12, 30)); }); - it('should not use `referenceDate` when a value is defined', () => { + it('should not use `referenceDate` when a value is defined', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const april = screen.getByText('Apr', { selector: 'button' }); - fireEvent.click(april); + await user.click(april); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 3, 1, 12, 20)); }); - it('should not use `referenceDate` when a defaultValue is defined', () => { + it('should not use `referenceDate` when a defaultValue is defined', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const april = screen.getByText('Apr', { selector: 'button' }); - fireEvent.click(april); + await user.click(april); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2019, 3, 1, 12, 20)); @@ -437,10 +442,10 @@ describe('', () => { expect(screen.getAllByMuiTest('year')).to.have.length(200); }); - it('should select the closest enabled date in the month if the current date is disabled', () => { + it('should select the closest enabled date in the month if the current date is disabled', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const year2022 = screen.getByText('2022', { selector: 'button' }); - fireEvent.click(year2022); + await user.click(year2022); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2022, 4, 1)); }); - it('should respect minDate when selecting closest enabled date', () => { + it('should respect minDate when selecting closest enabled date', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const year2017 = screen.getByText('2017', { selector: 'button' }); - fireEvent.click(year2017); + await user.click(year2017); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2017, 4, 12)); }); - it('should respect maxDate when selecting closest enabled date', () => { + it('should respect maxDate when selecting closest enabled date', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const year2022 = screen.getByText('2022', { selector: 'button' }); - fireEvent.click(year2022); + await user.click(year2022); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2022, 2, 31)); }); - it('should go to next view without changing the date when no date of the new year is enabled', () => { + it('should go to next view without changing the date when no date of the new year is enabled', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const year2022 = screen.getByText('2022', { selector: 'button' }); - fireEvent.click(year2022); - clock.runToLast(); + await user.click(year2022); expect(onChange.callCount).to.equal(0); expect(screen.getByMuiTest('calendar-month-and-year-text')).to.have.text('January 2022'); @@ -545,10 +549,10 @@ describe('', () => { expect(parentBoundingBox.bottom).not.to.lessThan(buttonBoundingBox.bottom); }); - it('should use `referenceDate` when no value defined', () => { + it('should use `referenceDate` when no value defined', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const year2022 = screen.getByText('2022', { selector: 'button' }); - fireEvent.click(year2022); + await user.click(year2022); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2022, 0, 1, 12, 30)); }); - it('should not use `referenceDate` when a value is defined', () => { + it('should not use `referenceDate` when a value is defined', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const year2022 = screen.getByText('2022', { selector: 'button' }); - fireEvent.click(year2022); + await user.click(year2022); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2022, 0, 1, 12, 20)); }); - it('should not use `referenceDate` when a defaultValue is defined', () => { + it('should not use `referenceDate` when a defaultValue is defined', async () => { const onChange = spy(); - render( + const { user } = render( ', () => { ); const year2022 = screen.getByText('2022', { selector: 'button' }); - fireEvent.click(year2022); + await user.click(year2022); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2022, 0, 1, 12, 20)); @@ -618,7 +622,7 @@ describe('', () => { ); const renderCountBeforeChange = RenderCount.callCount; - userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); + fireEvent.click(screen.getByRole('gridcell', { name: '2' })); expect(RenderCount.callCount - renderCountBeforeChange).to.equal(2); // 2 render * 1 day }); @@ -635,7 +639,7 @@ describe('', () => { ); const renderCountBeforeChange = RenderCount.callCount; - userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); + fireEvent.click(screen.getByRole('gridcell', { name: '2' })); expect(RenderCount.callCount - renderCountBeforeChange).to.equal(4); // 2 render * 2 days }); }); 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 7a807f651b179..248fbb6e620e8 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 @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { screen, userEvent } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { DateCalendar, dateCalendarClasses as classes } from '@mui/x-date-pickers/DateCalendar'; import { pickersDayClasses } from '@mui/x-date-pickers/PickersDay'; import { @@ -47,7 +47,7 @@ describe(' - Describes', () => { }, setNewValue: (value) => { const newValue = adapterToUse.addDays(value, 1); - userEvent.mousePress( + fireEvent.click( screen.getByRole('gridcell', { name: adapterToUse.getDate(newValue).toString() }), ); diff --git a/packages/x-date-pickers/src/DateCalendar/tests/timezone.DateCalendar.test.tsx b/packages/x-date-pickers/src/DateCalendar/tests/timezone.DateCalendar.test.tsx index 59249ab7660bc..b6f1ef24905b9 100644 --- a/packages/x-date-pickers/src/DateCalendar/tests/timezone.DateCalendar.test.tsx +++ b/packages/x-date-pickers/src/DateCalendar/tests/timezone.DateCalendar.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { spy } from 'sinon'; import { expect } from 'chai'; -import { userEvent, screen } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { describeAdapters } from 'test/utils/pickers'; import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; @@ -17,7 +17,7 @@ describe(' - Timezone', () => { const onChange = spy(); render(); - userEvent.mousePress(screen.getByRole('gridcell', { name: '25' })); + fireEvent.click(screen.getByRole('gridcell', { name: '25' })); const expectedDate = adapter.setDate(adapter.date(undefined, 'default'), 25); // Check the `onChange` value (uses default timezone, e.g: UTC, see TZ env variable) @@ -34,7 +34,7 @@ describe(' - Timezone', () => { it('should use timezone prop for onChange when no value is provided', () => { const onChange = spy(); render(); - userEvent.mousePress(screen.getByRole('gridcell', { name: '25' })); + fireEvent.click(screen.getByRole('gridcell', { name: '25' })); const expectedDate = adapter.setDate( adapter.startOfDay(adapter.date(undefined, timezone)), 25, @@ -52,7 +52,7 @@ describe(' - Timezone', () => { render(); - userEvent.mousePress(screen.getByRole('gridcell', { name: '25' })); + fireEvent.click(screen.getByRole('gridcell', { name: '25' })); const expectedDate = adapter.setDate(value, 25); // Check the `onChange` value (uses timezone prop) 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 7c6ac2b0a0bc8..b5140fdffb725 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 @@ -1,13 +1,14 @@ import { expect } from 'chai'; import { spy } from 'sinon'; import { DateField } from '@mui/x-date-pickers/DateField'; -import { act, userEvent, fireEvent } from '@mui/internal-test-utils'; +import { act, fireEvent } from '@mui/internal-test-utils'; import { expectFieldValueV7, getTextbox, describeAdapters, expectFieldValueV6, } from 'test/utils/pickers'; +import { fireUserEvent } from 'test/utils/fireUserEvent'; describe(' - Editing', () => { describeAdapters('key: ArrowDown', DateField, ({ adapter, testFieldKeyPress }) => { @@ -213,30 +214,30 @@ describe(' - Editing', () => { describeAdapters('key: Delete', DateField, ({ adapter, testFieldKeyPress, renderWithProps }) => { it('should clear the selected section when only this section is completed', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Set a value for the "month" section - v7Response.pressKey(0, 'j'); - expectFieldValueV7(v7Response.getSectionsContainer(), 'January YYYY'); + view.pressKey(0, 'j'); + expectFieldValueV7(view.getSectionsContainer(), 'January YYYY'); - userEvent.keyPress(v7Response.getActiveSection(0), { key: 'Delete' }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM YYYY'); + fireUserEvent.keyPress(view.getActiveSection(0), { key: 'Delete' }); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `${adapter.formats.month} ${adapter.formats.year}`, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Set a value for the "month" section fireEvent.change(input, { @@ -244,7 +245,7 @@ describe(' - Editing', () => { }); // press "j" expectFieldValueV6(input, 'January YYYY'); - userEvent.keyPress(input, { key: 'Delete' }); + fireUserEvent.keyPress(input, { key: 'Delete' }); expectFieldValueV6(input, 'MMMM YYYY'); }); @@ -259,68 +260,68 @@ describe(' - Editing', () => { it('should clear all the sections when all sections are selected and all sections are completed', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), }); - v7Response.selectSection('month'); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - userEvent.keyPress(v7Response.getSectionsContainer(), { key: 'Delete' }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM YYYY'); + fireUserEvent.keyPress(view.getSectionsContainer(), { key: 'Delete' }); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Select all sections - userEvent.keyPress(input, { key: 'a', ctrlKey: true }); + fireUserEvent.keyPress(input, { key: 'a', ctrlKey: true }); - userEvent.keyPress(input, { key: 'Delete' }); + fireUserEvent.keyPress(input, { key: 'Delete' }); expectFieldValueV6(input, 'MMMM YYYY'); }); it('should clear all the sections when all sections are selected and not all sections are completed', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Set a value for the "month" section - v7Response.pressKey(0, 'j'); - expectFieldValueV7(v7Response.getSectionsContainer(), 'January YYYY'); + view.pressKey(0, 'j'); + expectFieldValueV7(view.getSectionsContainer(), 'January YYYY'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - userEvent.keyPress(v7Response.getSectionsContainer(), { key: 'Delete' }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM YYYY'); + fireUserEvent.keyPress(view.getSectionsContainer(), { key: 'Delete' }); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `${adapter.formats.month} ${adapter.formats.year}`, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Set a value for the "month" section fireEvent.change(input, { @@ -329,45 +330,45 @@ describe(' - Editing', () => { expectFieldValueV6(input, 'January YYYY'); // Select all sections - userEvent.keyPress(input, { key: 'a', ctrlKey: true }); + fireUserEvent.keyPress(input, { key: 'a', ctrlKey: true }); - userEvent.keyPress(input, { key: 'Delete' }); + fireUserEvent.keyPress(input, { key: 'Delete' }); expectFieldValueV6(input, 'MMMM YYYY'); }); it('should not keep query after typing again on a cleared section', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: adapter.formats.year, }); - v7Response.selectSection('year'); + view.selectSection('year'); - v7Response.pressKey(0, '2'); - expectFieldValueV7(v7Response.getSectionsContainer(), '0002'); + view.pressKey(0, '2'); + expectFieldValueV7(view.getSectionsContainer(), '0002'); - userEvent.keyPress(v7Response.getActiveSection(0), { key: 'Delete' }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'YYYY'); + fireUserEvent.keyPress(view.getActiveSection(0), { key: 'Delete' }); + expectFieldValueV7(view.getSectionsContainer(), 'YYYY'); - v7Response.pressKey(0, '2'); - expectFieldValueV7(v7Response.getSectionsContainer(), '0002'); + view.pressKey(0, '2'); + expectFieldValueV7(view.getSectionsContainer(), '0002'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: adapter.formats.year, }); const input = getTextbox(); - v6Response.selectSection('year'); + view.selectSection('year'); fireEvent.change(input, { target: { value: '2' } }); // press "2" expectFieldValueV6(input, '0002'); - userEvent.keyPress(input, { key: 'Delete' }); + fireUserEvent.keyPress(input, { key: 'Delete' }); expectFieldValueV6(input, 'YYYY'); fireEvent.change(input, { target: { value: '2' } }); // press "2" @@ -388,38 +389,38 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - userEvent.keyPress(v7Response.getSectionsContainer(), { key: 'Delete' }); + fireUserEvent.keyPress(view.getSectionsContainer(), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(0); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `${adapter.formats.month} ${adapter.formats.year}`, onChange: onChangeV6, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Select all sections - userEvent.keyPress(input, { key: 'a', ctrlKey: true }); + fireUserEvent.keyPress(input, { key: 'a', ctrlKey: true }); - userEvent.keyPress(input, { key: 'Delete' }); + fireUserEvent.keyPress(input, { key: 'Delete' }); expect(onChangeV6.callCount).to.equal(0); }); @@ -427,32 +428,32 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); - userEvent.keyPress(v7Response.getActiveSection(0), { key: 'Delete' }); + fireUserEvent.keyPress(view.getActiveSection(0), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(1); expect(onChangeV7.lastCall.args[1].validationError).to.equal('invalidDate'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowRight' }); - userEvent.keyPress(v7Response.getActiveSection(1), { key: 'Delete' }); + fireUserEvent.keyPress(view.getActiveSection(1), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(2); expect(onChangeV7.lastCall.firstArg).to.equal(null); expect(onChangeV7.lastCall.args[1].validationError).to.equal(null); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), @@ -460,15 +461,15 @@ describe(' - Editing', () => { }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); - userEvent.keyPress(input, { key: 'Delete' }); + fireUserEvent.keyPress(input, { key: 'Delete' }); expect(onChangeV6.callCount).to.equal(1); expect(onChangeV6.lastCall.args[1].validationError).to.equal('invalidDate'); - userEvent.keyPress(input, { key: 'ArrowRight' }); + fireUserEvent.keyPress(input, { key: 'ArrowRight' }); - userEvent.keyPress(input, { key: 'Delete' }); + fireUserEvent.keyPress(input, { key: 'Delete' }); expect(onChangeV6.callCount).to.equal(2); expect(onChangeV6.lastCall.firstArg).to.equal(null); expect(onChangeV6.lastCall.args[1].validationError).to.equal(null); @@ -478,27 +479,27 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); - userEvent.keyPress(v7Response.getActiveSection(0), { key: 'Delete' }); + fireUserEvent.keyPress(view.getActiveSection(0), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(1); - userEvent.keyPress(v7Response.getActiveSection(0), { key: 'Delete' }); + fireUserEvent.keyPress(view.getActiveSection(0), { key: 'Delete' }); expect(onChangeV7.callCount).to.equal(1); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), @@ -506,12 +507,12 @@ describe(' - Editing', () => { }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); - userEvent.keyPress(input, { key: 'Delete' }); + fireUserEvent.keyPress(input, { key: 'Delete' }); expect(onChangeV6.callCount).to.equal(1); - userEvent.keyPress(input, { key: 'Delete' }); + fireUserEvent.keyPress(input, { key: 'Delete' }); expect(onChangeV6.callCount).to.equal(1); }); }); @@ -748,7 +749,7 @@ describe(' - Editing', () => { it('should not allow key editing on disabled field', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange: onChangeV7, disabled: true, @@ -766,30 +767,30 @@ describe(' - Editing', () => { 'ArrowRight', ]; - v7Response.selectSection('month'); + view.selectSection('month'); keys.forEach((key) => { - v7Response.pressKey(0, key); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY'); + view.pressKey(0, key); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); expect(onChangeV7.callCount).to.equal(0); }); // digit key press - userEvent.keyPress(v7Response.getActiveSection(0), { key: '2' }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY'); + fireUserEvent.keyPress(view.getActiveSection(0), { key: '2' }); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ onChange: onChangeV6, enableAccessibleFieldDOMStructure: false, disabled: true, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // v6 doesn't allow focusing on sections when disabled keys.forEach((key) => { @@ -964,44 +965,44 @@ describe(' - Editing', () => { it('should allow to type the date 29th of February for leap years', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: adapter.formats.keyboardDate, }); - v7Response.selectSection('month'); + view.selectSection('month'); - v7Response.pressKey(0, '2'); - expectFieldValueV7(v7Response.getSectionsContainer(), '02/DD/YYYY'); + view.pressKey(0, '2'); + expectFieldValueV7(view.getSectionsContainer(), '02/DD/YYYY'); - v7Response.pressKey(1, '2'); - expectFieldValueV7(v7Response.getSectionsContainer(), '02/02/YYYY'); + view.pressKey(1, '2'); + expectFieldValueV7(view.getSectionsContainer(), '02/02/YYYY'); - v7Response.pressKey(1, '9'); - expectFieldValueV7(v7Response.getSectionsContainer(), '02/29/YYYY'); + view.pressKey(1, '9'); + expectFieldValueV7(view.getSectionsContainer(), '02/29/YYYY'); - v7Response.pressKey(2, '1'); - expectFieldValueV7(v7Response.getSectionsContainer(), '02/29/0001'); + view.pressKey(2, '1'); + expectFieldValueV7(view.getSectionsContainer(), '02/29/0001'); - v7Response.pressKey(2, '9'); - expectFieldValueV7(v7Response.getSectionsContainer(), '02/29/0019'); + view.pressKey(2, '9'); + expectFieldValueV7(view.getSectionsContainer(), '02/29/0019'); - v7Response.pressKey(2, '8'); - expectFieldValueV7(v7Response.getSectionsContainer(), '02/29/0198'); + view.pressKey(2, '8'); + expectFieldValueV7(view.getSectionsContainer(), '02/29/0198'); - v7Response.pressKey(2, '8'); - expectFieldValueV7(v7Response.getSectionsContainer(), '02/29/1988'); + view.pressKey(2, '8'); + expectFieldValueV7(view.getSectionsContainer(), '02/29/1988'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: adapter.formats.keyboardDate, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); fireEvent.change(input, { target: { value: '2/DD/YYYY' } }); expectFieldValueV6(input, '02/DD/YYYY'); @@ -1136,28 +1137,28 @@ describe(' - Editing', () => { ({ adapter, renderWithProps, testFieldChange }) => { it('should clear the selected section when only this section is completed (Backspace)', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, }); - v7Response.selectSection('month'); - v7Response.pressKey(0, 'j'); - expectFieldValueV7(v7Response.getSectionsContainer(), 'January YYYY'); + view.selectSection('month'); + view.pressKey(0, 'j'); + expectFieldValueV7(view.getSectionsContainer(), 'January YYYY'); - v7Response.pressKey(0, ''); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM YYYY'); + view.pressKey(0, ''); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ format: `${adapter.formats.month} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); fireEvent.change(input, { target: { value: 'j YYYY' } }); expectFieldValueV6(input, 'January YYYY'); @@ -1167,28 +1168,28 @@ describe(' - Editing', () => { it('should clear the selected section when all sections are completed (Backspace)', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), }); - v7Response.selectSection('month'); + view.selectSection('month'); - v7Response.pressKey(0, ''); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM 2022'); + view.pressKey(0, ''); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM 2022'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); fireEvent.change(input, { target: { value: ' 2022' } }); expectFieldValueV6(input, 'MMMM 2022'); @@ -1196,31 +1197,31 @@ describe(' - Editing', () => { it('should clear all the sections when all sections are selected and all sections are completed (Backspace)', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), }); - v7Response.selectSection('month'); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - v7Response.pressKey(null, ''); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM YYYY'); + view.pressKey(null, ''); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Select all sections fireEvent.keyDown(input, { key: 'a', ctrlKey: true }); @@ -1231,31 +1232,31 @@ describe(' - Editing', () => { it('should clear all the sections when all sections are selected and not all sections are completed (Backspace)', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, }); - v7Response.selectSection('month'); - v7Response.pressKey(0, 'j'); - expectFieldValueV7(v7Response.getSectionsContainer(), 'January YYYY'); + view.selectSection('month'); + view.pressKey(0, 'j'); + expectFieldValueV7(view.getSectionsContainer(), 'January YYYY'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - v7Response.pressKey(null, ''); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM YYYY'); + view.pressKey(null, ''); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ format: `${adapter.formats.month} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); fireEvent.change(input, { target: { value: 'j YYYY' } }); expectFieldValueV6(input, 'January YYYY'); @@ -1302,30 +1303,30 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), onChange: onChangeV7, }); - v7Response.selectSection('month'); - v7Response.pressKey(0, ''); + view.selectSection('month'); + view.pressKey(0, ''); expect(onChangeV7.callCount).to.equal(1); expect(onChangeV7.lastCall.args[1].validationError).to.equal('invalidDate'); - v7Response.selectSection('year'); - v7Response.pressKey(1, ''); + view.selectSection('year'); + view.pressKey(1, ''); expect(onChangeV7.callCount).to.equal(2); expect(onChangeV7.lastCall.firstArg).to.equal(null); expect(onChangeV7.lastCall.args[1].validationError).to.equal(null); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), @@ -1333,12 +1334,12 @@ describe(' - Editing', () => { }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); fireEvent.change(input, { target: { value: ' 2022' } }); expect(onChangeV6.callCount).to.equal(1); expect(onChangeV6.lastCall.args[1].validationError).to.equal('invalidDate'); - userEvent.keyPress(input, { key: 'ArrowRight' }); + fireUserEvent.keyPress(input, { key: 'ArrowRight' }); fireEvent.change(input, { target: { value: 'MMMM ' } }); expect(onChangeV6.callCount).to.equal(2); @@ -1367,90 +1368,92 @@ describe(' - Editing', () => { describeAdapters('Pasting', DateField, ({ adapter, renderWithProps }) => { const firePasteEventV7 = (element: HTMLElement, pastedValue: string) => { - act(() => { - const clipboardEvent = new window.Event('paste', { - bubbles: true, - cancelable: true, - composed: true, - }); + const clipboardEvent = new window.Event('paste', { + bubbles: true, + cancelable: true, + composed: true, + }); - // @ts-ignore - clipboardEvent.clipboardData = { - getData: () => pastedValue, - }; + // @ts-ignore + clipboardEvent.clipboardData = { + getData: () => pastedValue, + }; + let canContinue = true; + act(() => { // canContinue is `false` if default have been prevented - const canContinue = element.dispatchEvent(clipboardEvent); - if (!canContinue) { - return; - } - - fireEvent.input(element, { target: { textContent: pastedValue } }); + canContinue = element.dispatchEvent(clipboardEvent); }); + if (!canContinue) { + return; + } + + fireEvent.input(element, { target: { textContent: pastedValue } }); }; const firePasteEventV6 = (input: HTMLInputElement, pastedValue?: string, rawValue?: string) => { - act(() => { - const clipboardEvent = new window.Event('paste', { - bubbles: true, - cancelable: true, - composed: true, - }); + const clipboardEvent = new window.Event('paste', { + bubbles: true, + cancelable: true, + composed: true, + }); - // @ts-ignore - clipboardEvent.clipboardData = { - getData: () => pastedValue ?? rawValue ?? '', - }; + // @ts-ignore + clipboardEvent.clipboardData = { + getData: () => pastedValue ?? rawValue ?? '', + }; + let canContinue = true; + act(() => { // canContinue is `false` if default have been prevented - const canContinue = input.dispatchEvent(clipboardEvent); - if (!canContinue) { - return; - } - - if (!pastedValue) { - return; - } - - const prevValue = input.value; - const nextValue = `${prevValue.slice( - 0, - input.selectionStart || 0, - )}${pastedValue}${prevValue.slice(input.selectionEnd || 0)}`; - fireEvent.change(input, { target: { value: nextValue } }); + canContinue = input.dispatchEvent(clipboardEvent); }); + if (!canContinue) { + return; + } + + if (!pastedValue) { + return; + } + + const prevValue = input.value; + const nextValue = `${prevValue.slice( + 0, + input.selectionStart || 0, + )}${pastedValue}${prevValue.slice(input.selectionEnd || 0)}`; + fireEvent.change(input, { target: { value: nextValue } }); }; it('should set the date when all sections are selected, the pasted value is valid and a value is provided', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date(), onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - firePasteEventV7(v7Response.getSectionsContainer(), '09/16/2022'); + firePasteEventV7(view.getSectionsContainer(), '09/16/2022'); expect(onChangeV7.callCount).to.equal(1); expect(onChangeV7.lastCall.firstArg).toEqualDateTime(new Date(2022, 8, 16)); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ defaultValue: adapter.date(), onChange: onChangeV6, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Select all sections - userEvent.keyPress(input, { key: 'a', ctrlKey: true }); + fireUserEvent.keyPress(input, { key: 'a', ctrlKey: true }); firePasteEventV6(input, '09/16/2022'); @@ -1461,32 +1464,32 @@ describe(' - Editing', () => { it('should set the date when all sections are selected, the pasted value is valid and no value is provided', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - firePasteEventV7(v7Response.getSectionsContainer(), '09/16/2022'); + firePasteEventV7(view.getSectionsContainer(), '09/16/2022'); expect(onChangeV7.callCount).to.equal(1); expect(onChangeV7.lastCall.firstArg).toEqualDateTime(new Date(2022, 8, 16)); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ onChange: onChangeV6, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Select all sections - userEvent.keyPress(input, { key: 'a', ctrlKey: true }); + fireUserEvent.keyPress(input, { key: 'a', ctrlKey: true }); firePasteEventV6(input, '09/16/2022'); @@ -1497,30 +1500,30 @@ describe(' - Editing', () => { it('should not set the date when all sections are selected and the pasted value is not valid', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - firePasteEventV7(v7Response.getSectionsContainer(), 'Some invalid content'); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY'); - v7Response.unmount(); + firePasteEventV7(view.getSectionsContainer(), 'Some invalid content'); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ onChange: onChangeV6, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Select all sections - userEvent.keyPress(input, { key: 'a', ctrlKey: true }); + fireUserEvent.keyPress(input, { key: 'a', ctrlKey: true }); firePasteEventV6(input, 'Some invalid content'); expectFieldValueV6(input, 'MM/DD/YYYY'); @@ -1531,35 +1534,35 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange: onChangeV7, format: `${startChar}Escaped${endChar} ${adapter.formats.year}`, }); - v7Response.selectSection('year'); + view.selectSection('year'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - firePasteEventV7(v7Response.getSectionsContainer(), `Escaped 2014`); + firePasteEventV7(view.getSectionsContainer(), `Escaped 2014`); expect(onChangeV7.callCount).to.equal(1); expect(adapter.getYear(onChangeV7.lastCall.firstArg)).to.equal(2014); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ onChange: onChangeV6, format: `${startChar}Escaped${endChar} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('year'); + view.selectSection('year'); // Select all sections - userEvent.keyPress(input, { key: 'a', ctrlKey: true }); + fireUserEvent.keyPress(input, { key: 'a', ctrlKey: true }); firePasteEventV6(input, `Escaped 2014`); expect(onChangeV6.callCount).to.equal(1); @@ -1570,36 +1573,36 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange: onChangeV7, readOnly: true, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - firePasteEventV7(v7Response.getSectionsContainer(), '09/16/2022'); + firePasteEventV7(view.getSectionsContainer(), '09/16/2022'); expect(onChangeV7.callCount).to.equal(0); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ onChange: onChangeV6, readOnly: true, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Select all sections - userEvent.keyPress(input, { key: 'a', ctrlKey: true }); + fireUserEvent.keyPress(input, { key: 'a', ctrlKey: true }); firePasteEventV6(input, '09/16/2022'); expect(onChangeV6.callCount).to.equal(0); @@ -1609,31 +1612,31 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY'); - firePasteEventV7(v7Response.getActiveSection(0), '12'); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); + firePasteEventV7(view.getActiveSection(0), '12'); expect(onChangeV7.callCount).to.equal(1); - expectFieldValueV7(v7Response.getSectionsContainer(), '12/DD/YYYY'); + expectFieldValueV7(view.getSectionsContainer(), '12/DD/YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ onChange: onChangeV6, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); expectFieldValueV6(input, 'MM/DD/YYYY'); firePasteEventV6(input, '12'); @@ -1646,33 +1649,33 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date('2018-01-13'), onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); - expectFieldValueV7(v7Response.getSectionsContainer(), '01/13/2018'); - firePasteEventV7(v7Response.getActiveSection(0), '12'); - expectFieldValueV7(v7Response.getSectionsContainer(), '12/13/2018'); + expectFieldValueV7(view.getSectionsContainer(), '01/13/2018'); + firePasteEventV7(view.getActiveSection(0), '12'); + expectFieldValueV7(view.getSectionsContainer(), '12/13/2018'); expect(onChangeV7.callCount).to.equal(1); expect(onChangeV7.lastCall.firstArg).toEqualDateTime(new Date(2018, 11, 13)); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ defaultValue: adapter.date('2018-01-13'), onChange: onChangeV6, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); expectFieldValueV6(input, '01/13/2018'); firePasteEventV6(input, '12'); @@ -1685,32 +1688,32 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date('2018-01-13'), onChange: onChangeV7, }); - v7Response.selectSection('month'); + view.selectSection('month'); - expectFieldValueV7(v7Response.getSectionsContainer(), '01/13/2018'); - firePasteEventV7(v7Response.getActiveSection(0), 'Jun'); - expectFieldValueV7(v7Response.getSectionsContainer(), '01/13/2018'); + expectFieldValueV7(view.getSectionsContainer(), '01/13/2018'); + firePasteEventV7(view.getActiveSection(0), 'Jun'); + expectFieldValueV7(view.getSectionsContainer(), '01/13/2018'); expect(onChangeV7.callCount).to.equal(0); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ defaultValue: adapter.date('2018-01-13'), onChange: onChangeV6, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); expectFieldValueV6(input, '01/13/2018'); firePasteEventV6(input, 'Jun'); @@ -1720,37 +1723,37 @@ describe(' - Editing', () => { it('should reset sections internal state when pasting', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date('2018-12-05'), }); - v7Response.selectSection('day'); + view.selectSection('day'); - v7Response.pressKey(1, '2'); - expectFieldValueV7(v7Response.getSectionsContainer(), '12/02/2018'); + view.pressKey(1, '2'); + expectFieldValueV7(view.getSectionsContainer(), '12/02/2018'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(1), { key: 'a', ctrlKey: true }); - firePasteEventV7(v7Response.getSectionsContainer(), '09/16/2022'); - expectFieldValueV7(v7Response.getSectionsContainer(), '09/16/2022'); + firePasteEventV7(view.getSectionsContainer(), '09/16/2022'); + expectFieldValueV7(view.getSectionsContainer(), '09/16/2022'); - v7Response.selectSection('day'); + view.selectSection('day'); - v7Response.pressKey(1, '2'); // Press 2 - expectFieldValueV7(v7Response.getSectionsContainer(), '09/02/2022'); // If internal state is not reset it would be 22 instead of 02 + view.pressKey(1, '2'); // Press 2 + expectFieldValueV7(view.getSectionsContainer(), '09/02/2022'); // If internal state is not reset it would be 22 instead of 02 - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ defaultValue: adapter.date('2018-12-05'), enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('day'); + view.selectSection('day'); fireEvent.change(input, { target: { value: '12/2/2018' } }); // Press 2 expectFieldValueV6(input, '12/02/2018'); @@ -1763,32 +1766,32 @@ describe(' - Editing', () => { }); it('should allow pasting a section', () => { - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date('2018-12-05'), }); - v7Response.selectSection('month'); + view.selectSection('month'); - v7Response.pressKey(0, '1'); // Press 1 - expectFieldValueV7(v7Response.getSectionsContainer(), '01/05/2018'); + view.pressKey(0, '1'); // Press 1 + expectFieldValueV7(view.getSectionsContainer(), '01/05/2018'); - firePasteEventV7(v7Response.getActiveSection(0), '05'); - expectFieldValueV7(v7Response.getSectionsContainer(), '05/05/2018'); + firePasteEventV7(view.getActiveSection(0), '05'); + expectFieldValueV7(view.getSectionsContainer(), '05/05/2018'); - v7Response.selectSection('month'); // move back to month section - v7Response.pressKey(0, '2'); // check that the search query has been cleared after pasting - expectFieldValueV7(v7Response.getSectionsContainer(), '02/05/2018'); // If internal state is not reset it would be 12 instead of 02 + view.selectSection('month'); // move back to month section + view.pressKey(0, '2'); // check that the search query has been cleared after pasting + expectFieldValueV7(view.getSectionsContainer(), '02/05/2018'); // If internal state is not reset it would be 12 instead of 02 - v7Response.unmount(); + view.unmount(); - const v6Response = renderWithProps({ + view = renderWithProps({ defaultValue: adapter.date('2018-12-05'), enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); fireEvent.change(input, { target: { value: '1/05/2018' } }); // initiate search query on month section expectFieldValueV6(input, '01/05/2018'); @@ -1796,7 +1799,7 @@ describe(' - Editing', () => { firePasteEventV6(input, undefined, '05'); expectFieldValueV6(input, '05/05/2018'); - v6Response.selectSection('month'); // move back to month section + view.selectSection('month'); // move back to month section fireEvent.change(input, { target: { value: '2/05/2018' } }); // check that the search query has been cleared after pasting expectFieldValueV6(input, '02/05/2018'); // If internal state is not reset it would be 12 instead of 02 }); @@ -1804,32 +1807,32 @@ describe(' - Editing', () => { it('should not allow pasting on disabled field', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange: onChangeV7, disabled: true, }); - v7Response.selectSection('month'); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); - firePasteEventV7(v7Response.getSectionsContainer(), '09/16/2022'); + firePasteEventV7(view.getSectionsContainer(), '09/16/2022'); expect(onChangeV7.callCount).to.equal(0); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY'); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ onChange: onChangeV6, enableAccessibleFieldDOMStructure: false, disabled: true, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); firePasteEventV6(input, '9'); // v6 doesn't allow focusing on sections when disabled @@ -1846,27 +1849,27 @@ describe(' - Editing', () => { it('should not loose time information when a value is provided', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV7, }); - v7Response.selectSection('year'); - fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowDown' }); + view.selectSection('year'); + fireEvent.keyDown(view.getActiveSection(2), { key: 'ArrowDown' }); expect(onChangeV7.lastCall.firstArg).toEqualDateTime(new Date(2009, 3, 3, 3, 3, 3)); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV6, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('year'); - userEvent.keyPress(input, { key: 'ArrowDown' }); + view.selectSection('year'); + fireUserEvent.keyPress(input, { key: 'ArrowDown' }); expect(onChangeV6.lastCall.firstArg).toEqualDateTime(new Date(2009, 3, 3, 3, 3, 3)); }); @@ -1874,51 +1877,51 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV7, }); - v7Response.selectSection('month'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); - v7Response.pressKey(null, ''); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY'); - v7Response.selectSection('month'); + view.selectSection('month'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); + view.pressKey(null, ''); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); + view.selectSection('month'); - v7Response.pressKey(0, '1'); - expectFieldValueV7(v7Response.getSectionsContainer(), '01/DD/YYYY'); + view.pressKey(0, '1'); + expectFieldValueV7(view.getSectionsContainer(), '01/DD/YYYY'); - v7Response.pressKey(0, '1'); - expectFieldValueV7(v7Response.getSectionsContainer(), '11/DD/YYYY'); + view.pressKey(0, '1'); + expectFieldValueV7(view.getSectionsContainer(), '11/DD/YYYY'); - v7Response.pressKey(1, '2'); - v7Response.pressKey(1, '5'); - expectFieldValueV7(v7Response.getSectionsContainer(), '11/25/YYYY'); + view.pressKey(1, '2'); + view.pressKey(1, '5'); + expectFieldValueV7(view.getSectionsContainer(), '11/25/YYYY'); - v7Response.pressKey(2, '2'); - v7Response.pressKey(2, '0'); - v7Response.pressKey(2, '0'); - v7Response.pressKey(2, '9'); - expectFieldValueV7(v7Response.getSectionsContainer(), '11/25/2009'); + view.pressKey(2, '2'); + view.pressKey(2, '0'); + view.pressKey(2, '0'); + view.pressKey(2, '9'); + expectFieldValueV7(view.getSectionsContainer(), '11/25/2009'); expect(onChangeV7.lastCall.firstArg).toEqualDateTime(new Date(2009, 10, 25, 3, 3, 3)); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV6, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); - userEvent.keyPress(input, { key: 'a', ctrlKey: true }); + view.selectSection('month'); + fireUserEvent.keyPress(input, { key: 'a', ctrlKey: true }); fireEvent.change(input, { target: { value: '' } }); - userEvent.keyPress(input, { key: 'ArrowLeft' }); + fireUserEvent.keyPress(input, { key: 'ArrowLeft' }); fireEvent.change(input, { target: { value: '1/DD/YYYY' } }); // Press "1" expectFieldValueV6(input, '01/DD/YYYY'); @@ -1942,24 +1945,24 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: adapter.formats.year, defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV7, }); - v7Response.selectSection('year'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowDown' }); + view.selectSection('year'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowDown' }); expect(onChangeV7.lastCall.firstArg).toEqualDateTime(new Date(2009, 3, 3, 3, 3, 3)); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ format: adapter.formats.year, defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV6, @@ -1967,8 +1970,8 @@ describe(' - Editing', () => { }); const input = getTextbox(); - v6Response.selectSection('year'); - userEvent.keyPress(input, { key: 'ArrowDown' }); + view.selectSection('year'); + fireUserEvent.keyPress(input, { key: 'ArrowDown' }); expect(onChangeV6.lastCall.firstArg).toEqualDateTime(new Date(2009, 3, 3, 3, 3, 3)); }); @@ -1977,32 +1980,32 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: adapter.formats.month, defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV7, }); - v7Response.selectSection('month'); - userEvent.keyPress(v7Response.getActiveSection(0), { key: 'ArrowDown' }); + view.selectSection('month'); + fireUserEvent.keyPress(view.getActiveSection(0), { key: 'ArrowDown' }); expect(onChangeV7.lastCall.firstArg).toEqualDateTime(new Date(2010, 2, 3, 3, 3, 3)); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ format: adapter.formats.month, defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV6, enableAccessibleFieldDOMStructure: false, }); - v6Response.selectSection('month'); + view.selectSection('month'); const input = getTextbox(); - userEvent.keyPress(input, { key: 'ArrowDown' }); + fireUserEvent.keyPress(input, { key: 'ArrowDown' }); expect(onChangeV6.lastCall.firstArg).toEqualDateTime(new Date(2010, 2, 3, 3, 3, 3)); }); }, @@ -2015,16 +2018,16 @@ describe(' - Editing', () => { it('should set the date when the change value is valid and no value is provided', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange: onChangeV7, }); - fireEvent.change(v7Response.getHiddenInput(), { target: { value: '09/16/2022' } }); + fireEvent.change(view.getHiddenInput(), { target: { value: '09/16/2022' } }); expect(onChangeV7.callCount).to.equal(1); expect(onChangeV7.lastCall.firstArg).toEqualDateTime(new Date(2022, 8, 16)); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); @@ -2043,18 +2046,18 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV7, }); - fireEvent.change(v7Response.getHiddenInput(), { target: { value: '09/16/2022' } }); + fireEvent.change(view.getHiddenInput(), { target: { value: '09/16/2022' } }); expect(onChangeV7.callCount).to.equal(1); expect(onChangeV7.lastCall.firstArg).toEqualDateTime(new Date(2022, 8, 16, 3, 3, 3)); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); @@ -2098,7 +2101,7 @@ describe(' - Editing', () => { }); it('should support digit editing', () => { - const v6Response = renderWithProps({ + const view = renderWithProps({ defaultValue: adapter.date('2022-11-23'), enableAccessibleFieldDOMStructure: false, }); @@ -2106,53 +2109,45 @@ describe(' - Editing', () => { const input = getTextbox(); const initialValueStr = input.value; - v6Response.selectSection('day'); + view.selectSection('day'); - act(() => { - // Remove the selected section - fireEvent.change(input, { target: { value: initialValueStr.replace('23', '') } }); + // Remove the selected section + fireEvent.change(input, { target: { value: initialValueStr.replace('23', '') } }); - // Set the key pressed in the selected section - fireEvent.change(input, { target: { value: initialValueStr.replace('23', '2') } }); - }); + // Set the key pressed in the selected section + fireEvent.change(input, { target: { value: initialValueStr.replace('23', '2') } }); - act(() => { - // Remove the selected section - fireEvent.change(input, { target: { value: initialValueStr.replace('23', '') } }); + // Remove the selected section + fireEvent.change(input, { target: { value: initialValueStr.replace('23', '') } }); - // Set the key pressed in the selected section - fireEvent.change(input, { target: { value: initialValueStr.replace('23', '1') } }); - }); + // Set the key pressed in the selected section + fireEvent.change(input, { target: { value: initialValueStr.replace('23', '1') } }); expectFieldValueV6(input, '11/01/2022'); }); it('should support letter editing', () => { // Test with v6 input - const v6Response = renderWithProps({ + const view = renderWithProps({ defaultValue: adapter.date('2022-01-16'), format: `${adapter.formats.month} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); - act(() => { - // Remove the selected section - fireEvent.change(input, { target: { value: ' 2022' } }); + // Remove the selected section + fireEvent.change(input, { target: { value: ' 2022' } }); - // Set the key pressed in the selected section - fireEvent.change(input, { target: { value: 'J 2022' } }); - }); + // Set the key pressed in the selected section + fireEvent.change(input, { target: { value: 'J 2022' } }); - act(() => { - // Remove the selected section - fireEvent.change(input, { target: { value: ' 2022' } }); + // Remove the selected section + fireEvent.change(input, { target: { value: ' 2022' } }); - // Set the key pressed in the selected section - fireEvent.change(input, { target: { value: 'a 2022' } }); - }); + // Set the key pressed in the selected section + fireEvent.change(input, { target: { value: 'a 2022' } }); expectFieldValueV6(input, 'April 2022'); }); @@ -2162,76 +2157,76 @@ describe(' - Editing', () => { describeAdapters('Editing from the outside', DateField, ({ adapter, renderWithProps, clock }) => { it('should be able to reset the value from the outside', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: adapter.date('2022-11-23'), }); - expectFieldValueV7(v7Response.getSectionsContainer(), '11/23/2022'); + expectFieldValueV7(view.getSectionsContainer(), '11/23/2022'); - v7Response.setProps({ value: null }); + view.setProps({ value: null }); - v7Response.selectSection('month'); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY'); + view.selectSection('month'); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ value: adapter.date('2022-11-23'), enableAccessibleFieldDOMStructure: false, }); const input = getTextbox(); expectFieldValueV6(input, '11/23/2022'); - v6Response.setProps({ value: null }); + view.setProps({ value: null }); - v6Response.selectSection('month'); + view.selectSection('month'); expectFieldValueV6(input, 'MM/DD/YYYY'); }); it('should reset the input query state on an unfocused field', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: null }); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: null }); - v7Response.selectSection('month'); + view.selectSection('month'); - v7Response.pressKey(0, '1'); - expectFieldValueV7(v7Response.getSectionsContainer(), '01/DD/YYYY'); + view.pressKey(0, '1'); + expectFieldValueV7(view.getSectionsContainer(), '01/DD/YYYY'); - v7Response.pressKey(0, '1'); - expectFieldValueV7(v7Response.getSectionsContainer(), '11/DD/YYYY'); + view.pressKey(0, '1'); + expectFieldValueV7(view.getSectionsContainer(), '11/DD/YYYY'); - v7Response.pressKey(1, '2'); - v7Response.pressKey(1, '5'); - expectFieldValueV7(v7Response.getSectionsContainer(), '11/25/YYYY'); + view.pressKey(1, '2'); + view.pressKey(1, '5'); + expectFieldValueV7(view.getSectionsContainer(), '11/25/YYYY'); - v7Response.pressKey(2, '2'); - v7Response.pressKey(2, '0'); - expectFieldValueV7(v7Response.getSectionsContainer(), '11/25/0020'); + view.pressKey(2, '2'); + view.pressKey(2, '0'); + expectFieldValueV7(view.getSectionsContainer(), '11/25/0020'); act(() => { - v7Response.getSectionsContainer().blur(); + view.getSectionsContainer().blur(); }); clock.runToLast(); - v7Response.setProps({ value: adapter.date('2022-11-23') }); - expectFieldValueV7(v7Response.getSectionsContainer(), '11/23/2022'); + view.setProps({ value: adapter.date('2022-11-23') }); + expectFieldValueV7(view.getSectionsContainer(), '11/23/2022'); - v7Response.selectSection('year'); + view.selectSection('year'); - v7Response.pressKey(2, '2'); - expectFieldValueV7(v7Response.getSectionsContainer(), '11/23/0002'); - v7Response.pressKey(2, '1'); - expectFieldValueV7(v7Response.getSectionsContainer(), '11/23/0021'); + view.pressKey(2, '2'); + expectFieldValueV7(view.getSectionsContainer(), '11/23/0002'); + view.pressKey(2, '1'); + expectFieldValueV7(view.getSectionsContainer(), '11/23/0021'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false, value: null }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, value: null }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); fireEvent.change(input, { target: { value: '1/DD/YYYY' } }); // Press "1" expectFieldValueV6(input, '01/DD/YYYY'); @@ -2251,15 +2246,15 @@ describe(' - Editing', () => { input.blur(); }); - v6Response.setProps({ value: adapter.date('2022-11-23') }); + view.setProps({ value: adapter.date('2022-11-23') }); expectFieldValueV6(input, '11/23/2022'); + fireEvent.mouseDown(input); + fireEvent.mouseUp(input); act(() => { - fireEvent.mouseDown(input); - fireEvent.mouseUp(input); input.setSelectionRange(6, 9); - fireEvent.click(input); }); + fireEvent.click(input); fireEvent.change(input, { target: { value: '11/23/2' } }); // Press "2" expectFieldValueV6(input, '11/23/0002'); @@ -2271,26 +2266,26 @@ describe(' - Editing', () => { describeAdapters('Select all', DateField, ({ renderWithProps }) => { it('should edit the 1st section when all sections are selected', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - v7Response.selectSection('month'); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); // When all sections are selected, the value only contains the key pressed - v7Response.pressKey(null, '9'); + view.pressKey(null, '9'); - expectFieldValueV7(v7Response.getSectionsContainer(), '09/DD/YYYY'); + expectFieldValueV7(view.getSectionsContainer(), '09/DD/YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); - v6Response.selectSection('month'); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view.selectSection('month'); const input = getTextbox(); // Select all sections - userEvent.keyPress(input, { key: 'a', ctrlKey: true }); + fireUserEvent.keyPress(input, { key: 'a', ctrlKey: true }); // When all sections are selected, the value only contains the key pressed fireEvent.change(input, { target: { value: '9' } }); diff --git a/packages/x-date-pickers/src/DateField/tests/format.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/format.DateField.test.tsx index f680275405ce3..67c0df9d5edcf 100644 --- a/packages/x-date-pickers/src/DateField/tests/format.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/format.DateField.test.tsx @@ -12,20 +12,20 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp const { start: startChar, end: endChar } = adapter.escapedCharacters; // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, // For Day.js: "[Escaped] YYYY" format: `${startChar}Escaped${endChar} ${adapter.formats.year}`, }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'Escaped YYYY'); + expectFieldValueV7(view.getSectionsContainer(), 'Escaped YYYY'); - v7Response.setProps({ value: adapter.date('2019-01-01') }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'Escaped 2019'); + view.setProps({ value: adapter.date('2019-01-01') }); + expectFieldValueV7(view.getSectionsContainer(), 'Escaped 2019'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ // For Day.js: "[Escaped] YYYY" format: `${startChar}Escaped${endChar} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, @@ -33,7 +33,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp const input = getTextbox(); expectFieldPlaceholderV6(input, 'Escaped YYYY'); - v6Response.setProps({ value: adapter.date('2019-01-01') }); + view.setProps({ value: adapter.date('2019-01-01') }); expectFieldValueV6(input, 'Escaped 2019'); }); @@ -41,21 +41,21 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp const { start: startChar, end: endChar } = adapter.escapedCharacters; // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, // For Day.js: "MMMM [Escaped] YYYY" format: `${adapter.formats.month} ${startChar}Escaped${endChar} ${adapter.formats.year}`, }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM Escaped YYYY'); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM Escaped YYYY'); - v7Response.setProps({ value: adapter.date('2019-01-01') }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'January Escaped 2019'); + view.setProps({ value: adapter.date('2019-01-01') }); + expectFieldValueV7(view.getSectionsContainer(), 'January Escaped 2019'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ // For Day.js: "MMMM [Escaped] YYYY" format: `${adapter.formats.month} ${startChar}Escaped${endChar} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, @@ -64,7 +64,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp const input = getTextbox(); expectFieldPlaceholderV6(input, 'MMMM Escaped YYYY'); - v6Response.setProps({ value: adapter.date('2019-01-01') }); + view.setProps({ value: adapter.date('2019-01-01') }); expectFieldValueV6(input, 'January Escaped 2019'); }); @@ -77,21 +77,21 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp } // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, // For Day.js: "MMMM [Escaped[] YYYY" format: `${adapter.formats.month} ${startChar}Escaped ${startChar}${endChar} ${adapter.formats.year}`, }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM Escaped [ YYYY'); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM Escaped [ YYYY'); - v7Response.setProps({ value: adapter.date('2019-01-01') }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'January Escaped [ 2019'); + view.setProps({ value: adapter.date('2019-01-01') }); + expectFieldValueV7(view.getSectionsContainer(), 'January Escaped [ 2019'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ // For Day.js: "MMMM [Escaped[] YYYY" format: `${adapter.formats.month} ${startChar}Escaped ${startChar}${endChar} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, @@ -100,7 +100,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp const input = getTextbox(); expectFieldPlaceholderV6(input, 'MMMM Escaped [ YYYY'); - v6Response.setProps({ value: adapter.date('2019-01-01') }); + view.setProps({ value: adapter.date('2019-01-01') }); expectFieldValueV6(input, 'January Escaped [ 2019'); }); @@ -108,21 +108,21 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp const { start: startChar, end: endChar } = adapter.escapedCharacters; // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, // For Day.js: "[Escaped] MMMM [Escaped] YYYY" format: `${startChar}Escaped${endChar} ${adapter.formats.month} ${startChar}Escaped${endChar} ${adapter.formats.year}`, }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'Escaped MMMM Escaped YYYY'); + expectFieldValueV7(view.getSectionsContainer(), 'Escaped MMMM Escaped YYYY'); - v7Response.setProps({ value: adapter.date('2019-01-01') }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'Escaped January Escaped 2019'); + view.setProps({ value: adapter.date('2019-01-01') }); + expectFieldValueV7(view.getSectionsContainer(), 'Escaped January Escaped 2019'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ // For Day.js: "[Escaped] MMMM [Escaped] YYYY" format: `${startChar}Escaped${endChar} ${adapter.formats.month} ${startChar}Escaped${endChar} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, @@ -131,7 +131,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp const input = getTextbox(); expectFieldPlaceholderV6(input, 'Escaped MMMM Escaped YYYY'); - v6Response.setProps({ value: adapter.date('2019-01-01') }); + view.setProps({ value: adapter.date('2019-01-01') }); expectFieldValueV6(input, 'Escaped January Escaped 2019'); }); @@ -139,15 +139,15 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp const { start: startChar, end: endChar } = adapter.escapedCharacters; // Test with v7 input - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, // For Day.js: "[Escaped] [Escaped]" format: `${startChar}Escaped${endChar} ${startChar}Escaped${endChar}`, }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'Escaped Escaped'); + expectFieldValueV7(view.getSectionsContainer(), 'Escaped Escaped'); - v7Response.unmount(); + view.unmount(); // Test with v6 input renderWithProps({ @@ -161,30 +161,30 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp }); it('should support format without separators', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.dayOfMonth}${adapter.formats.monthShort}`, }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'DDMMMM'); + expectFieldValueV7(view.getSectionsContainer(), 'DDMMMM'); }); it('should add spaces around `/` when `formatDensity = "spacious"`', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, formatDensity: `spacious`, }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM / DD / YYYY'); + expectFieldValueV7(view.getSectionsContainer(), 'MM / DD / YYYY'); - v7Response.setProps({ value: adapter.date('2019-01-01') }); - expectFieldValueV7(v7Response.getSectionsContainer(), '01 / 01 / 2019'); + view.setProps({ value: adapter.date('2019-01-01') }); + expectFieldValueV7(view.getSectionsContainer(), '01 / 01 / 2019'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ formatDensity: `spacious`, enableAccessibleFieldDOMStructure: false, }); @@ -192,27 +192,27 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp const input = getTextbox(); expectFieldPlaceholderV6(input, 'MM / DD / YYYY'); - v6Response.setProps({ value: adapter.date('2019-01-01') }); + view.setProps({ value: adapter.date('2019-01-01') }); expectFieldValueV6(input, '01 / 01 / 2019'); }); it('should add spaces around `.` when `formatDensity = "spacious"`', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, formatDensity: `spacious`, format: adapter.expandFormat(adapter.formats.keyboardDate).replace(/\//g, '.'), }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM . DD . YYYY'); + expectFieldValueV7(view.getSectionsContainer(), 'MM . DD . YYYY'); - v7Response.setProps({ value: adapter.date('2019-01-01') }); - expectFieldValueV7(v7Response.getSectionsContainer(), '01 . 01 . 2019'); + view.setProps({ value: adapter.date('2019-01-01') }); + expectFieldValueV7(view.getSectionsContainer(), '01 . 01 . 2019'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ formatDensity: `spacious`, format: adapter.expandFormat(adapter.formats.keyboardDate).replace(/\//g, '.'), enableAccessibleFieldDOMStructure: false, @@ -221,27 +221,27 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp const input = getTextbox(); expectFieldPlaceholderV6(input, 'MM . DD . YYYY'); - v6Response.setProps({ value: adapter.date('2019-01-01') }); + view.setProps({ value: adapter.date('2019-01-01') }); expectFieldValueV6(input, '01 . 01 . 2019'); }); it('should add spaces around `-` when `formatDensity = "spacious"`', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, formatDensity: `spacious`, format: adapter.expandFormat(adapter.formats.keyboardDate).replace(/\//g, '-'), }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM - DD - YYYY'); + expectFieldValueV7(view.getSectionsContainer(), 'MM - DD - YYYY'); - v7Response.setProps({ value: adapter.date('2019-01-01') }); - expectFieldValueV7(v7Response.getSectionsContainer(), '01 - 01 - 2019'); + view.setProps({ value: adapter.date('2019-01-01') }); + expectFieldValueV7(view.getSectionsContainer(), '01 - 01 - 2019'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ formatDensity: `spacious`, format: adapter.expandFormat(adapter.formats.keyboardDate).replace(/\//g, '-'), enableAccessibleFieldDOMStructure: false, @@ -250,7 +250,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp const input = getTextbox(); expectFieldPlaceholderV6(input, 'MM - DD - YYYY'); - v6Response.setProps({ value: adapter.date('2019-01-01') }); + view.setProps({ value: adapter.date('2019-01-01') }); expectFieldValueV6(input, '01 - 01 - 2019'); }); }); diff --git a/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx index 4036ee7beaf68..e0b6a0114dd10 100644 --- a/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/selection.DateField.test.tsx @@ -18,13 +18,13 @@ describe(' - Selection', () => { describe('Focus', () => { it('should select 1st section (v7) / all sections (v6) on mount focus (`autoFocus = true`)', () => { // Text with v7 input - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, autoFocus: true, }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY'); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); expect(getCleanedSelectedContent()).to.equal('MM'); - v7Response.unmount(); + view.unmount(); // Text with v6 input renderWithProps({ enableAccessibleFieldDOMStructure: false, autoFocus: true }); @@ -35,14 +35,14 @@ describe(' - Selection', () => { it('should select 1st section (v7) / all sections (v6) (`autoFocus = true`) with start separator', () => { // Text with v7 input - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, autoFocus: true, format: `- ${adapterToUse.formats.year}`, }); - expectFieldValueV7(v7Response.getSectionsContainer(), '- YYYY'); + expectFieldValueV7(view.getSectionsContainer(), '- YYYY'); expect(getCleanedSelectedContent()).to.equal('YYYY'); - v7Response.unmount(); + view.unmount(); // Text with v6 input renderWithProps({ @@ -109,10 +109,10 @@ describe(' - Selection', () => { it('should select day on desktop (v6 only)', () => { // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + const view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); - v6Response.selectSection('day'); + view.selectSection('day'); expectFieldValueV6(input, 'MM/DD/YYYY'); expect(getCleanedSelectedContent()).to.equal('DD'); @@ -122,45 +122,45 @@ describe(' - Selection', () => { describe('Click', () => { it('should select the clicked selection when the input is already focused', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - v7Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); - v7Response.selectSection('month'); + view.selectSection('month'); expect(getCleanedSelectedContent()).to.equal('MM'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); - v6Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); - v6Response.selectSection('month'); + view.selectSection('month'); expect(getCleanedSelectedContent()).to.equal('MM'); }); it('should not change the selection when clicking on the only already selected section', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); - v7Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); - v7Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); - v6Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); - v6Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); }); }); @@ -168,40 +168,40 @@ describe(' - Selection', () => { describe('key: Ctrl + A', () => { it('should select all sections', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - v7Response.selectSection('month'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + view.selectSection('month'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); expect(getCleanedSelectedContent()).to.equal('MM/DD/YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); fireEvent.keyDown(input, { key: 'a', ctrlKey: true }); expect(getCleanedSelectedContent()).to.equal('MM/DD/YYYY'); }); it('should select all sections with start separator', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `- ${adapterToUse.formats.year}`, }); - v7Response.selectSection('year'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + view.selectSection('year'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); expect(getCleanedSelectedContent()).to.equal('- YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `- ${adapterToUse.formats.year}`, }); const input = getTextbox(); - v6Response.selectSection('year'); + view.selectSection('year'); fireEvent.keyDown(input, { key: 'a', ctrlKey: true }); expect(getCleanedSelectedContent()).to.equal('- YYYY'); }); @@ -210,17 +210,17 @@ describe(' - Selection', () => { describe('key: ArrowRight', () => { it('should move selection to the next section when one section is selected', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - v7Response.selectSection('day'); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); - fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(1), { key: 'ArrowRight' }); expect(getCleanedSelectedContent()).to.equal('YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); - v6Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); fireEvent.keyDown(input, { key: 'ArrowRight' }); expect(getCleanedSelectedContent()).to.equal('YYYY'); @@ -228,17 +228,17 @@ describe(' - Selection', () => { it('should stay on the current section when the last section is selected', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - v7Response.selectSection('year'); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + view.selectSection('year'); expect(getCleanedSelectedContent()).to.equal('YYYY'); - fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(2), { key: 'ArrowRight' }); expect(getCleanedSelectedContent()).to.equal('YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); - v6Response.selectSection('year'); + view.selectSection('year'); expect(getCleanedSelectedContent()).to.equal('YYYY'); fireEvent.keyDown(input, { key: 'ArrowRight' }); expect(getCleanedSelectedContent()).to.equal('YYYY'); @@ -246,22 +246,22 @@ describe(' - Selection', () => { it('should select the last section when all the sections are selected', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - v7Response.selectSection('month'); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); expect(getCleanedSelectedContent()).to.equal('MM/DD/YYYY'); - fireEvent.keyDown(v7Response.getSectionsContainer(), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getSectionsContainer(), { key: 'ArrowRight' }); expect(getCleanedSelectedContent()).to.equal('YYYY'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Select all sections fireEvent.keyDown(input, { key: 'a', ctrlKey: true }); @@ -275,17 +275,17 @@ describe(' - Selection', () => { describe('key: ArrowLeft', () => { it('should move selection to the previous section when one section is selected', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - v7Response.selectSection('day'); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); - fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowLeft' }); + fireEvent.keyDown(view.getActiveSection(1), { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('MM'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); - v6Response.selectSection('day'); + view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); fireEvent.keyDown(input, { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('MM'); @@ -293,17 +293,17 @@ describe(' - Selection', () => { it('should stay on the current section when the first section is selected', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - v7Response.selectSection('month'); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + view.selectSection('month'); expect(getCleanedSelectedContent()).to.equal('MM'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowLeft' }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('MM'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); expect(getCleanedSelectedContent()).to.equal('MM'); fireEvent.keyDown(input, { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('MM'); @@ -311,22 +311,22 @@ describe(' - Selection', () => { it('should select the first section when all the sections are selected', () => { // Test with v7 input - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); - v7Response.selectSection('month'); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + view.selectSection('month'); // Select all sections - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); expect(getCleanedSelectedContent()).to.equal('MM/DD/YYYY'); - fireEvent.keyDown(v7Response.getSectionsContainer(), { key: 'ArrowLeft' }); + fireEvent.keyDown(view.getSectionsContainer(), { key: 'ArrowLeft' }); expect(getCleanedSelectedContent()).to.equal('MM'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ enableAccessibleFieldDOMStructure: false }); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); // Select all sections fireEvent.keyDown(input, { key: 'a', ctrlKey: true }); diff --git a/packages/x-date-pickers/src/DateTimeField/tests/editing.DateTimeField.test.tsx b/packages/x-date-pickers/src/DateTimeField/tests/editing.DateTimeField.test.tsx index cd0885e18eef0..2abe7bd407d27 100644 --- a/packages/x-date-pickers/src/DateTimeField/tests/editing.DateTimeField.test.tsx +++ b/packages/x-date-pickers/src/DateTimeField/tests/editing.DateTimeField.test.tsx @@ -26,15 +26,15 @@ describe(' - Editing', () => { const onChange = spy(); const referenceDate = adapterToUse.date('2012-05-03T14:30:00'); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange, referenceDate, format: adapterToUse.formats.month, }); - v7Response.selectSection('month'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowUp' }); + view.selectSection('month'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowUp' }); // All sections not present should equal the one from the referenceDate, and the month should equal January (because it's an ArrowUp on an empty month). expect(onChange.lastCall.firstArg).toEqualDateTime(adapterToUse.setMonth(referenceDate, 0)); @@ -45,7 +45,7 @@ describe(' - Editing', () => { const value = adapterToUse.date('2018-11-03T22:15:00'); const referenceDate = adapterToUse.date('2012-05-03T14:30:00'); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange, referenceDate, @@ -53,8 +53,8 @@ describe(' - Editing', () => { format: adapterToUse.formats.month, }); - v7Response.selectSection('month'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowUp' }); + view.selectSection('month'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowUp' }); // Should equal the initial `value` prop with one less month. expect(onChange.lastCall.firstArg).toEqualDateTime(adapterToUse.setMonth(value, 11)); @@ -65,7 +65,7 @@ describe(' - Editing', () => { const defaultValue = adapterToUse.date('2018-11-03T22:15:00'); const referenceDate = adapterToUse.date('2012-05-03T14:30:00'); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange, referenceDate, @@ -73,8 +73,8 @@ describe(' - Editing', () => { format: adapterToUse.formats.month, }); - v7Response.selectSection('month'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowUp' }); + view.selectSection('month'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowUp' }); // Should equal the initial `defaultValue` prop with one less month. expect(onChange.lastCall.firstArg).toEqualDateTime(adapterToUse.setMonth(defaultValue, 11)); @@ -84,14 +84,14 @@ describe(' - Editing', () => { it('should only keep year when granularity = month', () => { const onChange = spy(); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange, format: adapterToUse.formats.month, }); - v7Response.selectSection('month'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowUp' }); + view.selectSection('month'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowUp' }); expect(onChange.lastCall.firstArg).toEqualDateTime('2012-01-01'); }); @@ -99,14 +99,14 @@ describe(' - Editing', () => { it('should only keep year and month when granularity = day', () => { const onChange = spy(); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange, format: adapterToUse.formats.dayOfMonth, }); - v7Response.selectSection('day'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowUp' }); + view.selectSection('day'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowUp' }); expect(onChange.lastCall.firstArg).toEqualDateTime('2012-05-01'); }); @@ -114,20 +114,20 @@ describe(' - Editing', () => { it('should only keep up to the hours when granularity = minutes', () => { const onChange = spy(); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange, format: adapterToUse.formats.fullTime24h, }); - v7Response.selectSection('hours'); + view.selectSection('hours'); // Set hours - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowUp' }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowUp' }); // Set minutes - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowRight' }); - fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowUp' }); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(1), { key: 'ArrowUp' }); expect(onChange.lastCall.firstArg).toEqualDateTime('2012-05-03T00:00:00.000Z'); }); @@ -138,15 +138,15 @@ describe(' - Editing', () => { const onChange = spy(); const minDate = adapterToUse.date('2030-05-05T18:30:00'); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange, minDate, format: adapterToUse.formats.month, }); - v7Response.selectSection('month'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowUp' }); + view.selectSection('month'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowUp' }); // Respect the granularity and the minDate expect(onChange.lastCall.firstArg).toEqualDateTime('2030-01-01T00:00'); @@ -156,15 +156,15 @@ describe(' - Editing', () => { const onChange = spy(); const minDate = adapterToUse.date('2007-05-05T18:30:00'); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange, minDate, format: adapterToUse.formats.month, }); - v7Response.selectSection('month'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowUp' }); + view.selectSection('month'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowUp' }); // Respect the granularity but not the minDate expect(onChange.lastCall.firstArg).toEqualDateTime('2012-01-01T00:00'); @@ -174,15 +174,15 @@ describe(' - Editing', () => { const onChange = spy(); const maxDate = adapterToUse.date('2007-05-05T18:30:00'); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange, maxDate, format: adapterToUse.formats.month, }); - v7Response.selectSection('month'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowUp' }); + view.selectSection('month'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowUp' }); // Respect the granularity and the minDate expect(onChange.lastCall.firstArg).toEqualDateTime('2007-01-01T00:00'); @@ -192,15 +192,15 @@ describe(' - Editing', () => { const onChange = spy(); const maxDate = adapterToUse.date('2030-05-05T18:30:00'); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange, maxDate, format: adapterToUse.formats.month, }); - v7Response.selectSection('month'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowUp' }); + view.selectSection('month'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowUp' }); // Respect the granularity but not the maxDate expect(onChange.lastCall.firstArg).toEqualDateTime('2012-01-01T00:00'); @@ -209,17 +209,17 @@ describe(' - Editing', () => { }); it('should correctly update `value` when both `format` and `value` are changed', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: null, format: 'P', }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY'); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); - v7Response.setProps({ + view.setProps({ format: 'Pp', value: adapterToUse.date('2012-05-03T14:30:00'), }); - expectFieldValueV7(v7Response.getSectionsContainer(), '05/03/2012, 02:30 PM'); + expectFieldValueV7(view.getSectionsContainer(), '05/03/2012, 02:30 PM'); }); }); diff --git a/packages/x-date-pickers/src/DateTimeField/tests/timezone.DateTimeField.test.tsx b/packages/x-date-pickers/src/DateTimeField/tests/timezone.DateTimeField.test.tsx index 1cc3a46e79727..9ff18ca2bf5e7 100644 --- a/packages/x-date-pickers/src/DateTimeField/tests/timezone.DateTimeField.test.tsx +++ b/packages/x-date-pickers/src/DateTimeField/tests/timezone.DateTimeField.test.tsx @@ -46,16 +46,16 @@ describe(' - Timezone', () => { it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { const onChange = spy(); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange, format, }); - const expectedDate = fillEmptyValue(v7Response, 'default'); + const expectedDate = fillEmptyValue(view, 'default'); // Check the rendered value (uses default timezone, e.g: UTC, see TZ env variable) - expectFieldValueV7(v7Response.getSectionsContainer(), '12/31/2022 23'); + expectFieldValueV7(view.getSectionsContainer(), '12/31/2022 23'); // Check the `onChange` value (uses default timezone, e.g: UTC, see TZ env variable) const actualDate = onChange.lastCall.firstArg; @@ -70,16 +70,16 @@ describe(' - Timezone', () => { describe(`Timezone: ${timezone}`, () => { it('should use timezone prop for onChange and rendering when no value is provided', () => { const onChange = spy(); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, onChange, format, timezone, }); - const expectedDate = fillEmptyValue(v7Response, timezone); + const expectedDate = fillEmptyValue(view, timezone); // Check the rendered value (uses timezone prop) - expectFieldValueV7(v7Response.getSectionsContainer(), '12/31/2022 23'); + expectFieldValueV7(view.getSectionsContainer(), '12/31/2022 23'); // Check the `onChange` value (uses timezone prop) const actualDate = onChange.lastCall.firstArg; @@ -89,7 +89,7 @@ describe(' - Timezone', () => { it('should use timezone prop for rendering and value timezone for onChange when a value is provided', () => { const onChange = spy(); - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: adapter.date(undefined, timezone), onChange, @@ -97,11 +97,11 @@ describe(' - Timezone', () => { timezone: 'America/Chicago', }); - v7Response.selectSection('month'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowDown' }); + view.selectSection('month'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowDown' }); // Check the rendered value (uses America/Chicago timezone) - expectFieldValueV7(v7Response.getSectionsContainer(), '05/14/2022 19'); + expectFieldValueV7(view.getSectionsContainer(), '05/14/2022 19'); // Check the `onChange` value (uses timezone prop) const expectedDate = adapter.addMonths(adapter.date(undefined, timezone), -1); @@ -125,16 +125,16 @@ describe(' - Timezone', () => { }); it('should update the field when time zone changes (timestamp remains the same)', () => { - const v7Response = renderWithProps({ enableAccessibleFieldDOMStructure: true }); + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); const date = adapter.date('2020-06-18T14:30:10.000Z').setZone('UTC'); - v7Response.setProps({ value: date }); + view.setProps({ value: date }); - expectFieldValueV7(v7Response.getSectionsContainer(), '06/18/2020 02:30 PM'); + expectFieldValueV7(view.getSectionsContainer(), '06/18/2020 02:30 PM'); - v7Response.setProps({ value: date.setZone('America/Los_Angeles') }); + view.setProps({ value: date.setZone('America/Los_Angeles') }); - expectFieldValueV7(v7Response.getSectionsContainer(), '06/18/2020 07:30 AM'); + expectFieldValueV7(view.getSectionsContainer(), '06/18/2020 07:30 AM'); }); }); }); 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 b3ed68ab68516..5d90362ce0788 100644 --- a/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { spy } from 'sinon'; import { TransitionProps } from '@mui/material/transitions'; import { inputBaseClasses } from '@mui/material/InputBase'; -import { fireEvent, screen, userEvent } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { DesktopDatePicker } from '@mui/x-date-pickers/DesktopDatePicker'; import { createPickerRenderer, adapterToUse, openPicker } from 'test/utils/pickers'; @@ -46,7 +46,8 @@ describe('', () => { expect(handleViewChange.callCount).to.equal(1); // Dismiss the picker - userEvent.keyPress(document.activeElement!, { key: 'Escape' }); + // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target + fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); openPicker({ type: 'date', variant: 'desktop' }); expect(handleViewChange.callCount).to.equal(2); @@ -71,7 +72,8 @@ describe('', () => { expect(handleViewChange.callCount).to.equal(1); // Dismiss the picker - userEvent.keyPress(document.activeElement!, { key: 'Escape' }); + // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target + fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); openPicker({ type: 'date', variant: 'desktop' }); expect(handleViewChange.callCount).to.equal(2); @@ -88,7 +90,8 @@ describe('', () => { expect(screen.getByRole('radio', { checked: true, name: '2018' })).not.to.equal(null); // Dismiss the picker - userEvent.keyPress(document.activeElement!, { key: 'Escape' }); + // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target + fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); setProps({ views: ['month', 'year'] }); openPicker({ type: 'date', variant: 'desktop' }); // wait for all pending changes to be flushed @@ -125,7 +128,8 @@ describe('', () => { expect(screen.getByRole('radio', { checked: true, name: 'January' })).not.to.equal(null); // Dismiss the picker - userEvent.keyPress(document.activeElement!, { key: 'Escape' }); + // eslint-disable-next-line material-ui/disallow-active-element-as-key-event-target + fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); setProps({ view: 'year' }); openPicker({ type: 'date', variant: 'desktop' }); // wait for all pending changes to be flushed @@ -220,7 +224,7 @@ describe('', () => { render(); - userEvent.mousePress(screen.getByLabelText(/Choose date/)); + fireEvent.click(screen.getByLabelText(/Choose date/)); expect(onOpen.callCount).to.equal(1); expect(screen.queryByRole('dialog')).toBeVisible(); @@ -244,14 +248,14 @@ describe('', () => { openPicker({ type: 'date', variant: 'desktop' }); // Select year - userEvent.mousePress(screen.getByRole('radio', { name: '2025' })); + fireEvent.click(screen.getByRole('radio', { name: '2025' })); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2025, 0, 1)); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); // Change the date (same value) - userEvent.mousePress(screen.getByRole('gridcell', { name: '1' })); + fireEvent.click(screen.getByRole('gridcell', { name: '1' })); expect(onChange.callCount).to.equal(1); // Don't call onChange again since the value did not change expect(onAccept.callCount).to.equal(1); expect(onAccept.lastCall.args[0]).toEqualDateTime(new Date(2025, 0, 1)); diff --git a/packages/x-date-pickers/src/DesktopDatePicker/tests/describes.DesktopDatePicker.test.tsx b/packages/x-date-pickers/src/DesktopDatePicker/tests/describes.DesktopDatePicker.test.tsx index 0357d40ba542d..5c56072e2b190 100644 --- a/packages/x-date-pickers/src/DesktopDatePicker/tests/describes.DesktopDatePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDatePicker/tests/describes.DesktopDatePicker.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { screen, userEvent } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { createPickerRenderer, adapterToUse, @@ -62,7 +62,7 @@ describe(' - Describes', () => { const newValue = applySameValue ? value : adapterToUse.addDays(value, 1); if (isOpened) { - userEvent.mousePress( + fireEvent.click( screen.getByRole('gridcell', { name: adapterToUse.getDate(newValue).toString() }), ); } else { diff --git a/packages/x-date-pickers/src/DesktopDatePicker/tests/field.DesktopDatePicker.test.tsx b/packages/x-date-pickers/src/DesktopDatePicker/tests/field.DesktopDatePicker.test.tsx index 808dce9bbcf9d..4ef2b7327fab4 100644 --- a/packages/x-date-pickers/src/DesktopDatePicker/tests/field.DesktopDatePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDatePicker/tests/field.DesktopDatePicker.test.tsx @@ -25,7 +25,7 @@ describe(' - Field', () => { it('should be able to reset a single section', () => { // Test with v7 input - const v7Response = renderWithProps( + let view = renderWithProps( { enableAccessibleFieldDOMStructure: true as const, format: `${adapterToUse.formats.month} ${adapterToUse.formats.dayOfMonth}`, @@ -33,22 +33,22 @@ describe(' - Field', () => { { componentFamily: 'picker' }, ); - v7Response.selectSection('month'); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM DD'); + view.selectSection('month'); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM DD'); - v7Response.pressKey(0, 'N'); - expectFieldValueV7(v7Response.getSectionsContainer(), 'November DD'); + view.pressKey(0, 'N'); + expectFieldValueV7(view.getSectionsContainer(), 'November DD'); - v7Response.pressKey(1, '4'); - expectFieldValueV7(v7Response.getSectionsContainer(), 'November 04'); + view.pressKey(1, '4'); + expectFieldValueV7(view.getSectionsContainer(), 'November 04'); - v7Response.pressKey(1, ''); - expectFieldValueV7(v7Response.getSectionsContainer(), 'November DD'); + view.pressKey(1, ''); + expectFieldValueV7(view.getSectionsContainer(), 'November DD'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps( + view = renderWithProps( { enableAccessibleFieldDOMStructure: false as const, format: `${adapterToUse.formats.month} ${adapterToUse.formats.dayOfMonth}`, @@ -57,7 +57,7 @@ describe(' - Field', () => { ); const input = getTextbox(); - v6Response.selectSection('month'); + view.selectSection('month'); expectFieldPlaceholderV6(input, 'MMMM DD'); fireEvent.change(input, { target: { value: 'N DD' } }); // Press "N" @@ -73,21 +73,21 @@ describe(' - Field', () => { it('should adapt the default field format based on the props of the picker', () => { const testFormat = (props: DesktopDatePickerProps, expectedFormat: string) => { // Test with v7 input - const v7Response = renderWithProps( + let view = renderWithProps( { ...props, enableAccessibleFieldDOMStructure: true as const }, { componentFamily: 'picker' }, ); - expectFieldValueV7(v7Response.getSectionsContainer(), expectedFormat); - v7Response.unmount(); + expectFieldValueV7(view.getSectionsContainer(), expectedFormat); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps( + view = renderWithProps( { ...props, enableAccessibleFieldDOMStructure: false as const }, { componentFamily: 'picker' }, ); const input = getTextbox(); expectFieldPlaceholderV6(input, expectedFormat); - v6Response.unmount(); + view.unmount(); }; testFormat({ views: ['year'] }, 'YYYY'); @@ -171,7 +171,7 @@ describe(' - Field', () => { describeAdapters('Timezone', DesktopDatePicker, ({ adapter, renderWithProps }) => { it('should clear the selected section when all sections are completed when using timezones', () => { - const v7Response = renderWithProps( + const view = renderWithProps( { enableAccessibleFieldDOMStructure: true as const, value: adapter.date()!, @@ -181,11 +181,11 @@ describe(' - Field', () => { { componentFamily: 'picker' }, ); - expectFieldValueV7(v7Response.getSectionsContainer(), 'June 2022'); - v7Response.selectSection('month'); + expectFieldValueV7(view.getSectionsContainer(), 'June 2022'); + view.selectSection('month'); - v7Response.pressKey(0, ''); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MMMM 2022'); + view.pressKey(0, ''); + expectFieldValueV7(view.getSectionsContainer(), 'MMMM 2022'); }); }); }); diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx index ade01ebb4a700..c298769075bb2 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx @@ -1,34 +1,31 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { screen, userEvent } from '@mui/internal-test-utils'; +import { screen } from '@mui/internal-test-utils'; import { DesktopDateTimePicker } from '@mui/x-date-pickers/DesktopDateTimePicker'; import { adapterToUse, createPickerRenderer, openPicker } from 'test/utils/pickers'; describe('', () => { - const { render } = createPickerRenderer({ - clock: 'fake', - clockConfig: new Date('2018-01-01T10:05:05.000'), - }); + const { render } = createPickerRenderer(); describe('picker state', () => { - it('should open when clicking "Choose date"', () => { + it('should open when clicking "Choose date"', async () => { const onOpen = spy(); - render(); + const { user } = render(); - userEvent.mousePress(screen.getByLabelText(/Choose date/)); + await user.click(screen.getByLabelText(/Choose date/)); expect(onOpen.callCount).to.equal(1); expect(screen.queryByRole('dialog')).toBeVisible(); }); - it('should call onAccept when selecting the same date and time after changing the year', () => { + it('should call onAccept when selecting the same date and time after changing the year', async () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); - render( + const { user } = render( ', () => { />, ); - openPicker({ type: 'date-time', variant: 'desktop' }); + await openPicker({ type: 'date-time', variant: 'desktop', click: user.click }); // Select year - userEvent.mousePress(screen.getByRole('radio', { name: '2025' })); + await user.click(screen.getByRole('radio', { name: '2025' })); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2025, 0, 1, 11, 55)); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); // Change the date (same value) - userEvent.mousePress(screen.getByRole('gridcell', { name: '1' })); + await user.click(screen.getByRole('gridcell', { name: '1' })); expect(onChange.callCount).to.equal(1); // Don't call onChange again since the value did not change // Change the hours (same value) - userEvent.mousePress(screen.getByRole('option', { name: '11 hours' })); + await user.click(screen.getByRole('option', { name: '11 hours' })); expect(onChange.callCount).to.equal(1); // Don't call onChange again since the value did not change // Change the minutes (same value) - userEvent.mousePress(screen.getByRole('option', { name: '55 minutes' })); + await user.click(screen.getByRole('option', { name: '55 minutes' })); expect(onChange.callCount).to.equal(1); // Don't call onChange again since the value did not change // Change the meridiem (same value) - userEvent.mousePress(screen.getByRole('option', { name: 'AM' })); + await user.click(screen.getByRole('option', { name: 'AM' })); expect(onChange.callCount).to.equal(1); // Don't call onChange again since the value did not change expect(onAccept.callCount).to.equal(1); expect(onClose.callCount).to.equal(1); }); }); - it('should allow selecting same view multiple times', () => { + it('should allow selecting same view multiple times', async () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); - render( + const { user } = render( ', () => { />, ); - openPicker({ type: 'date-time', variant: 'desktop' }); + await openPicker({ type: 'date-time', variant: 'desktop', click: user.click }); // Change the date multiple times to check that picker doesn't close after cycling through all views internally - userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); - userEvent.mousePress(screen.getByRole('gridcell', { name: '3' })); - userEvent.mousePress(screen.getByRole('gridcell', { name: '4' })); - userEvent.mousePress(screen.getByRole('gridcell', { name: '5' })); + await user.click(screen.getByRole('gridcell', { name: '2' })); + await user.click(screen.getByRole('gridcell', { name: '3' })); + await user.click(screen.getByRole('gridcell', { name: '4' })); + await user.click(screen.getByRole('gridcell', { name: '5' })); expect(onChange.callCount).to.equal(4); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); // Change the hours - userEvent.mousePress(screen.getByRole('option', { name: '10 hours' })); - userEvent.mousePress(screen.getByRole('option', { name: '9 hours' })); + await user.click(screen.getByRole('option', { name: '10 hours' })); + await user.click(screen.getByRole('option', { name: '9 hours' })); expect(onChange.callCount).to.equal(6); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); // Change the minutes - userEvent.mousePress(screen.getByRole('option', { name: '50 minutes' })); + await user.click(screen.getByRole('option', { name: '50 minutes' })); expect(onChange.callCount).to.equal(7); // Change the meridiem - userEvent.mousePress(screen.getByRole('option', { name: 'PM' })); + await user.click(screen.getByRole('option', { name: 'PM' })); expect(onChange.callCount).to.equal(8); expect(onAccept.callCount).to.equal(1); expect(onClose.callCount).to.equal(1); }); describe('prop: timeSteps', () => { - it('should use "DigitalClock" view renderer, when "timeSteps.minutes" = 60', () => { + it('should use "DigitalClock" view renderer, when "timeSteps.minutes" = 60', async () => { const onChange = spy(); const onAccept = spy(); - render( + const { user } = render( , ); - userEvent.mousePress(screen.getByLabelText(/Choose date/)); + await user.click(screen.getByLabelText(/Choose date/)); - userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); - userEvent.mousePress(screen.getByRole('option', { name: '03:00 AM' })); + await user.click(screen.getByRole('gridcell', { name: '2' })); + await user.click(screen.getByRole('option', { name: '03:00 AM' })); expect(onChange.callCount).to.equal(2); expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 2, 3, 0, 0)); expect(onAccept.callCount).to.equal(1); }); - it('should accept value and close picker when selecting time on "DigitalClock" view renderer', () => { + it('should accept value and close picker when selecting time on "DigitalClock" view renderer', async () => { const onChange = spy(); const onAccept = spy(); - render( + const { user } = render( , ); - userEvent.mousePress(screen.getByLabelText(/Choose date/)); + await user.click(screen.getByLabelText(/Choose date/)); - userEvent.mousePress(screen.getByRole('option', { name: '03:00 AM' })); + await user.click(screen.getByRole('option', { name: '03:00 AM' })); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 1, 3, 0, 0)); diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx index 04ba5bf600064..c59e43a86486e 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx @@ -1,4 +1,4 @@ -import { screen, userEvent } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { createPickerRenderer, adapterToUse, @@ -84,22 +84,20 @@ describe(' - Describes', () => { : adapterToUse.addMinutes(adapterToUse.addHours(adapterToUse.addDays(value, 1), 1), 5); if (isOpened) { - userEvent.mousePress( + fireEvent.click( screen.getByRole('gridcell', { name: adapterToUse.getDate(newValue).toString() }), ); const hasMeridiem = adapterToUse.is12HourCycleInCurrentLocale(); const hours = adapterToUse.format(newValue, hasMeridiem ? 'hours12h' : 'hours24h'); const hoursNumber = adapterToUse.getHours(newValue); - userEvent.mousePress(screen.getByRole('option', { name: `${parseInt(hours, 10)} hours` })); - userEvent.mousePress( + fireEvent.click(screen.getByRole('option', { name: `${parseInt(hours, 10)} hours` })); + fireEvent.click( screen.getByRole('option', { name: `${adapterToUse.getMinutes(newValue)} minutes` }), ); if (hasMeridiem) { // meridiem is an extra view on `DesktopDateTimePicker` // we need to click it to finish selection - userEvent.mousePress( - screen.getByRole('option', { name: hoursNumber >= 12 ? 'PM' : 'AM' }), - ); + fireEvent.click(screen.getByRole('option', { name: hoursNumber >= 12 ? 'PM' : 'AM' })); } } else { selectSection('day'); diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/field.DesktopDateTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/field.DesktopDateTimePicker.test.tsx index 3a951470e5f32..54877db6fa120 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/field.DesktopDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/field.DesktopDateTimePicker.test.tsx @@ -19,35 +19,35 @@ describe(' - Field', () => { }); it('should pass the ampm prop to the field', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true as const, ampm: true, }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY hh:mm aa'); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY hh:mm aa'); - v7Response.setProps({ ampm: false }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY hh:mm'); + view.setProps({ ampm: false }); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY hh:mm'); }); it('should adapt the default field format based on the props of the picker', () => { const testFormat = (props: DesktopDateTimePickerProps, expectedFormat: string) => { // Test with v7 input - const v7Response = renderWithProps( + let view = renderWithProps( { ...props, enableAccessibleFieldDOMStructure: true as const }, { componentFamily: 'picker' }, ); - expectFieldValueV7(v7Response.getSectionsContainer(), expectedFormat); - v7Response.unmount(); + expectFieldValueV7(view.getSectionsContainer(), expectedFormat); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps( + view = renderWithProps( { ...props, enableAccessibleFieldDOMStructure: false as const }, { componentFamily: 'picker' }, ); const input = getTextbox(); expectFieldPlaceholderV6(input, expectedFormat); - v6Response.unmount(); + view.unmount(); }; testFormat({ views: ['day', 'hours', 'minutes'], ampm: false }, 'DD hh:mm'); diff --git a/packages/x-date-pickers/src/DesktopTimePicker/tests/DesktopTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopTimePicker/tests/DesktopTimePicker.test.tsx index b520cad777277..28663c2a75572 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/tests/DesktopTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/tests/DesktopTimePicker.test.tsx @@ -1,17 +1,17 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { screen, userEvent } from '@mui/internal-test-utils'; +import { screen } from '@mui/internal-test-utils'; import { DesktopTimePicker } from '@mui/x-date-pickers/DesktopTimePicker'; -import { createPickerRenderer, openPicker } from 'test/utils/pickers'; +import { adapterToUse, createPickerRenderer, openPicker } from 'test/utils/pickers'; describe('', () => { - const { render } = createPickerRenderer({ - clock: 'fake', - clockConfig: new Date('2018-01-01T10:05:05.000'), - }); - describe('rendering behavior', () => { + const { render } = createPickerRenderer({ + clock: 'fake', + clockConfig: new Date('2018-01-01T10:05:05.000'), + }); + it('should render "accept" action and 3 time sections by default', () => { render(); @@ -60,23 +60,26 @@ describe('', () => { }); describe('selecting behavior', () => { - it('should call "onAccept", "onChange", and "onClose" when selecting a single option', () => { + const { render } = createPickerRenderer(); + + it('should call "onAccept", "onChange", and "onClose" when selecting a single option', async () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); - render( + const { user } = render( , ); - openPicker({ type: 'time', variant: 'desktop' }); + await openPicker({ type: 'time', variant: 'desktop', click: user.click }); - userEvent.mousePress(screen.getByRole('option', { name: '09:00 AM' })); + await user.click(screen.getByRole('option', { name: '09:00 AM' })); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 1, 9, 0)); expect(onAccept.callCount).to.equal(1); @@ -84,73 +87,94 @@ describe('', () => { expect(onClose.callCount).to.equal(1); }); - it('should call "onAccept", "onChange", and "onClose" when selecting all section', () => { + it('should call "onAccept", "onChange", and "onClose" when selecting all section', async () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); - render(); + const { user } = render( + , + ); - openPicker({ type: 'time', variant: 'desktop' }); + await openPicker({ type: 'time', variant: 'desktop', click: user.click }); - userEvent.mousePress(screen.getByRole('option', { name: '2 hours' })); + await user.click(screen.getByRole('option', { name: '2 hours' })); expect(onChange.callCount).to.equal(1); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); - userEvent.mousePress(screen.getByRole('option', { name: '15 minutes' })); + await user.click(screen.getByRole('option', { name: '15 minutes' })); expect(onChange.callCount).to.equal(2); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); - userEvent.mousePress(screen.getByRole('option', { name: 'PM' })); + await user.click(screen.getByRole('option', { name: 'PM' })); expect(onChange.callCount).to.equal(3); expect(onAccept.callCount).to.equal(1); expect(onAccept.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 1, 14, 15)); expect(onClose.callCount).to.equal(1); }); - it('should allow out of order section selection', () => { + it('should allow out of order section selection', async () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); - render(); + const { user } = render( + , + ); - openPicker({ type: 'time', variant: 'desktop' }); + await openPicker({ type: 'time', variant: 'desktop', click: user.click }); - userEvent.mousePress(screen.getByRole('option', { name: '15 minutes' })); + await user.click(screen.getByRole('option', { name: '15 minutes' })); expect(onChange.callCount).to.equal(1); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); - userEvent.mousePress(screen.getByRole('option', { name: '2 hours' })); + await user.click(screen.getByRole('option', { name: '2 hours' })); expect(onChange.callCount).to.equal(2); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); - userEvent.mousePress(screen.getByRole('option', { name: '25 minutes' })); + await user.click(screen.getByRole('option', { name: '25 minutes' })); expect(onChange.callCount).to.equal(3); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); - userEvent.mousePress(screen.getByRole('option', { name: 'PM' })); + await user.click(screen.getByRole('option', { name: 'PM' })); expect(onChange.callCount).to.equal(4); expect(onAccept.callCount).to.equal(1); expect(onAccept.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 1, 14, 25)); expect(onClose.callCount).to.equal(1); }); - it('should finish selection when selecting only the last section', () => { + it('should finish selection when selecting only the last section', async () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); - render(); + const { user } = render( + , + ); - openPicker({ type: 'time', variant: 'desktop' }); + await openPicker({ type: 'time', variant: 'desktop', click: user.click }); - userEvent.mousePress(screen.getByRole('option', { name: 'PM' })); + await user.click(screen.getByRole('option', { name: 'PM' })); expect(onChange.callCount).to.equal(1); expect(onAccept.callCount).to.equal(1); expect(onAccept.lastCall.args[0]).toEqualDateTime(new Date(2018, 0, 1, 12, 0)); 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 3b6e6eb288681..7d4652b6c4df5 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 @@ -1,5 +1,5 @@ import * as React from 'react'; -import { screen, userEvent } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { createPickerRenderer, adapterToUse, @@ -76,16 +76,14 @@ describe(' - Describes', () => { const hasMeridiem = adapterToUse.is12HourCycleInCurrentLocale(); const hours = adapterToUse.format(newValue, hasMeridiem ? 'hours12h' : 'hours24h'); const hoursNumber = adapterToUse.getHours(newValue); - userEvent.mousePress(screen.getByRole('option', { name: `${parseInt(hours, 10)} hours` })); - userEvent.mousePress( + fireEvent.click(screen.getByRole('option', { name: `${parseInt(hours, 10)} hours` })); + fireEvent.click( screen.getByRole('option', { name: `${adapterToUse.getMinutes(newValue)} minutes` }), ); if (hasMeridiem) { // meridiem is an extra view on `DesktopTimePicker` // we need to click it to finish selection - userEvent.mousePress( - screen.getByRole('option', { name: hoursNumber >= 12 ? 'PM' : 'AM' }), - ); + fireEvent.click(screen.getByRole('option', { name: hoursNumber >= 12 ? 'PM' : 'AM' })); } } else { selectSection('hours'); diff --git a/packages/x-date-pickers/src/DesktopTimePicker/tests/field.DesktopTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopTimePicker/tests/field.DesktopTimePicker.test.tsx index 10d1848a5fe3c..88c95f9815039 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/tests/field.DesktopTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/tests/field.DesktopTimePicker.test.tsx @@ -16,35 +16,35 @@ describe(' - Field', () => { }); it('should pass the ampm prop to the field', () => { - const v7Response = renderWithProps( + const view = renderWithProps( { enableAccessibleFieldDOMStructure: true as const, ampm: true }, { componentFamily: 'picker' }, ); - expectFieldValueV7(v7Response.getSectionsContainer(), 'hh:mm aa'); + expectFieldValueV7(view.getSectionsContainer(), 'hh:mm aa'); - v7Response.setProps({ ampm: false }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'hh:mm'); + view.setProps({ ampm: false }); + expectFieldValueV7(view.getSectionsContainer(), 'hh:mm'); }); it('should adapt the default field format based on the props of the picker', () => { const testFormat = (props: DesktopTimePickerProps, expectedFormat: string) => { // Test with v7 input - const v7Response = renderWithProps( + let view = renderWithProps( { ...props, enableAccessibleFieldDOMStructure: true as const }, { componentFamily: 'picker' }, ); - expectFieldValueV7(v7Response.getSectionsContainer(), expectedFormat); - v7Response.unmount(); + expectFieldValueV7(view.getSectionsContainer(), expectedFormat); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps( + view = renderWithProps( { ...props, enableAccessibleFieldDOMStructure: false as const }, { componentFamily: 'picker' }, ); const input = getTextbox(); expectFieldPlaceholderV6(input, expectedFormat); - v6Response.unmount(); + view.unmount(); }; testFormat({ views: ['hours'], ampm: false }, 'hh'); diff --git a/packages/x-date-pickers/src/DigitalClock/tests/DigitalClock.test.tsx b/packages/x-date-pickers/src/DigitalClock/tests/DigitalClock.test.tsx index 4f6617cc90c67..96fb9bd96fa67 100644 --- a/packages/x-date-pickers/src/DigitalClock/tests/DigitalClock.test.tsx +++ b/packages/x-date-pickers/src/DigitalClock/tests/DigitalClock.test.tsx @@ -93,16 +93,15 @@ describe('', () => { }); it('forwards list class to MenuList', () => { - const { getByRole } = render(); + render(); - const list = getByRole('listbox'); - expect(list).to.have.class('foo'); + expect(screen.getByRole('listbox')).to.have.class('foo'); }); it('forwards item class to clock item', () => { - const { getAllByRole } = render(); + render(); - const options = getAllByRole('option'); + const options = screen.getAllByRole('option'); expect(options[0]).to.have.class('bar'); }); }); diff --git a/packages/x-date-pickers/src/DigitalClock/tests/timezone.DigitalClock.test.tsx b/packages/x-date-pickers/src/DigitalClock/tests/timezone.DigitalClock.test.tsx index 9dc25d08aef52..5d7a0aa650d86 100644 --- a/packages/x-date-pickers/src/DigitalClock/tests/timezone.DigitalClock.test.tsx +++ b/packages/x-date-pickers/src/DigitalClock/tests/timezone.DigitalClock.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { spy } from 'sinon'; import { expect } from 'chai'; -import { screen, userEvent } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { DigitalClock } from '@mui/x-date-pickers/DigitalClock'; import { getDateOffset, describeAdapters } from 'test/utils/pickers'; @@ -25,7 +25,7 @@ describe(' - Timezone', () => { const onChange = spy(); render(); - userEvent.mousePress(screen.getByRole('option', { name: '08:00 AM' })); + fireEvent.click(screen.getByRole('option', { name: '08:00 AM' })); const expectedDate = adapter.setHours(adapter.date(), 8); @@ -44,7 +44,7 @@ describe(' - Timezone', () => { const onChange = spy(); render(); - userEvent.mousePress(screen.getByRole('option', { name: '08:00 AM' })); + fireEvent.click(screen.getByRole('option', { name: '08:00 AM' })); const expectedDate = adapter.setHours( adapter.startOfDay(adapter.date(undefined, timezone)), @@ -75,7 +75,7 @@ describe(' - Timezone', () => { (adapter.getHours(value) + offsetDiff / 60 + 24) % 24, ); - userEvent.mousePress(screen.getByRole('option', { name: '08:30 PM' })); + fireEvent.click(screen.getByRole('option', { name: '08:30 PM' })); const actualDate = onChange.lastCall.firstArg; diff --git a/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx b/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx index 1095449d3312e..8491613b26228 100644 --- a/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { fireEvent, screen, userEvent } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { PickersDay } from '@mui/x-date-pickers/PickersDay'; import { DayCalendarSkeleton } from '@mui/x-date-pickers/DayCalendarSkeleton'; import { MobileDatePicker } from '@mui/x-date-pickers/MobileDatePicker'; @@ -155,7 +155,7 @@ describe('', () => { render(); - userEvent.mousePress(getFieldSectionsContainer()); + fireEvent.click(getFieldSectionsContainer()); expect(onOpen.callCount).to.equal(1); expect(screen.queryByRole('dialog')).toBeVisible(); @@ -188,17 +188,17 @@ describe('', () => { }); it('should update internal state when controlled value is updated', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true as const, value: adapterToUse.date('2019-01-01'), }); // Set a date - expectFieldValueV7(v7Response.getSectionsContainer(), '01/01/2019'); + expectFieldValueV7(view.getSectionsContainer(), '01/01/2019'); // Clean value using external control - v7Response.setProps({ value: null }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY'); + view.setProps({ value: null }); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); // Open and Dismiss the picker openPicker({ type: 'date', variant: 'mobile' }); @@ -207,7 +207,7 @@ describe('', () => { clock.runToLast(); // Verify it's still a clean value - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY'); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); }); }); }); diff --git a/packages/x-date-pickers/src/MobileDatePicker/tests/describes.MobileDatePicker.test.tsx b/packages/x-date-pickers/src/MobileDatePicker/tests/describes.MobileDatePicker.test.tsx index 5dfe64dc6e899..431e6682e182d 100644 --- a/packages/x-date-pickers/src/MobileDatePicker/tests/describes.MobileDatePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDatePicker/tests/describes.MobileDatePicker.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { screen, userEvent, fireEvent } from '@mui/internal-test-utils'; +import { screen, fireEvent } from '@mui/internal-test-utils'; import { createPickerRenderer, adapterToUse, @@ -64,7 +64,7 @@ describe(' - Describes', () => { } const newValue = applySameValue ? value : adapterToUse.addDays(value, 1); - userEvent.mousePress( + fireEvent.click( screen.getByRole('gridcell', { name: adapterToUse.getDate(newValue).toString() }), ); diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx b/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx index ad9c3641efbbd..1e21c3dbc9355 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { fireTouchChangedEvent, screen, userEvent } from '@mui/internal-test-utils'; +import { fireEvent, fireTouchChangedEvent, screen } from '@mui/internal-test-utils'; import { MobileDateTimePicker } from '@mui/x-date-pickers/MobileDateTimePicker'; import { adapterToUse, @@ -98,7 +98,7 @@ describe('', () => { render(); - userEvent.mousePress(getFieldSectionsContainer()); + fireEvent.click(getFieldSectionsContainer()); expect(onOpen.callCount).to.equal(1); expect(screen.queryByRole('dialog')).toBeVisible(); @@ -131,8 +131,8 @@ describe('', () => { expect(onClose.callCount).to.equal(0); // Change the year view - userEvent.mousePress(screen.getByLabelText(/switch to year view/)); - userEvent.mousePress(screen.getByText('2010', { selector: 'button' })); + fireEvent.click(screen.getByLabelText(/switch to year view/)); + fireEvent.click(screen.getByText('2010', { selector: 'button' })); expect(onChange.callCount).to.equal(1); expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2010, 0, 1)); @@ -140,7 +140,7 @@ describe('', () => { clock.runToLast(); // Change the date - userEvent.mousePress(screen.getByRole('gridcell', { name: '15' })); + fireEvent.click(screen.getByRole('gridcell', { name: '15' })); expect(onChange.callCount).to.equal(2); expect(onChange.lastCall.args[0]).toEqualDateTime(new Date(2010, 0, 15)); diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/tests/describes.MobileDateTimePicker.test.tsx b/packages/x-date-pickers/src/MobileDateTimePicker/tests/describes.MobileDateTimePicker.test.tsx index 4d1d819ad7734..49ebb8d558371 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/tests/describes.MobileDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/tests/describes.MobileDateTimePicker.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { screen, userEvent, fireEvent, fireTouchChangedEvent } from '@mui/internal-test-utils'; +import { screen, fireEvent, fireTouchChangedEvent } from '@mui/internal-test-utils'; import { createPickerRenderer, adapterToUse, @@ -78,7 +78,7 @@ describe(' - Describes', () => { const newValue = applySameValue ? value : adapterToUse.addMinutes(adapterToUse.addHours(adapterToUse.addDays(value, 1), 1), 5); - userEvent.mousePress( + fireEvent.click( screen.getByRole('gridcell', { name: adapterToUse.getDate(newValue).toString() }), ); const hasMeridiem = adapterToUse.is12HourCycleInCurrentLocale(); @@ -97,7 +97,7 @@ describe(' - Describes', () => { if (hasMeridiem) { const newHours = adapterToUse.getHours(newValue); // select appropriate meridiem - userEvent.mousePress(screen.getByRole('button', { name: newHours >= 12 ? 'PM' : 'AM' })); + fireEvent.click(screen.getByRole('button', { name: newHours >= 12 ? 'PM' : 'AM' })); } // Close the picker @@ -107,7 +107,7 @@ describe(' - Describes', () => { clock.runToLast(); } else { // return to the date view in case we'd like to repeat the selection process - userEvent.mousePress(screen.getByRole('tab', { name: 'pick date' })); + fireEvent.click(screen.getByRole('tab', { name: 'pick date' })); } return newValue; diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/tests/field.MobileDateTimePicker.test.tsx b/packages/x-date-pickers/src/MobileDateTimePicker/tests/field.MobileDateTimePicker.test.tsx index 2a8aae0ea83ce..413a025f20021 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/tests/field.MobileDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/tests/field.MobileDateTimePicker.test.tsx @@ -14,14 +14,14 @@ describe(' - Field', () => { }); it('should pass the ampm prop to the field', () => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true as const, ampm: true, }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY hh:mm aa'); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY hh:mm aa'); - v7Response.setProps({ ampm: false }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'MM/DD/YYYY hh:mm'); + view.setProps({ ampm: false }); + expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY hh:mm'); }); }); diff --git a/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx b/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx index 5bf9dade08f81..62d073d3c10ca 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { spy } from 'sinon'; import { expect } from 'chai'; -import { fireTouchChangedEvent, screen, userEvent, act } from '@mui/internal-test-utils'; +import { fireTouchChangedEvent, screen } from '@mui/internal-test-utils'; import { MobileTimePicker } from '@mui/x-date-pickers/MobileTimePicker'; import { createPickerRenderer, @@ -12,23 +12,25 @@ import { } from 'test/utils/pickers'; describe('', () => { - const { render } = createPickerRenderer({ clock: 'fake' }); + const { render } = createPickerRenderer(); describe('picker state', () => { - it('should open when clicking the input', () => { + it('should open when clicking the input', async () => { const onOpen = spy(); - render(); + const { user } = render( + , + ); - userEvent.mousePress(getFieldSectionsContainer()); + await user.click(getFieldSectionsContainer()); expect(onOpen.callCount).to.equal(1); expect(screen.queryByRole('dialog')).toBeVisible(); }); - it('should fire a change event when meridiem changes', () => { + it('should fire a change event when meridiem changes', async () => { const handleChange = spy(); - render( + const { user } = render( ', () => { ); const buttonPM = screen.getByRole('button', { name: 'PM' }); - act(() => { - buttonPM.click(); - }); + await user.click(buttonPM); expect(handleChange.callCount).to.equal(1); expect(handleChange.firstCall.args[0]).toEqualDateTime(new Date(2019, 0, 1, 16, 20)); }); - it('should call onChange when selecting each view', function test() { + it('should call onChange when selecting each view', async function test() { if (typeof window.Touch === 'undefined' || typeof window.TouchEvent === 'undefined') { this.skip(); } @@ -58,7 +58,7 @@ describe('', () => { const onClose = spy(); const defaultValue = adapterToUse.date('2018-01-01'); - render( + const { user } = render( ', () => { />, ); - openPicker({ type: 'time', variant: 'mobile' }); + await openPicker({ type: 'time', variant: 'mobile', click: user.click }); // Change the hours const hourClockEvent = getClockTouchEvent(11, '12hours'); 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 79b4c2029072e..b3fa12461d7b5 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 @@ -1,5 +1,5 @@ import * as React from 'react'; -import { screen, fireEvent, userEvent, fireTouchChangedEvent } from '@mui/internal-test-utils'; +import { screen, fireEvent, fireTouchChangedEvent } from '@mui/internal-test-utils'; import { createPickerRenderer, adapterToUse, @@ -92,7 +92,7 @@ describe(' - Describes', () => { if (hasMeridiem) { const newHours = adapterToUse.getHours(newValue); // select appropriate meridiem - userEvent.mousePress(screen.getByRole('button', { name: newHours >= 12 ? 'PM' : 'AM' })); + fireEvent.click(screen.getByRole('button', { name: newHours >= 12 ? 'PM' : 'AM' })); } // Close the picker @@ -102,7 +102,7 @@ describe(' - Describes', () => { clock.runToLast(); } else { // return to the hours view in case we'd like to repeat the selection process - userEvent.mousePress(screen.getByRole('button', { name: 'Open previous view' })); + fireEvent.click(screen.getByRole('button', { name: 'Open previous view' })); } return newValue; diff --git a/packages/x-date-pickers/src/MobileTimePicker/tests/field.MobileTimePicker.test.tsx b/packages/x-date-pickers/src/MobileTimePicker/tests/field.MobileTimePicker.test.tsx index 206189acc74a2..befd200997643 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/tests/field.MobileTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/tests/field.MobileTimePicker.test.tsx @@ -14,14 +14,14 @@ describe(' - Field', () => { }); it('should pass the ampm prop to the field', () => { - const v7Response = renderWithProps( + const view = renderWithProps( { enableAccessibleFieldDOMStructure: true as const, ampm: true }, { componentFamily: 'picker' }, ); - expectFieldValueV7(v7Response.getSectionsContainer(), 'hh:mm aa'); + expectFieldValueV7(view.getSectionsContainer(), 'hh:mm aa'); - v7Response.setProps({ ampm: false }); - expectFieldValueV7(v7Response.getSectionsContainer(), 'hh:mm'); + view.setProps({ ampm: false }); + expectFieldValueV7(view.getSectionsContainer(), 'hh:mm'); }); }); 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 f439b851f2c34..2f289f70a5524 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 @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { userEvent, screen } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { createPickerRenderer, adapterToUse, @@ -54,9 +54,7 @@ describe(' - Describes', () => { setNewValue: (value) => { const newValue = adapterToUse.addMonths(value, 1); - userEvent.mousePress( - screen.getByRole('radio', { name: adapterToUse.format(newValue, 'month') }), - ); + fireEvent.click(screen.getByRole('radio', { name: adapterToUse.format(newValue, 'month') })); return newValue; }, diff --git a/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx b/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx index c4837e507ce74..58d3b35893ac1 100644 --- a/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx +++ b/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx @@ -1,14 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { screen, userEvent } from '@mui/internal-test-utils'; +import { screen } from '@mui/internal-test-utils'; import { PickersActionBar } from '@mui/x-date-pickers/PickersActionBar'; import { createPickerRenderer } from 'test/utils/pickers'; describe('', () => { - const { render } = createPickerRenderer({ - clock: 'fake', - }); + const { render } = createPickerRenderer(); it('should not render buttons if actions array is empty', () => { const onAccept = () => {}; @@ -28,13 +26,13 @@ describe('', () => { expect(screen.queryByRole('button')).to.equal(null); }); - it('should render button for "clear" action calling the associated callback', () => { + it('should render button for "clear" action calling the associated callback', async () => { const onAccept = spy(); const onClear = spy(); const onCancel = spy(); const onSetToday = spy(); - render( + const { user } = render( ', () => { />, ); - userEvent.mousePress(screen.getByText(/clear/i)); + await user.click(screen.getByText(/clear/i)); expect(onClear.callCount).to.equal(1); }); - it('should render button for "cancel" action calling the associated callback', () => { + it('should render button for "cancel" action calling the associated callback', async () => { const onAccept = spy(); const onClear = spy(); const onCancel = spy(); const onSetToday = spy(); - render( + const { user } = render( ', () => { />, ); - userEvent.mousePress(screen.getByText(/cancel/i)); + await user.click(screen.getByText(/cancel/i)); expect(onCancel.callCount).to.equal(1); }); - it('should render button for "accept" action calling the associated callback', () => { + it('should render button for "accept" action calling the associated callback', async () => { const onAccept = spy(); const onClear = spy(); const onCancel = spy(); const onSetToday = spy(); - render( + const { user } = render( ', () => { />, ); - userEvent.mousePress(screen.getByText(/ok/i)); + await user.click(screen.getByText(/ok/i)); expect(onAccept.callCount).to.equal(1); }); - it('should render button for "today" action calling the associated callback', () => { + it('should render button for "today" action calling the associated callback', async () => { const onAccept = spy(); const onClear = spy(); const onCancel = spy(); const onSetToday = spy(); - render( + const { user } = render( ', () => { />, ); - userEvent.mousePress(screen.getByText(/today/i)); + await user.click(screen.getByText(/today/i)); expect(onSetToday.callCount).to.equal(1); }); diff --git a/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx b/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx index 0303989d847cf..1ba9e2b3f1d1d 100644 --- a/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx +++ b/packages/x-date-pickers/src/PickersTextField/PickersTextField.tsx @@ -139,6 +139,7 @@ const PickersTextField = React.forwardRef(function PickersTextField( areAllSectionsEmpty={areAllSectionsEmpty} onClick={onClick} onKeyDown={onKeyDown} + onKeyUp={onKeyUp} onInput={onInput} onPaste={onPaste} endAdornment={endAdornment} diff --git a/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx b/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx index 8e2e37371b5db..f5a04623b4af4 100644 --- a/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx +++ b/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx @@ -1,7 +1,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 { fireTouchChangedEvent, screen, within, fireEvent } from '@mui/internal-test-utils'; import { adapterToUse, createPickerRenderer, describeValidation } from 'test/utils/pickers'; import { StaticTimePicker } from '@mui/x-date-pickers/StaticTimePicker'; import { describeConformance } from 'test/utils/describeConformance'; @@ -78,7 +78,7 @@ describe('', () => { // hours are not disabled const hoursContainer = screen.getByRole('listbox'); - const hours = getAllByRole(hoursContainer, 'option'); + const hours = within(hoursContainer).getAllByRole('option'); const disabledHours = hours.filter((day) => day.getAttribute('aria-disabled') === 'true'); expect(hours.length).to.equal(12); @@ -128,7 +128,7 @@ describe('', () => { // hours are disabled const hoursContainer = screen.getByRole('listbox'); - const hours = getAllByRole(hoursContainer, 'option'); + const hours = within(hoursContainer).getAllByRole('option'); const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); expect(hours.length).to.equal(12); expect(disabledHours.length).to.equal(12); diff --git a/packages/x-date-pickers/src/TimeClock/Clock.tsx b/packages/x-date-pickers/src/TimeClock/Clock.tsx index f47484d497ecf..fc69d7d028fe9 100644 --- a/packages/x-date-pickers/src/TimeClock/Clock.tsx +++ b/packages/x-date-pickers/src/TimeClock/Clock.tsx @@ -261,7 +261,7 @@ export function Clock(inProps: ClockProps) handleValueChange(newSelectedValue, isFinish); }; - const handleTouchMove = (event: React.TouchEvent) => { + const handleTouchSelection = (event: React.TouchEvent) => { isMoving.current = true; setTime(event, 'shallow'); }; @@ -332,6 +332,11 @@ export function Clock(inProps: ClockProps) handleValueChange(viewValue - keyboardControlStep, 'partial'); event.preventDefault(); break; + case 'Enter': + case ' ': + handleValueChange(viewValue, 'finish'); + event.preventDefault(); + break; default: // do nothing } @@ -342,7 +347,8 @@ export function Clock(inProps: ClockProps) ', () => { expect(reason).to.equal('partial'); }); + [ + { + keyName: 'Enter', + keyValue: 'Enter', + }, + { + keyName: 'Space', + keyValue: ' ', + }, + ].forEach(({ keyName, keyValue }) => { + it(`sets value on ${keyName} press`, () => { + const handleChange = spy(); + render( + , + ); + const listbox = screen.getByRole('listbox'); + + fireEvent.keyDown(listbox, { key: 'ArrowDown' }); + fireEvent.keyDown(listbox, { key: keyValue }); + + expect(handleChange.callCount).to.equal(2); + let [newDate, reason] = handleChange.lastCall.args; + + expect(adapterToUse.getHours(newDate)).to.equal(3); + expect(reason).to.equal('partial'); + + fireEvent.keyDown(listbox, { key: 'ArrowUp' }); + fireEvent.keyDown(listbox, { key: keyValue }); + + expect(handleChange.callCount).to.equal(4); + [newDate, reason] = handleChange.lastCall.args; + + expect(adapterToUse.getMinutes(newDate)).to.equal(21); + expect(reason).to.equal('finish'); + }); + }); + it('should display options, but not update value when readOnly prop is passed', function test() { // Only run in supported browsers if (typeof Touch === 'undefined') { @@ -161,12 +196,12 @@ describe('', () => { const onChangeMock = spy(); render(); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', selectEvent); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchstart', selectEvent); expect(onChangeMock.callCount).to.equal(0); // hours are not disabled const hoursContainer = screen.getByRole('listbox'); - const hours = getAllByRole(hoursContainer, 'option'); + const hours = within(hoursContainer).getAllByRole('option'); const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); expect(hours.length).to.equal(12); @@ -189,12 +224,12 @@ describe('', () => { const onChangeMock = spy(); render(); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', selectEvent); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchstart', selectEvent); expect(onChangeMock.callCount).to.equal(0); // hours are disabled const hoursContainer = screen.getByRole('listbox'); - const hours = getAllByRole(hoursContainer, 'option'); + const hours = within(hoursContainer).getAllByRole('option'); const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); expect(hours.length).to.equal(12); @@ -217,7 +252,7 @@ describe('', () => { }, ], }, - '20:--': { + '19:--': { changedTouches: [ { clientX: 66, @@ -257,7 +292,7 @@ describe('', () => { />, ); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', clockTouchEvent['13:--']); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchstart', clockTouchEvent['13:--']); expect(handleChange.callCount).to.equal(1); const [date, selectionState] = handleChange.firstCall.args; @@ -281,7 +316,7 @@ describe('', () => { />, ); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', clockTouchEvent['--:20']); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchstart', clockTouchEvent['--:20']); expect(handleChange.callCount).to.equal(1); const [date, selectionState] = handleChange.firstCall.args; @@ -303,7 +338,7 @@ describe('', () => { />, ); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', clockTouchEvent['--:20']); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchstart', clockTouchEvent['--:20']); expect(handleChange.callCount).to.equal(0); }); @@ -321,7 +356,7 @@ describe('', () => { />, ); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', clockTouchEvent['--:20']); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchstart', clockTouchEvent['--:20']); expect(handleChange.callCount).to.equal(0); }); @@ -339,7 +374,7 @@ describe('', () => { />, ); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', clockTouchEvent['20:--']); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchstart', clockTouchEvent['19:--']); expect(handleChange.callCount).to.equal(0); }); @@ -357,7 +392,7 @@ describe('', () => { />, ); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', clockTouchEvent['20:--']); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchstart', clockTouchEvent['19:--']); expect(handleChange.callCount).to.equal(0); }); @@ -392,7 +427,7 @@ describe('', () => { />, ); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', clockTouchEvent['--:10']); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchstart', clockTouchEvent['--:10']); expect(handleChange.callCount).to.equal(1); const [date, selectionState] = handleChange.firstCall.args; @@ -414,7 +449,7 @@ describe('', () => { />, ); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', clockTouchEvent['--:20']); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchstart', clockTouchEvent['--:20']); expect(handleChange.callCount).to.equal(0); }); @@ -432,10 +467,54 @@ describe('', () => { />, ); - fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', clockTouchEvent['--:20']); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchstart', clockTouchEvent['--:20']); expect(handleChange.callCount).to.equal(0); }); + + it('should select enabled hour on touch and drag', () => { + const handleChange = spy(); + const handleViewChange = spy(); + render( + , + ); + + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchstart', clockTouchEvent['13:--']); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchmove', clockTouchEvent['19:--']); + + expect(handleChange.callCount).to.equal(2); + const [date, selectionState] = handleChange.lastCall.args; + expect(date).toEqualDateTime(new Date(2018, 0, 1, 19)); + expect(selectionState).to.equal('shallow'); + expect(handleViewChange.callCount).to.equal(0); + }); + + it('should select enabled hour and move to next view on touch end', () => { + const handleChange = spy(); + const handleViewChange = spy(); + render( + , + ); + + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchstart', clockTouchEvent['13:--']); + fireTouchChangedEvent(screen.getByMuiTest('clock'), 'touchend', clockTouchEvent['13:--']); + + expect(handleChange.callCount).to.equal(2); + const [date, selectionState] = handleChange.lastCall.args; + expect(date).toEqualDateTime(new Date(2018, 0, 1, 13)); + expect(selectionState).to.equal('partial'); + expect(handleViewChange.callCount).to.equal(1); + }); }); describe('default value', () => { diff --git a/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx b/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx index d9736f41ad511..9ddc968fd0609 100644 --- a/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx +++ b/packages/x-date-pickers/src/TimeField/tests/editing.TimeField.test.tsx @@ -497,27 +497,27 @@ describe(' - Editing', () => { it('should go to the next section when pressing `2` in a 12-hours format', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: adapter.formats.fullTime12h, }); - v7Response.selectSection('hours'); + view.selectSection('hours'); - v7Response.pressKey(0, '2'); - expectFieldValueV7(v7Response.getSectionsContainer(), '02:mm aa'); + view.pressKey(0, '2'); + expectFieldValueV7(view.getSectionsContainer(), '02:mm aa'); expect(getCleanedSelectedContent()).to.equal('mm'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: adapter.formats.fullTime12h, }); const input = getTextbox(); - v6Response.selectSection('hours'); + view.selectSection('hours'); // Press "2" fireEvent.change(input, { target: { value: '2:mm aa' } }); @@ -527,32 +527,32 @@ describe(' - Editing', () => { it('should go to the next section when pressing `1` then `3` in a 12-hours format', () => { // Test with v7 input - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: adapter.formats.fullTime12h, }); - v7Response.selectSection('hours'); + view.selectSection('hours'); - v7Response.pressKey(0, '1'); - expectFieldValueV7(v7Response.getSectionsContainer(), '01:mm aa'); + view.pressKey(0, '1'); + expectFieldValueV7(view.getSectionsContainer(), '01:mm aa'); expect(getCleanedSelectedContent()).to.equal('01'); // Press "3" - v7Response.pressKey(0, '3'); - expectFieldValueV7(v7Response.getSectionsContainer(), '03:mm aa'); + view.pressKey(0, '3'); + expectFieldValueV7(view.getSectionsContainer(), '03:mm aa'); expect(getCleanedSelectedContent()).to.equal('mm'); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: adapter.formats.fullTime12h, }); const input = getTextbox(); - v6Response.selectSection('hours'); + view.selectSection('hours'); // Press "1" fireEvent.change(input, { target: { value: '1:mm aa' } }); @@ -638,30 +638,30 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV7, }); - v7Response.selectSection('hours'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowDown' }); + view.selectSection('hours'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowDown' }); expect(onChangeV7.lastCall.firstArg).toEqualDateTime(new Date(2010, 3, 3, 2, 3, 3)); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV6, }); const input = getTextbox(); - v6Response.selectSection('hours'); + view.selectSection('hours'); fireEvent.keyDown(input, { key: 'ArrowDown' }); expect(onChangeV6.lastCall.firstArg).toEqualDateTime(new Date(2010, 3, 3, 2, 3, 3)); @@ -671,31 +671,31 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV7, format: adapter.formats.fullTime24h, }); - v7Response.selectSection('hours'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'a', ctrlKey: true }); - v7Response.pressKey(null, ''); - fireEvent.keyDown(v7Response.getSectionsContainer(), { key: 'ArrowLeft' }); + view.selectSection('hours'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'a', ctrlKey: true }); + view.pressKey(null, ''); + fireEvent.keyDown(view.getSectionsContainer(), { key: 'ArrowLeft' }); - v7Response.pressKey(0, '3'); - expectFieldValueV7(v7Response.getSectionsContainer(), '03:mm'); + view.pressKey(0, '3'); + expectFieldValueV7(view.getSectionsContainer(), '03:mm'); - v7Response.pressKey(1, '4'); - expectFieldValueV7(v7Response.getSectionsContainer(), '03:04'); + view.pressKey(1, '4'); + expectFieldValueV7(view.getSectionsContainer(), '03:04'); expect(onChangeV7.lastCall.firstArg).toEqualDateTime(new Date(2010, 3, 3, 3, 4, 3)); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV6, @@ -703,7 +703,7 @@ describe(' - Editing', () => { }); const input = getTextbox(); - v6Response.selectSection('hours'); + view.selectSection('hours'); fireEvent.keyDown(input, { key: 'a', ctrlKey: true }); fireEvent.change(input, { target: { value: '' } }); fireEvent.keyDown(input, { key: 'ArrowLeft' }); @@ -720,24 +720,24 @@ describe(' - Editing', () => { // Test with v7 input const onChangeV7 = spy(); - const v7Response = renderWithProps({ + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV7, format: adapter.formats.hours24h, }); - v7Response.selectSection('hours'); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowDown' }); + view.selectSection('hours'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowDown' }); expect(onChangeV7.lastCall.firstArg).toEqualDateTime(new Date(2010, 3, 3, 2, 3, 3)); - v7Response.unmount(); + view.unmount(); // Test with v6 input const onChangeV6 = spy(); - const v6Response = renderWithProps({ + view = renderWithProps({ enableAccessibleFieldDOMStructure: false, defaultValue: adapter.date('2010-04-03T03:03:03'), onChange: onChangeV6, @@ -745,7 +745,7 @@ describe(' - Editing', () => { }); const input = getTextbox(); - v6Response.selectSection('hours'); + view.selectSection('hours'); fireEvent.keyDown(input, { key: 'ArrowDown' }); expect(onChangeV6.lastCall.firstArg).toEqualDateTime(new Date(2010, 3, 3, 2, 3, 3)); 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 e2b1c722d71a5..06c4de6035f45 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 @@ -1,6 +1,6 @@ import * as React from 'react'; import { expect } from 'chai'; -import { userEvent, screen } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { YearCalendar, yearCalendarClasses as classes } from '@mui/x-date-pickers/YearCalendar'; import { createPickerRenderer, @@ -51,7 +51,7 @@ describe(' - Describes', () => { }, setNewValue: (value) => { const newValue = adapterToUse.addYears(value, 1); - userEvent.mousePress( + fireEvent.click( screen.getByRole('radio', { name: adapterToUse.getYear(newValue).toString() }), ); diff --git a/packages/x-date-pickers/src/internals/utils/date-time-utils.ts b/packages/x-date-pickers/src/internals/utils/date-time-utils.ts index 47688559cf1b2..eb7c100e9468c 100644 --- a/packages/x-date-pickers/src/internals/utils/date-time-utils.ts +++ b/packages/x-date-pickers/src/internals/utils/date-time-utils.ts @@ -7,7 +7,7 @@ import { TimeView, } from '../../models'; import { resolveTimeFormat, isTimeView, isInternalTimeView } from './time-utils'; -import { resolveDateFormat } from './date-utils'; +import { isDatePickerView, resolveDateFormat } from './date-utils'; import { DateOrTimeViewWithMeridiem } from '../models'; import { DesktopOnlyTimePickerProps } from '../models/props/clock'; import { DefaultizedProps } from '../models/helpers'; @@ -18,7 +18,12 @@ export const resolveDateTimeFormat = ( views, format, ...other - }: { format?: string; views: readonly DateOrTimeViewWithMeridiem[]; ampm: boolean }, + }: { + format?: string; + views: readonly DateOrTimeViewWithMeridiem[]; + ampm: boolean; + }, + ignoreDateResolving?: boolean, ) => { if (format) { return format; @@ -30,7 +35,7 @@ export const resolveDateTimeFormat = ( views.forEach((view) => { if (isTimeView(view)) { timeViews.push(view as TimeView); - } else { + } else if (isDatePickerView(view)) { dateViews.push(view as DateView); } }); @@ -44,7 +49,9 @@ export const resolveDateTimeFormat = ( } const timeFormat = resolveTimeFormat(utils, { views: timeViews, ...other }); - const dateFormat = resolveDateFormat(utils, { views: dateViews, ...other }, false); + const dateFormat = ignoreDateResolving + ? utils.formats.keyboardDate + : resolveDateFormat(utils, { views: dateViews, ...other }, false); return `${dateFormat} ${timeFormat}`; }; diff --git a/packages/x-date-pickers/src/internals/utils/date-utils.test.ts b/packages/x-date-pickers/src/internals/utils/date-utils.test.ts index 6f7e2c855e308..a9d642959c4e4 100644 --- a/packages/x-date-pickers/src/internals/utils/date-utils.test.ts +++ b/packages/x-date-pickers/src/internals/utils/date-utils.test.ts @@ -32,9 +32,9 @@ describe('findClosestEnabledDate', () => { disableFuture: false, disablePast: false, timezone: 'default', - })!; + }); - expect(adapterToUse.isSameDay(result, adapterToUse.date('2000-01-01'))).to.equal(true); + expect(result).toEqualDateTime(adapterToUse.date('2000-01-01')); }); it('should return next 18th going from 10th', () => { @@ -47,9 +47,9 @@ describe('findClosestEnabledDate', () => { disableFuture: false, disablePast: false, timezone: 'default', - })!; + }); - expect(adapterToUse.isSameDay(result, adapterToUse.date('2018-08-18'))).to.equal(true); + expect(result).toEqualDateTime(adapterToUse.date('2018-08-18')); }); it('should return previous 18th going from 1st', () => { @@ -62,9 +62,9 @@ describe('findClosestEnabledDate', () => { disableFuture: false, disablePast: false, timezone: 'default', - })!; + }); - expect(adapterToUse.isSameDay(result, adapterToUse.date('2018-07-18'))).to.equal(true); + expect(result).toEqualDateTime(adapterToUse.date('2018-07-18')); }); it('should return future 18th if disablePast', () => { @@ -78,7 +78,7 @@ describe('findClosestEnabledDate', () => { disableFuture: false, disablePast: true, timezone: 'default', - })!; + }); expect(adapterToUse.isBefore(result, today)).to.equal(false); expect(adapterToUse.isBefore(result, adapterToUse.addDays(today, 31))).to.equal(true); @@ -95,9 +95,9 @@ describe('findClosestEnabledDate', () => { disableFuture: true, disablePast: true, timezone: 'default', - })!; + }); - expect(adapterToUse.isSameDay(result, today)).to.equal(true); + expect(result).toEqualDateTime(today); }); it('should return now with given time part if disablePast and now is valid', () => { @@ -113,13 +113,13 @@ describe('findClosestEnabledDate', () => { disableFuture: false, disablePast: true, timezone: 'default', - })!; + }); expect(result).toEqualDateTime(adapterToUse.addDays(tryDate, 1)); clock.reset(); }); - it('should fallback to today if disablePast+disableFuture and now is invalid', () => { + it('should return `null` when disablePast+disableFuture and now is invalid', () => { const today = adapterToUse.date(); const result = findClosestEnabledDate({ date: adapterToUse.date('2000-01-01'), @@ -132,7 +132,7 @@ describe('findClosestEnabledDate', () => { timezone: 'default', }); - expect(adapterToUse.isEqual(result, adapterToUse.date())); + expect(result).to.equal(null); }); it('should return minDate if it is after the date and valid', () => { @@ -145,9 +145,9 @@ describe('findClosestEnabledDate', () => { disableFuture: false, disablePast: false, timezone: 'default', - })!; + }); - expect(adapterToUse.isSameDay(result, adapterToUse.date('2018-08-18'))).to.equal(true); + expect(result).toEqualDateTime(adapterToUse.date('2018-08-18')); }); it('should return next 18th after minDate', () => { @@ -160,9 +160,27 @@ describe('findClosestEnabledDate', () => { disableFuture: false, disablePast: false, timezone: 'default', - })!; + }); + + expect(result).toEqualDateTime(adapterToUse.date('2018-08-18')); + }); + + it('should keep the time of the `date` when `disablePast`', () => { + const clock = useFakeTimers({ now: new Date('2000-01-02T11:12:13.123Z') }); + + const result = findClosestEnabledDate({ + date: adapterToUse.date('2000-01-01T11:12:13.550Z'), + minDate: adapterToUse.date('1900-01-01'), + maxDate: adapterToUse.date('2100-01-01'), + utils: adapterToUse, + isDateDisabled: () => false, + disableFuture: false, + disablePast: true, + timezone: 'default', + }); - expect(adapterToUse.isSameDay(result, adapterToUse.date('2018-08-18'))).to.equal(true); + expect(result).toEqualDateTime(adapterToUse.date('2000-01-02T11:12:13.550Z')); + clock.reset(); }); it('should return maxDate if it is before the date and valid', () => { @@ -175,9 +193,9 @@ describe('findClosestEnabledDate', () => { disableFuture: false, disablePast: false, timezone: 'default', - })!; + }); - expect(adapterToUse.isSameDay(result, adapterToUse.date('2018-07-18'))).to.equal(true); + expect(result).toEqualDateTime(adapterToUse.date('2018-07-18')); }); it('should return previous 18th before maxDate', () => { @@ -190,9 +208,9 @@ describe('findClosestEnabledDate', () => { disableFuture: false, disablePast: false, timezone: 'default', - })!; + }); - expect(adapterToUse.isSameDay(result, adapterToUse.date('2018-07-18'))).to.equal(true); + expect(result).toEqualDateTime(adapterToUse.date('2018-07-18')); }); it('should return null if minDate is after maxDate', () => { @@ -205,7 +223,7 @@ describe('findClosestEnabledDate', () => { disableFuture: false, disablePast: false, timezone: 'default', - })!; + }); expect(result).to.equal(null); }); diff --git a/packages/x-date-pickers/src/internals/utils/date-utils.ts b/packages/x-date-pickers/src/internals/utils/date-utils.ts index 5f04069743a44..781ca434fba6f 100644 --- a/packages/x-date-pickers/src/internals/utils/date-utils.ts +++ b/packages/x-date-pickers/src/internals/utils/date-utils.ts @@ -17,6 +17,7 @@ export const mergeDateAndTime = ( mergedDate = utils.setHours(mergedDate, utils.getHours(timeParam)); mergedDate = utils.setMinutes(mergedDate, utils.getMinutes(timeParam)); mergedDate = utils.setSeconds(mergedDate, utils.getSeconds(timeParam)); + mergedDate = utils.setMilliseconds(mergedDate, utils.getMilliseconds(timeParam)); return mergedDate; }; diff --git a/packages/x-date-pickers/src/locales/heIL.ts b/packages/x-date-pickers/src/locales/heIL.ts index 5068ec458a50e..cb588abaf3494 100644 --- a/packages/x-date-pickers/src/locales/heIL.ts +++ b/packages/x-date-pickers/src/locales/heIL.ts @@ -91,7 +91,7 @@ const heILPickers: Partial> = { hours: 'שעו×Ŗ', minutes: 'דקו×Ŗ', seconds: 'שניו×Ŗ', - // meridiem: 'Meridiem', + meridiem: 'יחיד×Ŗ זמן', // Common empty: '×Øיק', diff --git a/packages/x-date-pickers/src/locales/viVN.ts b/packages/x-date-pickers/src/locales/viVN.ts index 8444dbc804c5e..6e269490c20a0 100644 --- a/packages/x-date-pickers/src/locales/viVN.ts +++ b/packages/x-date-pickers/src/locales/viVN.ts @@ -25,10 +25,10 @@ const viVNPickers: Partial> = { // DateRange labels start: 'BįŗÆt đįŗ§u', end: 'Kįŗæt thĆŗc', - // startDate: 'Start date', - // startTime: 'Start time', - // endDate: 'End date', - // endTime: 'End time', + startDate: 'NgĆ y bįŗÆt đįŗ§u', + startTime: 'Thį»i gian bįŗÆt đįŗ§u', + endDate: 'NgĆ y kįŗæt thĆŗc', + endTime: 'Thį»i gian kįŗæt thĆŗc', // Action bar cancelButtonLabel: 'Hį»§y', @@ -67,7 +67,7 @@ const viVNPickers: Partial> = { value !== null && utils.isValid(value) ? `Chį»n giį», giį» Ä‘Ć£ chį»n lĆ  ${utils.format(value, 'fullTime')}` : 'Chį»n giį»', - // fieldClearLabel: 'Clear value', + fieldClearLabel: 'XĆ³a giĆ” trį»‹', // Table labels timeTableLabel: 'chį»n giį»', @@ -84,17 +84,17 @@ const viVNPickers: Partial> = { fieldMeridiemPlaceholder: () => 'aa', // View names - // year: 'Year', - // month: 'Month', - // day: 'Day', - // weekDay: 'Week day', - // hours: 'Hours', - // minutes: 'Minutes', - // seconds: 'Seconds', - // meridiem: 'Meridiem', + year: 'Năm', + month: 'ThĆ”ng', + day: 'NgĆ y', + weekDay: 'Thį»©', + hours: 'Giį»', + minutes: 'PhĆŗt', + seconds: 'GiĆ¢y', + meridiem: 'Buį»•i', // Common - // empty: 'Empty', + empty: 'Trį»‘ng', }; export const viVN = getPickersLocalization(viVNPickers); diff --git a/packages/x-date-pickers/src/locales/zhHK.ts b/packages/x-date-pickers/src/locales/zhHK.ts index 73f30fe05cb6c..9fb72b4769c59 100644 --- a/packages/x-date-pickers/src/locales/zhHK.ts +++ b/packages/x-date-pickers/src/locales/zhHK.ts @@ -65,7 +65,7 @@ const zhHKPickers: Partial> = { value !== null && utils.isValid(value) ? `éø꓇Ꙃ間ļ¼Œå·²éø꓇${utils.format(value, 'fullTime')}` : 'éø꓇Ꙃ間', - // fieldClearLabel: 'Clear value', + fieldClearLabel: 'ęø…除', // Table labels timeTableLabel: 'éø꓇Ꙃ間', diff --git a/packages/x-date-pickers/src/tests/fieldKeyboardInteraction.test.tsx b/packages/x-date-pickers/src/tests/fieldKeyboardInteraction.test.tsx index f9a7fc6144780..3d7436f158ea1 100644 --- a/packages/x-date-pickers/src/tests/fieldKeyboardInteraction.test.tsx +++ b/packages/x-date-pickers/src/tests/fieldKeyboardInteraction.test.tsx @@ -75,28 +75,22 @@ describe(`RTL - test arrows navigation`, () => { const expectedValues = ['hh', 'mm', 'YYYY', 'MM', 'DD', 'DD']; // Test with v7 input - const v7Response = renderWithProps( - { enableAccessibleFieldDOMStructure: true }, - { direction: 'rtl' }, - ); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }, { direction: 'rtl' }); - v7Response.selectSection('hours'); + view.selectSection('hours'); expectedValues.forEach((expectedValue) => { expect(getCleanedSelectedContent()).to.equal(expectedValue); - fireEvent.keyDown(v7Response.getActiveSection(undefined), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(undefined), { key: 'ArrowRight' }); }); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps( - { enableAccessibleFieldDOMStructure: false }, - { direction: 'rtl' }, - ); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }, { direction: 'rtl' }); const input = getTextbox(); - v6Response.selectSection('hours'); + view.selectSection('hours'); expectedValues.forEach((expectedValue) => { expect(getCleanedSelectedContent()).to.equal(expectedValue); @@ -108,28 +102,22 @@ describe(`RTL - test arrows navigation`, () => { const expectedValues = ['DD', 'MM', 'YYYY', 'mm', 'hh', 'hh']; // Test with v7 input - const v7Response = renderWithProps( - { enableAccessibleFieldDOMStructure: true }, - { direction: 'rtl' }, - ); + let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }, { direction: 'rtl' }); - v7Response.selectSection('day'); + view.selectSection('day'); expectedValues.forEach((expectedValue) => { expect(getCleanedSelectedContent()).to.equal(expectedValue); - fireEvent.keyDown(v7Response.getActiveSection(undefined), { key: 'ArrowLeft' }); + fireEvent.keyDown(view.getActiveSection(undefined), { key: 'ArrowLeft' }); }); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps( - { enableAccessibleFieldDOMStructure: false }, - { direction: 'rtl' }, - ); + view = renderWithProps({ enableAccessibleFieldDOMStructure: false }, { direction: 'rtl' }); const input = getTextbox(); - v6Response.selectSection('day'); + view.selectSection('day'); expectedValues.forEach((expectedValue) => { expect(getCleanedSelectedContent()).to.equal(expectedValue); @@ -142,7 +130,7 @@ describe(`RTL - test arrows navigation`, () => { const expectedValues = ['11', '54', '1397', '02', '05', '05']; // Test with v7 input - const v7Response = renderWithProps( + let view = renderWithProps( { enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date('2018-04-25T11:54:00'), @@ -150,17 +138,17 @@ describe(`RTL - test arrows navigation`, () => { { direction: 'rtl' }, ); - v7Response.selectSection('hours'); + view.selectSection('hours'); expectedValues.forEach((expectedValue) => { expect(getCleanedSelectedContent()).to.equal(expectedValue); - fireEvent.keyDown(v7Response.getActiveSection(undefined), { key: 'ArrowRight' }); + fireEvent.keyDown(view.getActiveSection(undefined), { key: 'ArrowRight' }); }); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps( + view = renderWithProps( { defaultValue: adapter.date('2018-04-25T11:54:00'), enableAccessibleFieldDOMStructure: false, @@ -169,7 +157,7 @@ describe(`RTL - test arrows navigation`, () => { ); const input = getTextbox(); - v6Response.selectSection('hours'); + view.selectSection('hours'); expectedValues.forEach((expectedValue) => { expect(getCleanedSelectedContent()).to.equal(expectedValue); @@ -182,7 +170,7 @@ describe(`RTL - test arrows navigation`, () => { const expectedValues = ['05', '02', '1397', '54', '11', '11']; // Test with v7 input - const v7Response = renderWithProps( + let view = renderWithProps( { enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date('2018-04-25T11:54:00'), @@ -190,17 +178,17 @@ describe(`RTL - test arrows navigation`, () => { { direction: 'rtl' }, ); - v7Response.selectSection('day'); + view.selectSection('day'); expectedValues.forEach((expectedValue) => { expect(getCleanedSelectedContent()).to.equal(expectedValue); - fireEvent.keyDown(v7Response.getActiveSection(undefined), { key: 'ArrowLeft' }); + fireEvent.keyDown(view.getActiveSection(undefined), { key: 'ArrowLeft' }); }); - v7Response.unmount(); + view.unmount(); // Test with v6 input - const v6Response = renderWithProps( + view = renderWithProps( { defaultValue: adapter.date('2018-04-25T11:54:00'), enableAccessibleFieldDOMStructure: false, @@ -209,7 +197,7 @@ describe(`RTL - test arrows navigation`, () => { ); const input = getTextbox(); - v6Response.selectSection('day'); + view.selectSection('day'); expectedValues.forEach((expectedValue) => { expect(getCleanedSelectedContent()).to.equal(expectedValue); @@ -265,16 +253,16 @@ adapterToTest.forEach((adapterName) => { expectedValue: TDate; sectionConfig: ReturnType; }) => { - const v7Response = renderWithProps({ + const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: initialValue, format, }); - v7Response.selectSection(sectionConfig.type); - fireEvent.keyDown(v7Response.getActiveSection(0), { key }); + view.selectSection(sectionConfig.type); + fireEvent.keyDown(view.getActiveSection(0), { key }); expectFieldValueV7( - v7Response.getSectionsContainer(), + view.getSectionsContainer(), cleanValueStr(adapter.formatByString(expectedValue, format), sectionConfig), ); }; diff --git a/packages/x-date-pickers/src/tests/materialVersion.test.tsx b/packages/x-date-pickers/src/tests/materialVersion.test.tsx new file mode 100644 index 0000000000000..dc0c0a7e6ee01 --- /dev/null +++ b/packages/x-date-pickers/src/tests/materialVersion.test.tsx @@ -0,0 +1,5 @@ +import materialPackageJson from '@mui/material/package.json'; +import { checkMaterialVersion } from 'test/utils/checkMaterialVersion'; +import packageJson from '../../package.json'; + +checkMaterialVersion({ packageJson, materialPackageJson }); diff --git a/packages/x-date-pickers/src/themeAugmentation/index.ts b/packages/x-date-pickers/src/themeAugmentation/index.ts index 492f27d8f9176..d387cd19110ff 100644 --- a/packages/x-date-pickers/src/themeAugmentation/index.ts +++ b/packages/x-date-pickers/src/themeAugmentation/index.ts @@ -1,3 +1,3 @@ -export * from './overrides'; -export * from './props'; -export * from './components'; +export type * from './overrides'; +export type * from './props'; +export type * from './components'; diff --git a/packages/x-internals/package.json b/packages/x-internals/package.json index 405e3106e5d09..8bb3b2a1c80e7 100644 --- a/packages/x-internals/package.json +++ b/packages/x-internals/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-internals", - "version": "7.12.0", + "version": "7.15.0", "description": "Utility functions for the MUI X packages (internal use only).", "author": "MUI Team", "license": "MIT", @@ -41,15 +41,15 @@ "directory": "packages/x-internals" }, "dependencies": { - "@babel/runtime": "^7.25.0", - "@mui/utils": "^5.16.5" + "@babel/runtime": "^7.25.4", + "@mui/utils": "^5.16.6" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0" }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.5", - "rimraf": "^5.0.9" + "@mui/internal-test-utils": "^1.0.11", + "rimraf": "^5.0.10" }, "engines": { "node": ">=14.0.0" diff --git a/packages/x-data-grid/src/utils/fastMemo.ts b/packages/x-internals/src/fastMemo/fastMemo.ts similarity index 69% rename from packages/x-data-grid/src/utils/fastMemo.ts rename to packages/x-internals/src/fastMemo/fastMemo.ts index 86780baa5e551..7abb1f6d95f2c 100644 --- a/packages/x-data-grid/src/utils/fastMemo.ts +++ b/packages/x-internals/src/fastMemo/fastMemo.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { fastObjectShallowCompare } from './fastObjectShallowCompare'; +import { fastObjectShallowCompare } from '../fastObjectShallowCompare'; export function fastMemo(component: T): T { return React.memo(component as any, fastObjectShallowCompare) as unknown as T; diff --git a/packages/x-internals/src/fastMemo/index.ts b/packages/x-internals/src/fastMemo/index.ts new file mode 100644 index 0000000000000..b0fe71454ee09 --- /dev/null +++ b/packages/x-internals/src/fastMemo/index.ts @@ -0,0 +1 @@ +export { fastMemo } from './fastMemo'; diff --git a/packages/x-data-grid/src/utils/fastObjectShallowCompare.ts b/packages/x-internals/src/fastObjectShallowCompare/fastObjectShallowCompare.ts similarity index 100% rename from packages/x-data-grid/src/utils/fastObjectShallowCompare.ts rename to packages/x-internals/src/fastObjectShallowCompare/fastObjectShallowCompare.ts diff --git a/packages/x-internals/src/fastObjectShallowCompare/index.ts b/packages/x-internals/src/fastObjectShallowCompare/index.ts new file mode 100644 index 0000000000000..44bf7f5f0efec --- /dev/null +++ b/packages/x-internals/src/fastObjectShallowCompare/index.ts @@ -0,0 +1 @@ +export { fastObjectShallowCompare } from './fastObjectShallowCompare'; diff --git a/packages/x-internals/src/throttle/index.ts b/packages/x-internals/src/throttle/index.ts new file mode 100644 index 0000000000000..5bf825e16ac80 --- /dev/null +++ b/packages/x-internals/src/throttle/index.ts @@ -0,0 +1 @@ +export { throttle } from './throttle'; diff --git a/packages/x-data-grid/src/utils/throttle.ts b/packages/x-internals/src/throttle/throttle.ts similarity index 100% rename from packages/x-data-grid/src/utils/throttle.ts rename to packages/x-internals/src/throttle/throttle.ts diff --git a/packages/x-license/package.json b/packages/x-license/package.json index dcc406c7f0b75..04f463940f084 100644 --- a/packages/x-license/package.json +++ b/packages/x-license/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-license", - "version": "7.12.0", + "version": "7.15.0", "description": "MUI X License verification", "author": "MUI Team", "main": "src/index.ts", @@ -34,15 +34,15 @@ "directory": "packages/x-license" }, "dependencies": { - "@babel/runtime": "^7.25.0", - "@mui/utils": "^5.16.5" + "@babel/runtime": "^7.25.4", + "@mui/utils": "^5.16.6" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0" }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.5", - "rimraf": "^5.0.9" + "@mui/internal-test-utils": "^1.0.11", + "rimraf": "^5.0.10" }, "engines": { "node": ">=14.0.0" diff --git a/packages/x-license/src/generateLicense/generateLicense.ts b/packages/x-license/src/generateLicense/generateLicense.ts index badebb90d782c..107b510888d0a 100644 --- a/packages/x-license/src/generateLicense/generateLicense.ts +++ b/packages/x-license/src/generateLicense/generateLicense.ts @@ -8,25 +8,36 @@ const licenseVersion = '2'; export interface LicenseDetails { orderNumber: string; expiryDate: Date; - scope: LicenseScope; - licensingModel: LicensingModel; + scope?: LicenseScope; + planScope?: LicenseScope; // TODO deprecate + licenseModel?: LicensingModel; + licensingModel?: LicensingModel; // TODO deprecate planVersion: PlanVersion; } function getClearLicenseString(details: LicenseDetails) { - if (details.scope && !LICENSE_SCOPES.includes(details.scope)) { + // TODO remove + if (details.licensingModel) { + details.licenseModel = details.licensingModel; + } + // TODO remove + if (details.scope) { + details.planScope = details.scope; + } + + if (details.planScope && !LICENSE_SCOPES.includes(details.planScope)) { throw new Error('MUI X: Invalid scope'); } - if (details.licensingModel && !LICENSING_MODELS.includes(details.licensingModel)) { + if (details.licenseModel && !LICENSING_MODELS.includes(details.licenseModel)) { throw new Error('MUI X: Invalid licensing model'); } const keyParts = [ `O=${details.orderNumber}`, `E=${details.expiryDate.getTime()}`, - `S=${details.scope}`, - `LM=${details.licensingModel}`, + `S=${details.planScope}`, + `LM=${details.licenseModel}`, `PV=${details.planVersion}`, `KV=${licenseVersion}`, ]; diff --git a/packages/x-tree-view-pro/package.json b/packages/x-tree-view-pro/package.json index b2f2f014f6d18..5a418238512a3 100644 --- a/packages/x-tree-view-pro/package.json +++ b/packages/x-tree-view-pro/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-tree-view-pro", - "version": "7.12.0", + "version": "7.15.0", "description": "The Pro plan edition of the Tree View components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -15,7 +15,8 @@ }, "sideEffects": false, "publishConfig": { - "access": "public" + "access": "public", + "directory": "build" }, "keywords": [ "react", @@ -42,13 +43,12 @@ "directory": "packages/x-tree-view-pro" }, "dependencies": { - "@babel/runtime": "^7.25.0", - "@mui/system": "^5.16.5", - "@mui/utils": "^5.16.5", + "@babel/runtime": "^7.25.4", + "@mui/utils": "^5.16.6", "@mui/x-internals": "workspace:*", "@mui/x-license": "workspace:*", "@mui/x-tree-view": "workspace:*", - "@types/react-transition-group": "^4.4.10", + "@types/react-transition-group": "^4.4.11", "clsx": "^2.1.1", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" @@ -56,14 +56,25 @@ "peerDependencies": { "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", "react": "^17.0.0 || ^18.0.0", "react-dom": "^17.0.0 || ^18.0.0" }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + }, "devDependencies": { - "@mui/internal-test-utils": "^1.0.5", + "@mui/internal-test-utils": "^1.0.11", + "@mui/material": "^5.16.7", + "@mui/system": "^5.16.7", "@types/prop-types": "^15.7.12", - "rimraf": "^5.0.9" + "rimraf": "^5.0.10" }, "engines": { "node": ">=14.0.0" diff --git a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.plugins.ts b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.plugins.ts index 70dbf423afe4b..7e7adee15189f 100644 --- a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.plugins.ts +++ b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.plugins.ts @@ -13,6 +13,8 @@ import { ConvertPluginsIntoSignatures, MergeSignaturesProperty, TreeViewCorePluginParameters, + useTreeViewLabel, + UseTreeViewLabelParameters, } from '@mui/x-tree-view/internals'; import { useTreeViewItemsReordering, @@ -26,6 +28,7 @@ export const RICH_TREE_VIEW_PRO_PLUGINS = [ useTreeViewFocus, useTreeViewKeyboardNavigation, useTreeViewIcons, + useTreeViewLabel, useTreeViewItemsReordering, ] as const; @@ -51,4 +54,5 @@ export interface RichTreeViewProPluginParameters, UseTreeViewIconsParameters, + UseTreeViewLabelParameters, UseTreeViewItemsReorderingParameters {} diff --git a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx index e71cc8ed65343..c08210e097822 100644 --- a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx +++ b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx @@ -190,12 +190,13 @@ RichTreeViewPro.propTypes = { getItemTree: PropTypes.func.isRequired, selectItem: PropTypes.func.isRequired, setItemExpansion: PropTypes.func.isRequired, + updateItemLabel: PropTypes.func.isRequired, }), }), /** * Used to determine if a given item can move to some new position. * @param {object} params The params describing the item re-ordering. - * @param {string} params.itemId The id of the item to check. + * @param {string} params.itemId The id of the item that is being moved to a new position. * @param {TreeViewItemReorderPosition} params.oldPosition The old position of the item. * @param {TreeViewItemReorderPosition} params.newPosition The new position of the item. * @returns {boolean} `true` if the item can move to the new position. @@ -251,6 +252,7 @@ RichTreeViewPro.propTypes = { experimentalFeatures: PropTypes.shape({ indentationAtItemLevel: PropTypes.bool, itemsReordering: PropTypes.bool, + labelEditing: PropTypes.bool, }), /** * Used to determine the id of a given item. @@ -282,6 +284,16 @@ RichTreeViewPro.propTypes = { * @returns {boolean} `true` if the item should be disabled. */ isItemDisabled: PropTypes.func, + /** + * Determines if a given item is editable or not. + * Make sure to also enable the `labelEditing` experimental feature: + * ``. + * By default, the items are not editable. + * @template R + * @param {R} item The item to check. + * @returns {boolean} `true` if the item is editable. + */ + isItemEditable: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), /** * Used to determine if a given item can be reordered. * @param {string} itemId The id of the item to check. @@ -333,6 +345,12 @@ RichTreeViewPro.propTypes = { * @param {string} itemId The id of the focused item. */ onItemFocus: PropTypes.func, + /** + * Callback fired when the label of an item changes. + * @param {TreeViewItemId} itemId The id of the item that was edited. + * @param {string} newLabel The new label of the items. + */ + onItemLabelChange: PropTypes.func, /** * Callback fired when a tree item is moved in the tree. * @param {object} params The params describing the item re-ordering. diff --git a/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.test.tsx b/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.test.tsx index b316969dbc1fb..feae408bdcc7f 100644 --- a/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.test.tsx +++ b/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.test.tsx @@ -76,54 +76,54 @@ describeTreeView< describe('itemReordering prop', () => { it('should allow to drag and drop items when props.itemsReordering={true}', () => { - const response = render({ + const view = render({ experimentalFeatures: { indentationAtItemLevel: true, itemsReordering: true }, items: [{ id: '1' }, { id: '2' }, { id: '3' }], itemsReordering: true, }); - dragEvents.fullDragSequence(response.getItemRoot('1'), response.getItemContent('2')); - expect(response.getItemIdTree()).to.deep.equal([ + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([ { id: '2', children: [{ id: '1' }] }, { id: '3' }, ]); }); it('should not allow to drag and drop items when props.itemsReordering={false}', () => { - const response = render({ + const view = render({ experimentalFeatures: { indentationAtItemLevel: true, itemsReordering: true }, items: [{ id: '1' }, { id: '2' }, { id: '3' }], itemsReordering: false, }); - dragEvents.fullDragSequence(response.getItemRoot('1'), response.getItemContent('2')); - expect(response.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); it('should not allow to drag and drop items when props.itemsReordering is not defined', () => { - const response = render({ + const view = render({ experimentalFeatures: { indentationAtItemLevel: true }, items: [{ id: '1' }, { id: '2' }, { id: '3' }], }); - dragEvents.fullDragSequence(response.getItemRoot('1'), response.getItemContent('2')); - expect(response.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); it('should allow to expand the new parent of the dragged item when it was not expandable before', () => { - const response = render({ + const view = render({ experimentalFeatures: { indentationAtItemLevel: true, itemsReordering: true }, items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], itemsReordering: true, defaultExpandedItems: ['1'], }); - dragEvents.fullDragSequence(response.getItemRoot('1.1'), response.getItemContent('2')); + dragEvents.fullDragSequence(view.getItemRoot('1.1'), view.getItemContent('2')); - fireEvent.focus(response.getItemRoot('2')); - fireEvent.keyDown(response.getItemRoot('2'), { key: 'Enter' }); + fireEvent.focus(view.getItemRoot('2')); + fireEvent.keyDown(view.getItemRoot('2'), { key: 'Enter' }); - expect(response.getItemIdTree()).to.deep.equal([ + expect(view.getItemIdTree()).to.deep.equal([ { id: '1', children: [] }, { id: '2', children: [{ id: '1.1' }] }, ]); @@ -133,14 +133,14 @@ describeTreeView< describe('onItemPositionChange prop', () => { it('should call onItemPositionChange when an item is moved', () => { const onItemPositionChange = spy(); - const response = render({ + const view = render({ experimentalFeatures: { indentationAtItemLevel: true, itemsReordering: true }, items: [{ id: '1' }, { id: '2' }, { id: '3' }], itemsReordering: true, onItemPositionChange, }); - dragEvents.fullDragSequence(response.getItemRoot('1'), response.getItemContent('2')); + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); expect(onItemPositionChange.callCount).to.equal(1); expect(onItemPositionChange.lastCall.firstArg).to.deep.equal({ itemId: '1', @@ -152,27 +152,27 @@ describeTreeView< describe('isItemReorderable prop', () => { it('should not allow to drag an item when isItemReorderable returns false', () => { - const response = render({ + const view = render({ experimentalFeatures: { indentationAtItemLevel: true, itemsReordering: true }, items: [{ id: '1' }, { id: '2' }, { id: '3' }], itemsReordering: true, canMoveItemToNewPosition: () => false, }); - dragEvents.fullDragSequence(response.getItemRoot('1'), response.getItemContent('2')); - expect(response.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); it('should allow to drag an item when isItemReorderable returns true', () => { - const response = render({ + const view = render({ experimentalFeatures: { indentationAtItemLevel: true, itemsReordering: true }, items: [{ id: '1' }, { id: '2' }, { id: '3' }], itemsReordering: true, canMoveItemToNewPosition: () => true, }); - dragEvents.fullDragSequence(response.getItemRoot('1'), response.getItemContent('2')); - expect(response.getItemIdTree()).to.deep.equal([ + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([ { id: '2', children: [{ id: '1' }] }, { id: '3' }, ]); @@ -180,28 +180,45 @@ describeTreeView< }); describe('canMoveItemToNewPosition prop', () => { + it('should call canMoveItemToNewPosition with the correct parameters', () => { + const canMoveItemToNewPosition = spy(); + const view = render({ + experimentalFeatures: { indentationAtItemLevel: true, itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + canMoveItemToNewPosition, + }); + + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(canMoveItemToNewPosition.lastCall.firstArg).to.deep.equal({ + itemId: '1', + oldPosition: { parentId: null, index: 0 }, + newPosition: { parentId: null, index: 1 }, + }); + }); + it('should not allow to drop an item when canMoveItemToNewPosition returns false', () => { - const response = render({ + const view = render({ experimentalFeatures: { indentationAtItemLevel: true, itemsReordering: true }, items: [{ id: '1' }, { id: '2' }, { id: '3' }], itemsReordering: true, canMoveItemToNewPosition: () => false, }); - dragEvents.fullDragSequence(response.getItemRoot('1'), response.getItemContent('2')); - expect(response.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); it('should allow to drop an item when canMoveItemToNewPosition returns true', () => { - const response = render({ + const view = render({ experimentalFeatures: { indentationAtItemLevel: true, itemsReordering: true }, items: [{ id: '1' }, { id: '2' }, { id: '3' }], itemsReordering: true, canMoveItemToNewPosition: () => true, }); - dragEvents.fullDragSequence(response.getItemRoot('1'), response.getItemContent('2')); - expect(response.getItemIdTree()).to.deep.equal([ + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([ { id: '2', children: [{ id: '1' }] }, { id: '3' }, ]); diff --git a/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.ts b/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.ts index 91843a7b6dc49..a19c0c491e0e3 100644 --- a/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.ts +++ b/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.ts @@ -55,18 +55,19 @@ export const useTreeViewItemsReordering: TreeViewPlugin { - if (!state.itemsReordering) { + const itemsReordering = state.itemsReordering; + if (!itemsReordering) { throw new Error('There is no ongoing reordering.'); } - if (itemId === state.itemsReordering.draggedItemId) { + if (itemId === itemsReordering.draggedItemId) { return {}; } const canMoveItemToNewPosition = params.canMoveItemToNewPosition; const targetItemMeta = instance.getItemMeta(itemId); const targetItemIndex = instance.getItemIndex(targetItemMeta.id); - const draggedItemMeta = instance.getItemMeta(state.itemsReordering.draggedItemId); + const draggedItemMeta = instance.getItemMeta(itemsReordering.draggedItemId); const draggedItemIndex = instance.getItemIndex(draggedItemMeta.id); const oldPosition: TreeViewItemReorderPosition = { @@ -84,7 +85,7 @@ export const useTreeViewItemsReordering: TreeViewPlugin=14.0.0" diff --git a/packages/x-tree-view/src/RichTreeView/RichTreeView.plugins.ts b/packages/x-tree-view/src/RichTreeView/RichTreeView.plugins.ts index 3da29c2c32789..6b2d736da3947 100644 --- a/packages/x-tree-view/src/RichTreeView/RichTreeView.plugins.ts +++ b/packages/x-tree-view/src/RichTreeView/RichTreeView.plugins.ts @@ -21,6 +21,10 @@ import { UseTreeViewIconsParameters, } from '../internals/plugins/useTreeViewIcons'; import { ConvertPluginsIntoSignatures, MergeSignaturesProperty } from '../internals/models'; +import { + useTreeViewLabel, + UseTreeViewLabelParameters, +} from '../internals/plugins/useTreeViewLabel'; export const RICH_TREE_VIEW_PLUGINS = [ useTreeViewItems, @@ -29,6 +33,7 @@ export const RICH_TREE_VIEW_PLUGINS = [ useTreeViewFocus, useTreeViewKeyboardNavigation, useTreeViewIcons, + useTreeViewLabel, ] as const; export type RichTreeViewPluginSignatures = ConvertPluginsIntoSignatures< @@ -52,4 +57,5 @@ export interface RichTreeViewPluginParameters, - UseTreeViewIconsParameters {} + UseTreeViewIconsParameters, + UseTreeViewLabelParameters {} diff --git a/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx b/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx index 6eb825d49fa1e..5c46c54794d1d 100644 --- a/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx +++ b/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx @@ -154,6 +154,7 @@ RichTreeView.propTypes = { getItemTree: PropTypes.func.isRequired, selectItem: PropTypes.func.isRequired, setItemExpansion: PropTypes.func.isRequired, + updateItemLabel: PropTypes.func.isRequired, }), }), /** @@ -205,6 +206,7 @@ RichTreeView.propTypes = { */ experimentalFeatures: PropTypes.shape({ indentationAtItemLevel: PropTypes.bool, + labelEditing: PropTypes.bool, }), /** * Used to determine the id of a given item. @@ -236,6 +238,16 @@ RichTreeView.propTypes = { * @returns {boolean} `true` if the item should be disabled. */ isItemDisabled: PropTypes.func, + /** + * Determines if a given item is editable or not. + * Make sure to also enable the `labelEditing` experimental feature: + * ``. + * By default, the items are not editable. + * @template R + * @param {R} item The item to check. + * @returns {boolean} `true` if the item is editable. + */ + isItemEditable: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]), /** * Horizontal indentation between an item and its children. * Examples: 24, "24px", "2rem", "2em". @@ -273,6 +285,12 @@ RichTreeView.propTypes = { * @param {string} itemId The id of the focused item. */ onItemFocus: PropTypes.func, + /** + * Callback fired when the label of an item changes. + * @param {TreeViewItemId} itemId The id of the item that was edited. + * @param {string} newLabel The new label of the items. + */ + onItemLabelChange: PropTypes.func, /** * Callback fired when a tree item is selected or deselected. * @param {React.SyntheticEvent} event The DOM event that triggered the change. diff --git a/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx b/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx index d85f3e7aa395a..f893aec46d2ac 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx +++ b/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx @@ -21,12 +21,12 @@ describeTreeView<[]>('TreeItem component', ({ render, treeItemComponentName }) =
)); - const response = render({ + const view = render({ items: [{ id: '1' }], slotProps: { item: { ContentComponent } }, }); - expect(response.getItemContent('1').textContent).to.equal('MOCK CONTENT COMPONENT'); + expect(view.getItemContent('1').textContent).to.equal('MOCK CONTENT COMPONENT'); }); it('should use the ContentProps prop when defined', function test() { @@ -40,12 +40,12 @@ describeTreeView<[]>('TreeItem component', ({ render, treeItemComponentName }) = )); - const response = render({ + const view = render({ items: [{ id: '1' }], slotProps: { item: { ContentComponent, ContentProps: { customProp: 'ABCDEF' } as any } }, }); - expect(response.getItemContent('1').textContent).to.equal('ABCDEF'); + expect(view.getItemContent('1').textContent).to.equal('ABCDEF'); }); it('should render TreeItem when itemId prop is escaping characters without throwing an error', function test() { @@ -53,11 +53,11 @@ describeTreeView<[]>('TreeItem component', ({ render, treeItemComponentName }) = this.skip(); } - const response = render({ + const view = render({ items: [{ id: 'C:\\\\', label: 'ABCDEF' }], }); - expect(response.getItemContent('C:\\\\').textContent).to.equal('ABCDEF'); + expect(view.getItemContent('C:\\\\').textContent).to.equal('ABCDEF'); }); }); }); diff --git a/packages/x-tree-view/src/TreeItem/TreeItem.tsx b/packages/x-tree-view/src/TreeItem/TreeItem.tsx index c66a4fde6a302..2be1daaa83f5e 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItem.tsx +++ b/packages/x-tree-view/src/TreeItem/TreeItem.tsx @@ -26,6 +26,7 @@ import { TreeViewCollapseIcon, TreeViewExpandIcon } from '../icons'; import { TreeItem2Provider } from '../TreeItem2Provider'; import { TreeViewItemDepthContext } from '../internals/TreeViewItemDepthContext'; import { useTreeItemState } from './useTreeItemState'; +import { isTargetInDescendants } from '../internals/utils/tree'; const useThemeProps = createUseThemeProps('MuiTreeItem'); @@ -42,6 +43,9 @@ const useUtilityClasses = (ownerState: TreeItemOwnerState) => { iconContainer: ['iconContainer'], checkbox: ['checkbox'], label: ['label'], + labelInput: ['labelInput'], + editing: ['editing'], + editable: ['editable'], groupTransition: ['groupTransition'], }; @@ -217,7 +221,8 @@ export const TreeItem = React.forwardRef(function TreeItem( ...other } = props; - const { expanded, focused, selected, disabled, handleExpansion } = useTreeItemState(itemId); + const { expanded, focused, selected, disabled, editing, handleExpansion } = + useTreeItemState(itemId); const { contentRef, rootRef, propsEnhancers } = runItemPlugins(props); const rootRefObject = React.useRef(null); @@ -343,11 +348,27 @@ export const TreeItem = React.forwardRef(function TreeItem( function handleBlur(event: React.FocusEvent) { onBlur?.(event); + if ( + editing || + // we can exit the editing state by clicking outside the input (within the tree item) or by pressing Enter or Escape -> we don't want to remove the focused item from the state in these cases + // we can also exit the editing state by clicking on the root itself -> want to remove the focused item from the state in this case + (event.relatedTarget && + isTargetInDescendants(event.relatedTarget as HTMLElement, rootRefObject.current) && + ((event.target && + (event.target as HTMLElement)?.dataset?.element === 'labelInput' && + isTargetInDescendants(event.target as HTMLElement, rootRefObject.current)) || + (event.relatedTarget as HTMLElement)?.dataset?.element === 'labelInput')) + ) { + return; + } instance.removeFocusedItem(); } const handleKeyDown = (event: React.KeyboardEvent) => { onKeyDown?.(event); + if ((event.target as HTMLElement)?.dataset?.element === 'labelInput') { + return; + } instance.handleItemKeyDown(event, itemId); }; @@ -372,6 +393,12 @@ export const TreeItem = React.forwardRef(function TreeItem( contentRefObject, externalEventHandlers: {}, }) ?? {}; + const enhancedLabelInputProps = + propsEnhancers.labelInput?.({ + rootRefObject, + contentRefObject, + externalEventHandlers: {}, + }) ?? {}; return ( @@ -408,8 +435,11 @@ export const TreeItem = React.forwardRef(function TreeItem( selected: classes.selected, focused: classes.focused, disabled: classes.disabled, + editable: classes.editable, + editing: classes.editing, iconContainer: classes.iconContainer, label: classes.label, + labelInput: classes.labelInput, checkbox: classes.checkbox, }} label={label} @@ -425,6 +455,9 @@ export const TreeItem = React.forwardRef(function TreeItem( {...((enhancedDragAndDropOverlayProps as any).action == null ? {} : { dragAndDropOverlayProps: enhancedDragAndDropOverlayProps })} + {...((enhancedLabelInputProps as any).value == null + ? {} + : { labelInputProps: enhancedLabelInputProps })} ref={handleContentRef} /> {children && ( diff --git a/packages/x-tree-view/src/TreeItem/TreeItemContent.tsx b/packages/x-tree-view/src/TreeItem/TreeItemContent.tsx index 686d7d9c88a5d..2c62bd29c452f 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItemContent.tsx +++ b/packages/x-tree-view/src/TreeItem/TreeItemContent.tsx @@ -7,6 +7,8 @@ import { TreeItem2DragAndDropOverlay, TreeItem2DragAndDropOverlayProps, } from '../TreeItem2DragAndDropOverlay'; +import { TreeItem2LabelInput, TreeItem2LabelInputProps } from '../TreeItem2LabelInput'; +import { MuiCancellableEvent } from '../internals/models'; export interface TreeItemContentProps extends React.HTMLAttributes { className?: string; @@ -30,6 +32,12 @@ export interface TreeItemContentProps extends React.HTMLAttributes label: string; /** Styles applied to the checkbox element. */ checkbox: string; + /** Styles applied to the input element that is visible when editing is enabled. */ + labelInput: string; + /** Styles applied to the content element when editing is enabled. */ + editing: string; + /** Styles applied to the content of the items that are editable. */ + editable: string; }; /** * The tree item label. @@ -52,6 +60,7 @@ export interface TreeItemContentProps extends React.HTMLAttributes */ displayIcon?: React.ReactNode; dragAndDropOverlayProps?: TreeItem2DragAndDropOverlayProps; + labelInputProps?: TreeItem2LabelInputProps; } export type TreeItemContentClassKey = keyof NonNullable; @@ -74,6 +83,7 @@ const TreeItemContent = React.forwardRef(function TreeItemContent( onClick, onMouseDown, dragAndDropOverlayProps, + labelInputProps, ...other } = props; @@ -82,6 +92,8 @@ const TreeItemContent = React.forwardRef(function TreeItemContent( expanded, selected, focused, + editing, + editable, disableSelection, checkboxSelection, handleExpansion, @@ -90,6 +102,9 @@ const TreeItemContent = React.forwardRef(function TreeItemContent( handleContentClick, preventSelection, expansionTrigger, + toggleItemEditing, + handleSaveItemLabel, + handleCancelItemLabelEditing, } = useTreeItemState(itemId); const icon = iconProp || expansionIcon || displayIcon; @@ -123,6 +138,39 @@ const TreeItemContent = React.forwardRef(function TreeItemContent( } }; + const handleLabelDoubleClick = (event: React.MouseEvent & MuiCancellableEvent) => { + if (event.defaultMuiPrevented) { + return; + } + toggleItemEditing(); + }; + const handleLabelInputBlur = ( + event: React.FocusEvent & MuiCancellableEvent, + ) => { + if (event.defaultMuiPrevented) { + return; + } + + if (event.target.value) { + handleSaveItemLabel(event, event.target.value); + } + }; + + const handleLabelInputKeydown = ( + event: React.KeyboardEvent & MuiCancellableEvent, + ) => { + if (event.defaultMuiPrevented) { + return; + } + + const target = event.target as HTMLInputElement; + if (event.key === 'Enter' && target.value) { + handleSaveItemLabel(event, target.value); + } else if (event.key === 'Escape') { + handleCancelItemLabelEditing(event); + } + }; + return ( /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions -- Key event is handled by the TreeView */
)} -
{label}
+ {editing ? ( + + ) : ( +
+ {label} +
+ )} + {dragAndDropOverlayProps && }
); @@ -189,6 +251,7 @@ TreeItemContent.propTypes = { * The tree item label. */ label: PropTypes.node, + labelInputProps: PropTypes.object, } as any; export { TreeItemContent }; diff --git a/packages/x-tree-view/src/TreeItem/treeItemClasses.ts b/packages/x-tree-view/src/TreeItem/treeItemClasses.ts index a8ac533418c60..6b0efd7279ce5 100644 --- a/packages/x-tree-view/src/TreeItem/treeItemClasses.ts +++ b/packages/x-tree-view/src/TreeItem/treeItemClasses.ts @@ -22,6 +22,12 @@ export interface TreeItemClasses { label: string; /** Styles applied to the checkbox element. */ checkbox: string; + /** Styles applied to the input element that is visible when editing is enabled. */ + labelInput: string; + /** Styles applied to the content element when editing is enabled. */ + editing: string; + /** Styles applied to the content of the items that are editable. */ + editable: string; /** Styles applied to the drag and drop overlay. */ dragAndDropOverlay: string; } @@ -43,5 +49,8 @@ export const treeItemClasses: TreeItemClasses = generateUtilityClasses('MuiTreeI 'iconContainer', 'label', 'checkbox', + 'labelInput', + 'editable', + 'editing', 'dragAndDropOverlay', ]); diff --git a/packages/x-tree-view/src/TreeItem/useTreeItemState.ts b/packages/x-tree-view/src/TreeItem/useTreeItemState.ts index 7f41a5ad7dcc2..a236983664ee5 100644 --- a/packages/x-tree-view/src/TreeItem/useTreeItemState.ts +++ b/packages/x-tree-view/src/TreeItem/useTreeItemState.ts @@ -1,9 +1,12 @@ import * as React from 'react'; +import { MuiCancellableEvent } from '../internals/models/MuiCancellableEvent'; import { useTreeViewContext } from '../internals/TreeViewProvider'; import { UseTreeViewSelectionSignature } from '../internals/plugins/useTreeViewSelection'; import { UseTreeViewExpansionSignature } from '../internals/plugins/useTreeViewExpansion'; import { UseTreeViewFocusSignature } from '../internals/plugins/useTreeViewFocus'; import { UseTreeViewItemsSignature } from '../internals/plugins/useTreeViewItems'; +import { UseTreeViewLabelSignature, useTreeViewLabel } from '../internals/plugins/useTreeViewLabel'; +import { hasPlugin } from '../internals/utils/plugins'; type UseTreeItemStateMinimalPlugins = readonly [ UseTreeViewSelectionSignature, @@ -12,7 +15,7 @@ type UseTreeItemStateMinimalPlugins = readonly [ UseTreeViewItemsSignature, ]; -type UseTreeItemStateOptionalPlugins = readonly []; +type UseTreeItemStateOptionalPlugins = readonly [UseTreeViewLabelSignature]; export function useTreeItemState(itemId: string) { const { @@ -27,6 +30,8 @@ export function useTreeItemState(itemId: string) { const focused = instance.isItemFocused(itemId); const selected = instance.isItemSelected(itemId); const disabled = instance.isItemDisabled(itemId); + const editing = instance?.isItemBeingEdited ? instance?.isItemBeingEdited(itemId) : false; + const editable = instance.isItemEditable ? instance.isItemEditable(itemId) : false; const handleExpansion = (event: React.MouseEvent) => { if (!disabled) { @@ -87,11 +92,56 @@ export function useTreeItemState(itemId: string) { } }; + const toggleItemEditing = () => { + if (!hasPlugin(instance, useTreeViewLabel)) { + return; + } + if (instance.isItemEditable(itemId)) { + if (instance.isItemBeingEdited(itemId)) { + instance.setEditedItemId(null); + } else { + instance.setEditedItemId(itemId); + } + } + }; + + const handleSaveItemLabel = ( + event: React.SyntheticEvent & MuiCancellableEvent, + label: string, + ) => { + if (!hasPlugin(instance, useTreeViewLabel)) { + return; + } + + // As a side effect of `instance.focusItem` called here and in `handleCancelItemLabelEditing` the `labelInput` is blurred + // The `onBlur` event is triggered, which calls `handleSaveItemLabel` again. + // To avoid creating an unwanted behavior we need to check if the item is being edited before calling `updateItemLabel` + // using `instance.isItemBeingEditedRef` instead of `instance.isItemBeingEdited` since the state is not yet updated in this point + if (instance.isItemBeingEditedRef(itemId)) { + instance.updateItemLabel(itemId, label); + toggleItemEditing(); + instance.focusItem(event, itemId); + } + }; + + const handleCancelItemLabelEditing = (event: React.SyntheticEvent) => { + if (!hasPlugin(instance, useTreeViewLabel)) { + return; + } + + if (instance.isItemBeingEditedRef(itemId)) { + toggleItemEditing(); + instance.focusItem(event, itemId); + } + }; + return { disabled, expanded, selected, focused, + editable, + editing, disableSelection, checkboxSelection, handleExpansion, @@ -100,5 +150,8 @@ export function useTreeItemState(itemId: string) { handleContentClick: onItemClick, preventSelection, expansionTrigger, + toggleItemEditing, + handleSaveItemLabel, + handleCancelItemLabelEditing, }; } diff --git a/packages/x-tree-view/src/TreeItem2/TreeItem2.tsx b/packages/x-tree-view/src/TreeItem2/TreeItem2.tsx index a1f72bfc089d5..c792574ce2294 100644 --- a/packages/x-tree-view/src/TreeItem2/TreeItem2.tsx +++ b/packages/x-tree-view/src/TreeItem2/TreeItem2.tsx @@ -13,12 +13,14 @@ import { TreeItem2Props, TreeItem2OwnerState } from './TreeItem2.types'; import { unstable_useTreeItem2 as useTreeItem2, UseTreeItem2ContentSlotOwnProps, + UseTreeItem2LabelSlotOwnProps, UseTreeItem2Status, } from '../useTreeItem2'; import { getTreeItemUtilityClass } from '../TreeItem'; import { TreeItem2Icon } from '../TreeItem2Icon'; import { TreeItem2DragAndDropOverlay } from '../TreeItem2DragAndDropOverlay'; import { TreeItem2Provider } from '../TreeItem2Provider'; +import { TreeItem2LabelInput } from '../TreeItem2LabelInput'; const useThemeProps = createUseThemeProps('MuiTreeItem2'); @@ -115,13 +117,23 @@ export const TreeItem2Label = styled('div', { name: 'MuiTreeItem2', slot: 'Label', overridesResolver: (props, styles) => styles.label, -})(({ theme }) => ({ + shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'editable', +})<{ editable?: boolean }>(({ theme }) => ({ width: '100%', boxSizing: 'border-box', // prevent width + padding to overflow // fixes overflow - see https://github.com/mui/material-ui/issues/27372 minWidth: 0, position: 'relative', + overflow: 'hidden', ...theme.typography.body1, + variants: [ + { + props: ({ editable }: UseTreeItem2LabelSlotOwnProps) => editable, + style: { + paddingLeft: '2px', + }, + }, + ], })); export const TreeItem2IconContainer = styled('div', { @@ -182,6 +194,8 @@ const useUtilityClasses = (ownerState: TreeItem2OwnerState) => { root: ['root'], content: ['content'], expanded: ['expanded'], + editing: ['editing'], + editable: ['editable'], selected: ['selected'], focused: ['focused'], disabled: ['disabled'], @@ -189,6 +203,7 @@ const useUtilityClasses = (ownerState: TreeItem2OwnerState) => { checkbox: ['checkbox'], label: ['label'], groupTransition: ['groupTransition'], + labelInput: ['labelInput'], dragAndDropOverlay: ['dragAndDropOverlay'], }; @@ -224,6 +239,7 @@ export const TreeItem2 = React.forwardRef(function TreeItem2( getCheckboxProps, getLabelProps, getGroupTransitionProps, + getLabelInputProps, getDragAndDropOverlayProps, status, } = useTreeItem2({ @@ -265,8 +281,11 @@ export const TreeItem2 = React.forwardRef(function TreeItem2( [classes.selected]: status.selected, [classes.focused]: status.focused, [classes.disabled]: status.disabled, + [classes.editing]: status.editing, + [classes.editable]: status.editable, }), }); + const IconContainer: React.ElementType = slots.iconContainer ?? TreeItem2IconContainer; const iconContainerProps = useSlotProps({ elementType: IconContainer, @@ -303,6 +322,15 @@ export const TreeItem2 = React.forwardRef(function TreeItem2( className: classes.groupTransition, }); + const LabelInput: React.ElementType = slots.labelInput ?? TreeItem2LabelInput; + const labelInputProps = useSlotProps({ + elementType: LabelInput, + getSlotProps: getLabelInputProps, + externalSlotProps: slotProps.labelInput, + ownerState: {}, + className: classes.labelInput, + }); + const DragAndDropOverlay: React.ElementType | undefined = slots.dragAndDropOverlay ?? TreeItem2DragAndDropOverlay; const dragAndDropOverlayProps = useSlotProps({ @@ -321,7 +349,7 @@ export const TreeItem2 = React.forwardRef(function TreeItem2( -