diff --git a/.github/workflows/cherry-pick-next-to-master.yml b/.github/workflows/cherry-pick-master-to-v7.yml similarity index 88% rename from .github/workflows/cherry-pick-next-to-master.yml rename to .github/workflows/cherry-pick-master-to-v7.yml index 614c58330d3a..6f980ac23c16 100644 --- a/.github/workflows/cherry-pick-next-to-master.yml +++ b/.github/workflows/cherry-pick-master-to-v7.yml @@ -1,17 +1,17 @@ -name: Cherry pick next to master +name: Cherry pick master to v7 on: pull_request_target: branches: - - next + - master types: ['closed'] permissions: {} jobs: - cherry_pick_to_master: + cherry_pick_to_v7: runs-on: ubuntu-latest - name: Cherry pick into master + name: Cherry pick into v7 permissions: pull-requests: write contents: write @@ -26,7 +26,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} uses: carloscastrojumo/github-cherry-pick-action@503773289f4a459069c832dc628826685b75b4b3 # v1.0.10 with: - branch: master + branch: v7.x body: 'Cherry-pick of #{old_pull_request_id}' cherry-pick-branch: ${{ format('cherry-pick-{0}', github.event.number) }} title: '{old_title} (@${{ github.event.pull_request.user.login }})' diff --git a/.github/workflows/cherry-pick-master-to-v6.yml b/.github/workflows/cherry-pick-v7-to-v6.yml similarity index 91% rename from .github/workflows/cherry-pick-master-to-v6.yml rename to .github/workflows/cherry-pick-v7-to-v6.yml index 1b594e5b8200..f7e48ffe9fbe 100644 --- a/.github/workflows/cherry-pick-master-to-v6.yml +++ b/.github/workflows/cherry-pick-v7-to-v6.yml @@ -1,17 +1,17 @@ -name: Cherry pick master to v6 +name: Cherry pick v7 to v6 on: pull_request_target: branches: - - master + - v7.x types: ['closed'] permissions: {} jobs: - cherry_pick_to_v6: + cherry_pick_v7_to_v6: runs-on: ubuntu-latest - name: Cherry pick into v6 + name: Cherry pick v7 into v6 permissions: pull-requests: write contents: write diff --git a/CHANGELOG.md b/CHANGELOG.md index cad4db17e886..2dac6eb4e0c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,95 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 7.21.0 + +_Oct 17, 2024_ + +We'd like to offer a big thanks to the 13 contributors who made this release possible. Here are some highlights ✨: + +- 💫 Added [`dataset` prop support for the Scatter Chart component](https://mui.com/x/react-charts/scatter/#using-a-dataset) +- 🐞 Bugfixes +- 📚 Documentation improvements + +Special thanks go out to the community contributors who have helped make this release possible: +@k-rajat19, @kalyan90, @rotembarsela, @wangkailang. +Following are all team members who have contributed to this release: +@arthurbalduini, @cherniavskii, @flaviendelangle, @JCQuintas, @LukasTy, @MBilalShafi, @arminmeh, @romgrk, @KenanYusuf, @oliviertassinari, @samuelsycamore. + + + +### Data Grid + +#### `@mui/x-data-grid@7.21.0` + +- [DataGrid] Fix `onRowSelectionModelChange` firing unnecessarily on initial render (#14909) @MBilalShafi +- [DataGrid] Fix `onRowSelectionModelChange` not being called after row is removed (#14972) @arminmeh +- [DataGrid] Fix pagination scrollbar issue on small zoom (#14911) @cherniavskii +- [DataGrid] Fix scroll jumping (#14929) @romgrk +- [DataGrid] Fix excessive white space at the end of the Data Grid (#14864) @kalyan90 + +#### `@mui/x-data-grid-pro@7.21.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.21.0`, plus: + +- [DataGridPro] Fix indeterminate checkbox state for server-side data (#14956) @MBilalShafi +- [DataGridPro] Fix scrolling performance when `rowHeight={undefined}` (#14983) @cherniavskii +- [DataGridPro] List view (#14393) @KenanYusuf @cherniavskii + +#### `@mui/x-data-grid-premium@7.21.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.21.0`. + +### Date and Time Pickers + +#### `@mui/x-date-pickers@7.21.0` + +- [pickers] Cleanup `PageUp` and `PageDown` event handlers on time components (#14928) @arthurbalduini +- [pickers] Create the new picker's `ownerState` object (#14889) @flaviendelangle +- [pickers] Fix `PickerValidDate` usage in the Date Range Picker Toolbar (#14925) @flaviendelangle + +#### `@mui/x-date-pickers-pro@7.21.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.21.0`. + +### Charts + +#### `@mui/x-charts@7.21.0` + +- [charts] Allow `dataset` to be used with the Scatter Chart (#14915) @JCQuintas +- [charts] Ensure `reduce motion` preference disables animation on page load (#14417) @JCQuintas + +#### `@mui/x-charts-pro@7.0.0-beta.5` [![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.21.0`. + +### Tree View + +#### `@mui/x-tree-view@7.21.0` + +- [TreeView] Fix `alpha()` usage with CSS variables (#14969) @wangkailang +- [TreeView] Fix usage of the `aria-selected` attribute (#14991) @flaviendelangle +- [TreeView] Fix hydration error (#15002) @flaviendelangle + +### `@mui/x-codemod@7.21.0` + +- [codemod] Add a new utility to rename imports (#14919) @flaviendelangle + +### Docs + +- [docs] Add recipe showing how to toggle detail panels on row click (#14666) @k-rajat19 +- [docs] Fix broken link to the validation section in the Data grid component (#14973) @arminmeh +- [docs] Update v5 migration codesandbox @oliviertassinari +- [docs] Enforce component style rules for the Tree View (#14963) @samuelsycamore + +### Core + +- [core] Fix shortcut with localization keyboard (#14220) @rotembarsela +- [core] Fix docs deploy command (#14920) @arminmeh +- [code-infra] Prepare some tests to work in `vitest/playwright` (#14926) @JCQuintas +- [test] Fix `AdapterDayjs` coverage calculation (#14957) @LukasTy +- [test] Fix split infinitive API convention use @oliviertassinari + ## 7.20.0 _Oct 11, 2024_ @@ -79,7 +168,7 @@ Same changes as in `@mui/x-charts@7.20.0`. - [docs] Add custom columns panel demo (#14825) @cherniavskii - [docs] Capitalize all instances of "Data Grid" (#14884) @samuelsycamore - [docs] Divide charts `tooltip` and `highlighting` pages (#14824) @JCQuintas -- [docs] Document the `TreeItem2` component and the `useTreeItem2` hook (#14551) @noraleonte +- [docs] Document the `` component and the `useTreeItem2` hook (#14551) @noraleonte - [docs] Fix column pinning for "Disable detail panel content scroll" section (#14854 and #14885) @kalyan90 - [docs] Fix detail panel demo not working well with pinned columns (#14883) @cherniavskii - [docs] New recipe of a read-only field (#14606) @flaviendelangle @@ -200,7 +289,7 @@ Same changes as in `@mui/x-charts@7.19.0`. - [code-infra] Remove custom playwright installation steps (#14728) @Janpot - [code-infra] Replace or remove all instances of `e` identifier (#14724) @samuelsycamore - [infra] Adds community contribution section to the changelog script (#14799) @michelengelen -- [infra] Fix line break in Stack Overflow message @oliviertassinari +- [infra] Fix line break in Stack Overflow message @oliviertassinari - [test] Fix `Escape` event firing event (#14797) @oliviertassinari ## 7.18.0 @@ -292,7 +381,7 @@ Same changes as in `@mui/x-charts@7.18.0`. - [core] Fix 301 link to Next.js and git diff @oliviertassinari - [core] Fix failing CI on `master` (#14644) @cherniavskii - [core] Fix `package.json` repository rule @oliviertassinari -- [core] MUI X repository moved to a new location @oliviertassinari +- [core] MUI X repository moved to a new location @oliviertassinari - [docs-infra] Strengthen CSP (#14581) @oliviertassinari - [license] Finish renaming of LicensingModel (#14615) @oliviertassinari @@ -316,7 +405,7 @@ We'd like to offer a big thanks to the 12 contributors who made this release pos - [DataGrid] Add "does not equal" and "does not contain" filter operators (#14489) @KenanYusuf - [DataGrid] Add demo to the "Custom columns" page that does not use generator (#13695) @arminmeh -- [DataGrid] Fix Voice Over reading the column name twice (#14482) @arminmeh +- [DataGrid] Fix VoiceOver reading the column name twice (#14482) @arminmeh - [DataGrid] Fix bug in CRUD example (#14513) @michelengelen - [DataGrid] Fix failing jsdom tests caused by `:has()` selectors (#14559) @KenanYusuf - [DataGrid] Refactor string operator filter functions (#14564) @KenanYusuf @@ -367,7 +456,7 @@ Same changes as in `@mui/x-charts@7.17.0`. ### Docs -- [docs] Add missing callout on "Imperative API" tree view sections (#14503) @flaviendelangle +- [docs] Add missing callout on "Imperative API" Tree View sections (#14503) @flaviendelangle - [docs] Fix broken redirection to MUI X v5 @oliviertassinari - [docs] Fix multiple `console.error` messages on `charts` docs (#14554) @JCQuintas - [docs] Fixed typo in Row Grouping recipes (#14549) @Miodini @@ -708,7 +797,7 @@ Same changes as in `@mui/x-charts@7.13.0`. - [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 +- [internals] Move utils needed for Tree View virtualization to shared package (#14202) @flaviendelangle ## 7.12.1 @@ -799,7 +888,7 @@ This expansion of the Pro plan comes with some adjustments to our pricing strate We'd like to offer a big thanks to the 12 contributors who made this release possible. Here are some highlights ✨: -- 🎁 Introduce [item reordering using drag and drop](https://mui.com/x/react-tree-view/rich-tree-view/ordering/) on the `RichTreeViewPro` component +- 🎁 Introduce [item reordering using drag and drop](https://mui.com/x/react-tree-view/rich-tree-view/ordering/) on the `` component Item reordering using drag and drop @@ -1136,7 +1225,7 @@ _Jul 5, 2024_ We'd like to offer a big thanks to the 7 contributors who made this release possible. Here are some highlights ✨: - 🔄 Add loading overlay variants, including a skeleton loader option to the Data Grid component. See [Loading overlay docs](https://mui.com/x/react-data-grid/overlays/#loading-overlay) for more details. -- 🌳 Add `selectItem` and `getItemDOMElement` methods to the TreeView component public API +- 🌳 Add `selectItem()` and `getItemDOMElement()` methods to the TreeView component public API - ⛏️ Make the `usePickersTranslations` hook public in the pickers component - 🐞 Bugfixes @@ -1181,7 +1270,7 @@ Same changes as in `@mui/x-date-pickers@7.9.0`. #### `@mui/x-tree-view@7.9.0` -- [TreeView] Add `selectItem` and `getItemDOMElement` methods to the public API (#13485) @flaviendelangle +- [TreeView] Add `selectItem()` and `getItemDOMElement()` methods to the public API (#13485) @flaviendelangle ### Docs @@ -1292,7 +1381,7 @@ Same changes as in `@mui/x-date-pickers@7.8.0`. - [core] Add eslint rule to restrict import from `../internals` root (#13633) @JCQuintas - [docs-infra] Sync `\_app` folder with monorepo (#13582) @Janpot -- [license] Allow usage of charts and tree view pro package for old premium licenses (#13619) @flaviendelangle +- [license] Allow usage of Charts and Tree View Pro package for old premium licenses (#13619) @flaviendelangle ## 7.7.1 @@ -1360,7 +1449,7 @@ Same changes as in `@mui/x-date-pickers@7.7.1`, plus: - [TreeView] Improve typing to support optional dependencies in plugins and in the item (#13523) @flaviendelangle - [TreeView] Move `useTreeViewId` to the core plugins (#13566) @flaviendelangle - [TreeView] Remove unused state from `useTreeViewId` (#13579) @flaviendelangle -- [TreeView] Support `itemId` with escaping characters when using `SimpleTreeView` (#13487) @oukunan +- [TreeView] Support `itemId` with escaping characters when using Simple Tree View (#13487) @oukunan ### Docs @@ -1440,7 +1529,7 @@ Same changes as in `@mui/x-date-pickers@7.7.0`. - [TreeView] Improve TypeScript for plugins (#13380) @flaviendelangle - [TreeView] Improve the typing of the cancelable events (#13152) @flaviendelangle - [TreeView] Prepare support for PigmentCSS (#13412) @flaviendelangle -- [TreeView] Refactor the tree view internals to prepare for headless API (#13311) @flaviendelangle +- [TreeView] Refactor the Tree View internals to prepare for headless API (#13311) @flaviendelangle ### Docs @@ -1455,7 +1544,7 @@ Same changes as in `@mui/x-date-pickers@7.7.0`. - [core] Add `eslint-plugin-react-compiler` experimental version and rules (#13415) @JCQuintas - [core] Minor setup cleanup (#13467) @LukasTy - [infra] Adjust CI setup (#13448) @LukasTy -- [test] Add tests for the custom slots of `TreeItem2` (#13314) @flaviendelangle +- [test] Add tests for the custom slots of `` (#13314) @flaviendelangle ## 7.6.2 @@ -1640,7 +1729,7 @@ _May 23, 2024_ We'd like to offer a big thanks to the 6 contributors who made this release possible. Here are some highlights ✨: -- 🧰 Improve tree view testing +- 🧰 Improve Tree View testing - 📊 Add `label` to be displayed in BarChart ### Data Grid @@ -1816,7 +1905,7 @@ Same changes as in `@mui/x-date-pickers@7.4.0`. #### `@mui/x-tree-view@7.4.0` -- [TreeView] Fix props propagation and theme entry in `TreeItem2` (#12889) @flaviendelangle +- [TreeView] Fix props propagation and theme entry in `` (#12889) @flaviendelangle ### Docs @@ -2126,7 +2215,7 @@ Same changes as in `@mui/x-date-pickers@7.2.0`, plus: ### Docs - [docs] Add `AxisFormatter` documentation for customizing tick/tooltip value formatting (#12700) @JCQuintas -- [docs] Add file explorer example to rich tree view customization docs (#12707) @noraleonte +- [docs] Add file explorer example to rich Tree View customization docs (#12707) @noraleonte - [docs] Do not use import of depth 3 in the doc (#12716) @flaviendelangle - [docs] Explain how to clip plots with composition (#12679) @alexfauquette - [docs] Fix typo in Data Grid v7 migration page (#12720) @bfaulk96 @@ -2202,7 +2291,7 @@ Same changes as in `@mui/x-date-pickers@7.1.1`, plus: #### `@mui/x-tree-view@7.1.1` - [TreeView] Add JSDoc to all `publicAPI` methods (#12649) @flaviendelangle -- [TreeView] Create `RichTreeViewPro` component (not released yet) (#12610) @flaviendelangle +- [TreeView] Create `` component (not released yet) (#12610) @flaviendelangle - [TreeView] Create Pro package (not released yet) (#12240) @flaviendelangle - [TreeView] Fix typo in errors (#12623) @alissa-tung - [TreeView] New API method: `setItemExpansion` (#12595) @flaviendelangle @@ -2222,13 +2311,13 @@ Same changes as in `@mui/x-date-pickers@7.1.1`, plus: - [docs] Move Data Grid interfaces to standard API page layout (#12016) @alexfauquette - [docs] Remove ` around @default values (#12158) @alexfauquette - [docs] Remove `day` from the default `dayOfWeekFormatter` function params (#12644) @LukasTy -- [docs] Use `TreeItem2` for icon expansion example on `RichTreeView` (#12563) @flaviendelangle +- [docs] Use `` for icon expansion example on `` (#12563) @flaviendelangle ### Core - [core] Add cherry-pick `master` to `v6` action (#12648) @LukasTy - [core] Fix typo in `@mui/x-tree-view-pro/themeAugmentation` (#12674) @flaviendelangle -- [core] Introduce `describeTreeView` to run test on `SimpleTreeView` and `RichTreeView`, using `TreeItem` and `TreeItem2` + migrate expansion tests (#12428) @flaviendelangle +- [core] Introduce `describeTreeView` to run test on `` and ``, using `` and `` + migrate expansion tests (#12428) @flaviendelangle - [core] Limit `test-types` CI step allowed memory (#12651) @LukasTy - [core] Remove explicit `express` package (#12602) @LukasTy - [core] Update to new embedded translations in the docs package (#12232) @Janpot @@ -2293,7 +2382,7 @@ Same changes as in `@mui/x-date-pickers@7.1.0`, plus: #### `@mui/x-tree-view@7.1.0` -- [TreeView] Do not use outdated version of the state to compute new label first char in `RichTreeView` (#12512) @flaviendelangle +- [TreeView] Do not use outdated version of the state to compute new label first char in Rich Tree View (#12512) @flaviendelangle ### Docs @@ -2420,7 +2509,7 @@ Same changes as in `@mui/x-date-pickers@7.0.0`, plus: #### Breaking changes -- The required `nodeId` prop used by the `TreeItem` has been renamed to `itemId` for consistency: +- The required `nodeId` prop used by `` has been renamed to `itemId` for consistency: ```diff @@ -2573,7 +2662,7 @@ The `onNodeFocus` callback has been renamed to `onItemFocus` for consistency: #### `@mui/x-tree-view@7.0.0-beta.7` - [TreeView] Clean the usage of the term "item" and "node" in API introduced during v7 (#12368) @noraleonte -- [TreeView] Introduce a new `TreeItem2` component and a new `useTreeItem2` hook (#11721) @flaviendelangle +- [TreeView] Introduce a new `` component and a new `useTreeItem2` hook (#11721) @flaviendelangle - [TreeView] Rename `onNodeFocus` to `onItemFocus` (#12419) @noraleonte ### Docs @@ -2631,9 +2720,9 @@ Same changes as in `@mui/x-data-grid-pro@7.0.0-beta.6`. #### Breaking changes -- The component used to animate the item children is now defined as a slot on the `TreeItem` component. +- The component used to animate the item children is now defined as a slot on the `` component. - If you were passing a `TransitionComponent` or `TransitionProps` to your `TreeItem` component, + If you were passing a `TransitionComponent` or `TransitionProps` to your `` component, you need to use the new `groupTransition` slot on this component: ```diff @@ -2649,7 +2738,7 @@ Same changes as in `@mui/x-data-grid-pro@7.0.0-beta.6`. ``` -- The `group` class of the `TreeItem` component has been renamed to `groupTransition` to match with its new slot name. +- The `group` class of the `` component has been renamed to `groupTransition` to match with its new slot name. ```diff const StyledTreeItem = styled(TreeItem)({ @@ -2663,14 +2752,14 @@ Same changes as in `@mui/x-data-grid-pro@7.0.0-beta.6`. #### `@mui/x-tree-view@7.0.0-beta.6` - [TreeView] Fix invalid nodes state when updating `props.items` (#12359) @flaviendelangle -- [TreeView] In the `RichTreeView`, do not use the item id as the HTML id attribute (#12319) @flaviendelangle +- [TreeView] In the Rich Tree View, do not use the item id as the HTML id attribute (#12319) @flaviendelangle - [TreeView] New instance and publicAPI method: `getItem` (#12251) @flaviendelangle - [TreeView] Replace `TransitionComponent` and `TransitionProps` with a `groupTransition` slot (#12336) @flaviendelangle ### Docs - [docs] Add a note about `z-index` usage in SVG (#12337) @alexfauquette -- [docs] `RichTreeView` customization docs (#12231) @noraleonte +- [docs] Rich Tree View customization docs (#12231) @noraleonte ### Core @@ -2813,7 +2902,8 @@ Same changes as in `@mui/x-data-grid-pro@7.0.0-beta.4`. ``` - The headless field hooks (e.g.: `useDateField`) now returns a new prop called `enableAccessibleFieldDOMStructure`. - This property is utilized to determine whether the anticipated UI is constructed using an accessible DOM structure. Learn more about this new [accessible DOM structure](/x/react-date-pickers/fields/#accessible-dom-structure). + This property is utilized to determine whether the anticipated UI is constructed using an accessible DOM structure. + Learn more about this new accessible DOM structure in the [v8 migration guide](https://next.mui.com/x/migration/migration-pickers-v7/#new-dom-structure-for-the-field). When building a custom UI, you are most-likely only supporting one DOM structure, so you can remove `enableAccessibleFieldDOMStructure` before it is passed to the DOM: @@ -3323,7 +3413,7 @@ Same changes as in `@mui/x-date-pickers@7.0.0-beta.0`, plus: ### Docs -- [docs] Add `contextValue` to the headless tree view doc (#11705) @flaviendelangle +- [docs] Add `contextValue` to the headless Tree View doc (#11705) @flaviendelangle - [docs] Add section for the `disableSelection` prop (#11821) @flaviendelangle - [docs] Fix brand name non-breaking space (#11758) @oliviertassinari - [docs] Fix typo in Data Grid components page (#11775) @flaviendelangle @@ -3346,7 +3436,7 @@ We'd like to offer a big thanks to the 11 contributors who made this release pos - 🎁 The Data Grid headers have been refactored to bring immense improvements to scrolling, state management, and overall performance of the grid. - ⚙️ The Data Grid disabled column-specific features like filtering, sorting, grouping, etc. could now be accessed programmatically. See the related [docs](https://next.mui.com/x/react-data-grid/api-object/#access-the-disabled-column-features) section. -- 🚀 Uplift the `SimpleTreeView` customization examples (#11424) @noraleonte +- 🚀 Uplift the Simple Tree View customization examples (#11424) @noraleonte - 🌍 Add Croatian (hr-HR), Portuguese (pt-PT), and Chinese (Hong Kong) (zh-HK) locales (#11668) on the Data Grid @BCaspari - 🐞 Bugfixes - 💔 Bump `@mui/material` peer dependency for all packages (#11692) @LukasTy @@ -3557,7 +3647,7 @@ Same changes as in `@mui/x-date-pickers@7.0.0-alpha.9`. ``` - The `useTreeItem` hook has been renamed `useTreeItemState`. - This will help create a new headless version of the `TreeItem` component based on a future `useTreeItem` hook. + This will help create a new headless version of the Tree Item component based on a future `useTreeItem` hook. ```diff -import { TreeItem, useTreeItem } from '@mui/x-tree-view/TreeItem'; @@ -3657,13 +3747,13 @@ Same changes as in `@mui/x-date-pickers@7.0.0-alpha.9`. - [docs] Cleanup and fix Pickers Playground styling (#11700) @LukasTy - [docs] First draft of the Tree View custom plugin doc (#11564) @flaviendelangle - [docs] Fix Pickers migration syntax and diffs (#11695) @LukasTy -- [docs] Fix generated tree view API docs (#11737) @LukasTy +- [docs] Fix generated Tree View API docs (#11737) @LukasTy - [docs] Generate docs for Tree View slots (#11730) @flaviendelangle - [docs] Improve codemod for v7 (#11650) @oliviertassinari - [docs] Improve Data Grid `pageSizeOptions` prop documentation (#11682) @oliviertassinari - [docs] Parse markdown on API docs demo titles (#11728) @LukasTy - [docs] Remove the description from the `className` prop (#11693) @oliviertassinari -- [docs] Uplift `SimpleTreeView` customization examples (#11424) @noraleonte +- [docs] Uplift Simple Tree View customization examples (#11424) @noraleonte - [docs] Uplift the Date Pickers playground (#11555) @danilo-leal ### Core @@ -3740,8 +3830,8 @@ Same changes as in `@mui/x-date-pickers@7.0.0-alpha.8`. ### Tree View / `@mui/x-tree-view@7.0.0-alpha.8` -- [tree view] Cleanup `onKeyDown` handler (#11481) @flaviendelangle -- [tree view] Define the parameters used by each plugin to avoid listing them in each component (#11473) @flaviendelangle +- [TreeView] Cleanup `onKeyDown` handler (#11481) @flaviendelangle +- [TreeView] Define the parameters used by each plugin to avoid listing them in each component (#11473) @flaviendelangle ### Docs @@ -3768,7 +3858,7 @@ We'd like to offer a big thanks to the 7 contributors who made this release poss - 🎁 New component to create a Tree View from a structured data source: - You can now directly pass your data to the `RichTreeView` component instead of manually converting it into JSX `TreeItem` components: + You can now directly pass your data to the `` component instead of manually converting it into JSX `` components: ```tsx const ITEMS = [ @@ -5099,7 +5189,7 @@ Here is an example of the renaming for the `` component. ### Core -- [core] Adds migration docs for charts, pickers and tree view (#10926) @michelengelen +- [core] Adds migration docs for Charts, Pickers, and Tree View (#10926) @michelengelen - [core] Bump monorepo (#10959) @LukasTy - [core] Changed prettier branch value to next (#10917) @michelengelen - [core] Fix GitHub title tag consistency @oliviertassinari diff --git a/changelogOld/CHANGELOG.v6.md b/changelogOld/CHANGELOG.v6.md index 0401e317fe08..985e7aba0905 100644 --- a/changelogOld/CHANGELOG.v6.md +++ b/changelogOld/CHANGELOG.v6.md @@ -1485,7 +1485,7 @@ Same changes as in `@mui/x-date-pickers@6.13.0`, plus: - [docs] Fix charts demo using too deep import (#10263) @LukasTy - [docs] Fix `e.g.` typo @oliviertassinari - [docs] Fix npm package indentation @oliviertassinari -- [docs] Fix typo in tree view docs @oliviertassinari +- [docs] Fix typo in Tree View docs @oliviertassinari - [docs] Improve the week picker example (#8257) @flaviendelangle - [docs] Include code links in the Data Grid demo (#10219) @cherniavskii - [docs] Polish page for SEO (#10216) @oliviertassinari @@ -1546,7 +1546,7 @@ Same changes as in `@mui/x-date-pickers@6.12.1`. - [docs] Add `DemoContainer` and `DemoItem` JSDoc (#10186) @LukasTy - [docs] Add link to `custom layout` page (#10184) @LukasTy -- [docs] Add tree view nav item (#10181) @LukasTy +- [docs] Add Tree View nav item (#10181) @LukasTy - [docs] Fix wrong chart tooltip reference (#10169) @oliviertassinari - [docs] Improve chart SEO (#10170) @oliviertassinari - [docs] Precise expired license key condition (#10165) @oliviertassinari @@ -1748,7 +1748,7 @@ _Aug 4, 2023_ We'd like to offer a big thanks to the 12 contributors who made this release possible. Here are some highlights ✨: -- ⌚️ Move the tree view component from `@mui/lab` package +- ⌚️ Move the Tree View component from `@mui/lab` package The `` component has been moved to the MUI X repository. It is now accessible from its own package: `@mui/x-tree-view`. @@ -1804,7 +1804,7 @@ Same changes as in `@mui/x-date-pickers@6.11.0`. ### Tree View / `@mui/x-tree-view@6.0.0-alpha.0` - [TreeView] Add missing exported types (#9862) @flaviendelangle -- [TreeView] Add tree view to changelog generator script (#9903) @MBilalShafi +- [TreeView] Add Tree View to changelog generator script (#9903) @MBilalShafi - [TreeView] Create the package on the X repository (#9798) @flaviendelangle - [TreeView] Improve props typing (#9855) @flaviendelangle diff --git a/codecov.yml b/codecov.yml index 4a05503c5b9e..b4af65812615 100644 --- a/codecov.yml +++ b/codecov.yml @@ -10,15 +10,15 @@ coverage: adapters: target: 100% paths: - - 'packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts' - - 'packages/x-date-pickers/src/AdapterDateFnsV3/AdapterDateFnsV3.ts' - - 'packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts' - - 'packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.ts' - - 'packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts' - - 'packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts' - - 'packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts' - - 'packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.ts' - - 'packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.ts' + - packages/x-date-pickers/src/AdapterDateFns/AdapterDateFns.ts + - packages/x-date-pickers/src/AdapterDateFnsV3/AdapterDateFnsV3.ts + - packages/x-date-pickers/src/AdapterDateFnsJalali/AdapterDateFnsJalali.ts + - packages/x-date-pickers/src/AdapterDateFnsJalaliV3/AdapterDateFnsJalaliV3.ts + - packages/x-date-pickers/src/AdapterDayjs/AdapterDayjs.ts + - packages/x-date-pickers/src/AdapterLuxon/AdapterLuxon.ts + - packages/x-date-pickers/src/AdapterMoment/AdapterMoment.ts + - packages/x-date-pickers/src/AdapterMomentHijri/AdapterMomentHijri.ts + - packages/x-date-pickers/src/AdapterMomentJalaali/AdapterMomentJalaali.ts patch: off comment: false diff --git a/docs/.link-check-errors.txt b/docs/.link-check-errors.txt index 6ecc503cc78a..0438fa82a091 100644 --- a/docs/.link-check-errors.txt +++ b/docs/.link-check-errors.txt @@ -1,2 +1,3 @@ Broken links found by `docs:link-check` that exist: +- https://mui.com/x/react-date-pickers/fields/#accessible-dom-structure diff --git a/docs/data/charts/areas-demo/areas-demo.md b/docs/data/charts/areas-demo/areas-demo.md index 92432f02ad72..d784d0996c93 100644 --- a/docs/data/charts/areas-demo/areas-demo.md +++ b/docs/data/charts/areas-demo/areas-demo.md @@ -36,7 +36,7 @@ You can pass this gradient definition as a children of the `` and u To do so you will need to use the [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient) and [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop) SVG elements. The first part is to get the SVG total height. -Which can be done with the `useDrawingArea` hook. +Which can be done with the `useDrawingArea()` hook. It's useful to define the `` as a vector that goes from the top to the bottom of our SVG container. Then to define where the gradient should switch from one color to another, you can use the `useYScale` hook to get the y coordinate of value 0. diff --git a/docs/data/charts/axis/axis.md b/docs/data/charts/axis/axis.md index e415596c2583..e8edd2836409 100644 --- a/docs/data/charts/axis/axis.md +++ b/docs/data/charts/axis/axis.md @@ -56,7 +56,7 @@ Which expects an array of value coherent with the `scaleType`: Some series types also require specific axis attributes: - line plots require an `xAxis` to have `data` provided -- bar plots require an `xAxis` with `scaleType='band'` and some `data` provided. +- bar plots require an `xAxis` with `scaleType="band"` and some `data` provided. ### Axis formatter diff --git a/docs/data/charts/bars/bars.md b/docs/data/charts/bars/bars.md index 11c42dd85e93..606f2895de13 100644 --- a/docs/data/charts/bars/bars.md +++ b/docs/data/charts/bars/bars.md @@ -100,7 +100,7 @@ Learn more about the `colorMap` properties in the [Styling docs](/x/react-charts {{"demo": "ColorScale.js"}} -### Border Radius +### Border radius To give your bar chart rounded corners, you can change the value of the `borderRadius` property on the [BarChart](/x/api/charts/bar-chart/#bar-chart-prop-slots). @@ -117,7 +117,7 @@ Or you can pass `'value'` to display the raw value of the bar. {{"demo": "BarLabel.js"}} -### Custom Labels +### Custom labels You can display, change, or hide labels based on conditional logic. To do so, provide a function to the `barLabel`. @@ -174,7 +174,7 @@ import ChartsOnAxisClickHandler from '@mui/x-charts/ChartsOnAxisClickHandler'; To skip animation at the creation and update of your chart, you can use the `skipAnimation` prop. When set to `true` it skips animation powered by `@react-spring/web`. -Charts containers already use the `useReducedMotion` from `@react-spring/web` to skip animation [according to user preferences](https://react-spring.dev/docs/utilities/use-reduced-motion#why-is-it-important). +Charts containers already use the `useReducedMotion()` from `@react-spring/web` to skip animation [according to user preferences](https://react-spring.dev/docs/utilities/use-reduced-motion#why-is-it-important). ```jsx // For a single component chart diff --git a/docs/data/charts/components/components.md b/docs/data/charts/components/components.md index 5cfff2f9f6d9..be2f67d72870 100644 --- a/docs/data/charts/components/components.md +++ b/docs/data/charts/components/components.md @@ -20,7 +20,7 @@ Charts dimensions are defined by a few props: The term **drawing area** refers to the space available to plot data (scatter points, lines, or pie arcs). The `margin` is used to leave some space for extra elements, such as the axes, the legend, or the title. -You can use the `useDrawingArea` hook in the charts subcomponents to get the coordinates of the **drawing area**. +You can use the `useDrawingArea()` hook in the charts subcomponents to get the coordinates of the **drawing area**. ```jsx import { useDrawingArea } from '@mui/x-charts'; diff --git a/docs/data/charts/gauge/gauge.md b/docs/data/charts/gauge/gauge.md index 30c5df560756..465560c8527d 100644 --- a/docs/data/charts/gauge/gauge.md +++ b/docs/data/charts/gauge/gauge.md @@ -101,7 +101,7 @@ import { ### Creating your components -To create your own components, use the `useGaugeState` hook which provides all you need about the gauge configuration: +To create your own components, use the `useGaugeState()` hook which provides all you need about the gauge configuration: - information about the value: `value`, `valueMin`, `valueMax` - information to plot the arc: `startAngle`, `endAngle`, `outerRadius`, `innerRadius`, `cornerRadius`, `cx`, and `cy` diff --git a/docs/data/charts/getting-started/getting-started.md b/docs/data/charts/getting-started/getting-started.md index 6c6057abfc00..83dbff7001c7 100644 --- a/docs/data/charts/getting-started/getting-started.md +++ b/docs/data/charts/getting-started/getting-started.md @@ -4,20 +4,20 @@ githubLabel: 'component: charts' packageName: '@mui/x-charts' --- -# Charts - Getting Started +# Charts - Getting started -

Get started with the MUI X Charts components. Install the package, configure your application, and start using the components.

+

Install the MUI X Charts package to start building React data visualization components.

## Installation -Using your favorite package manager, install `@mui/x-charts-pro` for the commercial version, or `@mui/x-charts` for the free community version. +Run one of the following commands to install the free Community version or the paid Pro version of the MUI X Charts: {{"component": "modules/components/ChartsInstallationInstructions.js"}} -The Charts package has a peer dependency on `@mui/material`. -If you are not already using it in your project, you can install it with: +The Charts packages have a peer dependency on `@mui/material`. +If you're not already using it, install it with the following command: @@ -37,7 +37,7 @@ yarn add @mui/material @emotion/react @emotion/styled -Please note that [react](https://www.npmjs.com/package/react) and [react-dom](https://www.npmjs.com/package/react-dom) are peer dependencies too: +[`react`](https://www.npmjs.com/package/react) and [`react-dom`](https://www.npmjs.com/package/react-dom) are also peer dependencies: ```json "peerDependencies": { @@ -46,85 +46,66 @@ Please note that [react](https://www.npmjs.com/package/react) and [react-dom](ht }, ``` -### Style engine - -Material UI is using [Emotion](https://emotion.sh/docs/introduction) as a styling engine by default. If you want to use [`styled-components`](https://styled-components.com/) instead, run: - - -```bash npm -npm install @mui/styled-engine-sc styled-components -``` - -```bash pnpm -pnpm add @mui/styled-engine-sc styled-components -``` - -```bash yarn -yarn add @mui/styled-engine-sc styled-components -``` - - - -Take a look at the [Styled engine guide](/material-ui/integrations/styled-components/) for more information about how to configure `styled-components` as the style engine. - ### Usage with D3 To help folks using CommonJS, the `@mui/x-charts` package uses a vendored package named `@mui/x-charts-vendor` to access D3 libraries. +You can import D3 functions from `@mui/x-charts-vendor/d3-color`. -If you need some D3 functions, you can import them with `@mui/x-charts-vendor/d3-color`. +## Rendering Charts -## Displaying charts +MUI X Charts can be rendered as _self-contained_ or _composable_ components. +[Self-contained components](#self-contained-charts) are simpler to get started with and are recommended for most common use cases; more complex visualization (such as combining Bar and Line Charts on a single plot) requires [custom composition](#composable-charts). -A Chart can be rendered in one of two ways: as a single component, or by composing subcomponents. +### Self-contained Charts -### Single charts +Self-contained Chart components are imported and rendered as a single React component (such as `` or ``) which contains all of the necessary subcomponents. -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. +These components require a `series` prop describing the data to render, as well as a numerical value (rendered in pixels) for the `height` prop. +The `width` prop is optional; if no value is provided, the Charts expand to fill the available space. {{"demo": "SimpleCharts.js"}} -### Composed charts +### Composable Charts -To combine different Charts, like Lines with Bars, you can use composition with the `ChartContainer` wrapper. +More complex use cases require composition of the necessary subcomponents inside of a Chart Container wrapper. +Subcomponents include: -Inside this wrapper, render either axis components, such as `XAxis` and `YAxis`, or any plot component like `BarPlot`, `LinePlot`, `AreaPlot`, and `ScatterPlot`. +- Axis components – to define the X and Y axes +- Plot components – to create Bars, Lines, or any other Chart type +- Auxillary components - to add Tooltips, Highlights, and more +- Utilities - such as classes and types -Visit the [Composition page](/x/react-charts/composition/) for more details. +See the [Charts composition documentation](/x/react-charts/composition/) for complete details. -{{"demo": "Combining.js"}} +The demo below shows how to use composition to create a custom Chart that combines a Bar and a Line Chart on a single plot: -### Positions +{{"demo": "Combining.js"}} -Charts are composed of two main areas. -The SVG defined by its `width` and `height` delimits the available space. +## Chart layouts -Within this SVG, a dedicated "drawing area" (aka "plot area") serves as the canvas for data representation. -Here, elements like lines, bars, and areas visually depict the information. -It's controlled by the `margin = {top, bottom, left, right}` object defining the margin between the SVG and the drawing area. +The layout of a Chart is defined by two main spaces: the plot area, and the outer margins. -The space left by margins can display axes, titles, a legend, or any other additional information. +The `width` and `height` props define the dimensions of the SVG which is the root of the chart. +Within this SVG, the plot area (or drawing area) serves as the canvas for data visualization, where the lines, bars or other visual elements are rendered. +The size of the plot area is determined by the `margin = {top, bottom, left, right}` object which defines its outer margins inside the SVG. +The outer margin space is where information like axes, titles, and legends are displayed. -For more information about the position configuration, visit the [styling page](/x/react-charts/styling/#styling). +See the [Styling documentation](/x/react-charts/styling/#placement) for complete details. ## Axis management -MUI X Charts have a flexible approach to axis management, supporting multiple-axis charts with any combination of scales and ranges. - -Visit the [Axis page](/x/react-charts/axis/) for more details. - -## Styling +MUI X Charts take a flexible approach to axis management, with support for multiple axes and any combination of scales and ranges. -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. +See the [Axis documentation](/x/react-charts/axis/) for complete 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. +To benefit from [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 must import the following types. +These types use module augmentation to extend the default theme structure. ```tsx +// only one import is necessary, +// from the version you're currently using. import type {} from '@mui/x-charts/themeAugmentation'; import type {} from '@mui/x-charts-pro/themeAugmentation'; @@ -140,8 +121,3 @@ const theme = createTheme({ }, }); ``` - -:::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/lines.md b/docs/data/charts/lines/lines.md index be651d4dd57a..de9a55dfb928 100644 --- a/docs/data/charts/lines/lines.md +++ b/docs/data/charts/lines/lines.md @@ -235,7 +235,7 @@ sx={{ To skip animation at the creation and update of your chart, you can use the `skipAnimation` prop. When set to `true` it skips animation powered by `@react-spring/web`. -Charts containers already use the `useReducedMotion` from `@react-spring/web` to skip animation [according to user preferences](https://react-spring.dev/docs/utilities/use-reduced-motion#why-is-it-important). +Charts containers already use the `useReducedMotion()` from `@react-spring/web` to skip animation [according to user preferences](https://react-spring.dev/docs/utilities/use-reduced-motion#why-is-it-important). :::warning If you support interactive ways to add or remove series from your chart, you have to provide the series' id. diff --git a/docs/data/charts/overview/overview.md b/docs/data/charts/overview/overview.md index ef86f4a0793f..92f1d289714d 100644 --- a/docs/data/charts/overview/overview.md +++ b/docs/data/charts/overview/overview.md @@ -7,34 +7,33 @@ packageName: '@mui/x-charts' # MUI X Charts -

A fast and extendable library of react chart components for data visualization.

+

A collection of React chart components for data visualization.

{{"component": "@mui/docs/ComponentLinkHeader", "design": false}} ## Overview -The `@mui/x-charts` is an MIT library for rendering charts relying on [D3.js](https://d3js.org/) for data manipulation and SVG for rendering. -And, like other MUI X components, charts are production-ready components that integrate smoothly into your app. +MUI X Charts is a library of production-ready components for rendering charts with React. +It uses [D3.js](https://d3js.org/) for data manipulation and SVGs for rendering. -With a high level of customization, MUI X Charts provides three levels of customization layers: **single components** with great defaults, extensive **configuration props**, and **subcomponents** for flexible composition. -Additionally, you can also use all the [MUI System](https://mui.com/system/getting-started/) tools, such as the theme override or the `sx` prop. +The components provide a high level of customization, with beautiful defaults as well as extensive configuration props and flexible composition options. +They also have access to all [MUI System](https://mui.com/system/getting-started/) tools such as theme overrides and the `sx` prop. {{"demo": "ChartsOverviewDemo.js", "defaultCodeOpen": true}} -## Using the documentation +## All MUI X Charts -The MUI X Charts documentation has a slightly different structure than other MUI X components. -Instead of having a long page for each, the pages are divided in two: +{{"component": "modules/components/ChartComponentsGrid.js"}} -1. General description of the built-in features the component provides. -2. A set of examples demonstrating the component with customizations. +## Using this documentation -## All MUI X Charts components +Each Chart type has two accompanying documents: -{{"component": "modules/components/ChartComponentsGrid.js"}} +1. **Overview** – a general description of built-in features +2. **Demo** – a collection of custom examples ## Supported features -The features that are shared across multiple Charts components, such as axes and legends, are also documented on separate pages. +Features shared across Chart components such as axes and legends are described in standalone documents: {{"component": "modules/components/ChartFeaturesGrid.js"}} diff --git a/docs/data/charts/pie/pie.md b/docs/data/charts/pie/pie.md index 047a4cc576e2..eda5601ffc78 100644 --- a/docs/data/charts/pie/pie.md +++ b/docs/data/charts/pie/pie.md @@ -110,7 +110,7 @@ const onItemClick = ( To skip animation at the creation and update of your chart you can use the `skipAnimation` prop. When set to `true` it skips animation powered by `@react-spring/web`. -Charts containers already use the `useReducedMotion` from `@react-spring/web` to skip animation [according to user preferences](https://react-spring.dev/docs/utilities/use-reduced-motion#why-is-it-important). +Charts containers already use the `useReducedMotion()` from `@react-spring/web` to skip animation [according to user preferences](https://react-spring.dev/docs/utilities/use-reduced-motion#why-is-it-important). ```jsx // For a single component chart diff --git a/docs/data/charts/tooltip/CustomAxisTooltip.js b/docs/data/charts/tooltip/CustomAxisTooltip.js index bb893b9e83b1..c8b63c7f6cec 100644 --- a/docs/data/charts/tooltip/CustomAxisTooltip.js +++ b/docs/data/charts/tooltip/CustomAxisTooltip.js @@ -3,22 +3,80 @@ import NoSsr from '@mui/material/NoSsr'; import Popper from '@mui/material/Popper'; import Paper from '@mui/material/Paper'; import Typography from '@mui/material/Typography'; -import { useAxisTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import { useAxisTooltip } from '@mui/x-charts/ChartsTooltip'; +import { useSvgRef } from '@mui/x-charts/hooks'; import { generateVirtualElement } from './generateVirtualElement'; +function usePointer() { + const svgRef = useSvgRef(); + const popperRef = React.useRef(null); + const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + const handleMove = (event) => { + virtualElement.current = generateVirtualElement({ + x: event.clientX, + y: event.clientY, + }); + popperRef.current?.update(); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef]); + + return { ...pointer, popperRef, anchorEl: virtualElement.current }; +} + export function CustomAxisTooltip() { const tooltipData = useAxisTooltip(); - const mousePosition = useMouseTracker(); // Track the mouse position on chart. + const { isActive, isMousePointer, pointerHeight, popperRef, anchorEl } = + usePointer(); - if (!tooltipData || !mousePosition) { + if (!tooltipData || !isActive) { // No data to display return null; } // The pointer type can be used to have different behavior based on pointer type. - const isMousePointer = mousePosition?.pointerType === 'mouse'; // Adapt the tooltip offset to the size of the pointer. - const yOffset = isMousePointer ? 0 : 40 - mousePosition.height; + const yOffset = isMousePointer ? 0 : 40 - pointerHeight; return ( @@ -29,7 +87,8 @@ export function CustomAxisTooltip() { }} open placement={isMousePointer ? 'top-end' : 'top'} - anchorEl={generateVirtualElement(mousePosition)} + anchorEl={anchorEl} + popperRef={popperRef} modifiers={[ { name: 'offset', @@ -81,7 +140,7 @@ export function CustomAxisTooltip() { {tooltipData.seriesItems.map((seriesItem) => ( - +
{ + const svgRef = useSvgRef(); + const popperRef: PopperProps['popperRef'] = React.useRef(null); + const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event: PointerEvent) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event: PointerEvent) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + const handleMove = (event: PointerEvent) => { + virtualElement.current = generateVirtualElement({ + x: event.clientX, + y: event.clientY, + }); + popperRef.current?.update(); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef]); + + return { ...pointer, popperRef, anchorEl: virtualElement.current }; +} + export function CustomAxisTooltip() { const tooltipData = useAxisTooltip(); - const mousePosition = useMouseTracker(); // Track the mouse position on chart. + const { isActive, isMousePointer, pointerHeight, popperRef, anchorEl } = + usePointer(); - if (!tooltipData || !mousePosition) { + if (!tooltipData || !isActive) { // No data to display return null; } // The pointer type can be used to have different behavior based on pointer type. - const isMousePointer = mousePosition?.pointerType === 'mouse'; // Adapt the tooltip offset to the size of the pointer. - const yOffset = isMousePointer ? 0 : 40 - mousePosition.height; + const yOffset = isMousePointer ? 0 : 40 - pointerHeight; return ( @@ -29,7 +93,8 @@ export function CustomAxisTooltip() { }} open placement={isMousePointer ? 'top-end' : 'top'} - anchorEl={generateVirtualElement(mousePosition)} + anchorEl={anchorEl} + popperRef={popperRef} modifiers={[ { name: 'offset', @@ -81,7 +146,7 @@ export function CustomAxisTooltip() { {tooltipData.seriesItems.map((seriesItem) => ( - +
{ + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + const handleMove = (event) => { + virtualElement.current = generateVirtualElement({ + x: event.clientX, + y: event.clientY, + }); + popperRef.current?.update(); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef]); + + return { ...pointer, popperRef, anchorEl: virtualElement.current }; +} + export function CustomItemTooltip() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); // Track the mouse position on chart. + const { isActive, isMousePointer, pointerHeight, popperRef, anchorEl } = + usePointer(); - if (!tooltipData || !mousePosition) { + if (!tooltipData || !isActive) { // No data to display return null; } - // The pointer type can be used to have different behavior based on pointer type. - const isMousePointer = mousePosition?.pointerType === 'mouse'; // Adapt the tooltip offset to the size of the pointer. - const yOffset = isMousePointer ? 0 : 40 - mousePosition.height; + const yOffset = isMousePointer ? 0 : 40 - pointerHeight; return ( @@ -30,7 +87,8 @@ export function CustomItemTooltip() { }} open placement={isMousePointer ? 'top-end' : 'top'} - anchorEl={generateVirtualElement(mousePosition)} + anchorEl={anchorEl} + popperRef={popperRef} modifiers={[ { name: 'offset', diff --git a/docs/data/charts/tooltip/CustomItemTooltip.tsx b/docs/data/charts/tooltip/CustomItemTooltip.tsx index fcaf28469db1..926b4fd3179d 100644 --- a/docs/data/charts/tooltip/CustomItemTooltip.tsx +++ b/docs/data/charts/tooltip/CustomItemTooltip.tsx @@ -1,25 +1,88 @@ import * as React from 'react'; import NoSsr from '@mui/material/NoSsr'; -import Popper from '@mui/material/Popper'; +import Popper, { PopperProps } from '@mui/material/Popper'; import Paper from '@mui/material/Paper'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; +import { useSvgRef } from '@mui/x-charts/hooks'; import { generateVirtualElement } from './generateVirtualElement'; +type PointerState = { + isActive: boolean; + isMousePointer: boolean; + pointerHeight: number; +}; + +function usePointer(): PointerState & Pick { + const svgRef = useSvgRef(); + const popperRef: PopperProps['popperRef'] = React.useRef(null); + const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event: PointerEvent) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event: PointerEvent) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + const handleMove = (event: PointerEvent) => { + virtualElement.current = generateVirtualElement({ + x: event.clientX, + y: event.clientY, + }); + popperRef.current?.update(); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef]); + + return { ...pointer, popperRef, anchorEl: virtualElement.current }; +} + export function CustomItemTooltip() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); // Track the mouse position on chart. + const { isActive, isMousePointer, pointerHeight, popperRef, anchorEl } = + usePointer(); - if (!tooltipData || !mousePosition) { + if (!tooltipData || !isActive) { // No data to display return null; } - // The pointer type can be used to have different behavior based on pointer type. - const isMousePointer = mousePosition?.pointerType === 'mouse'; // Adapt the tooltip offset to the size of the pointer. - const yOffset = isMousePointer ? 0 : 40 - mousePosition.height; + const yOffset = isMousePointer ? 0 : 40 - pointerHeight; return ( @@ -30,7 +93,8 @@ export function CustomItemTooltip() { }} open placement={isMousePointer ? 'top-end' : 'top'} - anchorEl={generateVirtualElement(mousePosition)} + anchorEl={anchorEl} + popperRef={popperRef} modifiers={[ { name: 'offset', diff --git a/docs/data/charts/tooltip/ItemTooltip.js b/docs/data/charts/tooltip/ItemTooltip.js index 68fbab1d77ae..cb2a90370e5e 100644 --- a/docs/data/charts/tooltip/ItemTooltip.js +++ b/docs/data/charts/tooltip/ItemTooltip.js @@ -1,23 +1,80 @@ import * as React from 'react'; import NoSsr from '@mui/material/NoSsr'; import Popper from '@mui/material/Popper'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; +import { useSvgRef } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; import { generateVirtualElement } from './generateVirtualElement'; +function usePointer() { + const svgRef = useSvgRef(); + const popperRef = React.useRef(null); + const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + const handleMove = (event) => { + virtualElement.current = generateVirtualElement({ + x: event.clientX, + y: event.clientY, + }); + popperRef.current?.update(); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef]); + + return { ...pointer, popperRef, anchorEl: virtualElement.current }; +} + export function ItemTooltip() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); // Track the mouse position on chart. + const { isActive, isMousePointer, pointerHeight, popperRef, anchorEl } = + usePointer(); - if (!tooltipData || !mousePosition) { + if (!tooltipData || !isActive) { // No data to display return null; } - // The pointer type can be used to have different behavior based on pointer type. - const isMousePointer = mousePosition?.pointerType === 'mouse'; // Adapt the tooltip offset to the size of the pointer. - const yOffset = isMousePointer ? 0 : 40 - mousePosition.height; + const yOffset = isMousePointer ? 0 : 40 - pointerHeight; return ( @@ -28,7 +85,8 @@ export function ItemTooltip() { }} open placement={isMousePointer ? 'top-end' : 'top'} - anchorEl={generateVirtualElement(mousePosition)} + anchorEl={anchorEl} + popperRef={popperRef} modifiers={[ { name: 'offset', diff --git a/docs/data/charts/tooltip/ItemTooltip.tsx b/docs/data/charts/tooltip/ItemTooltip.tsx index 68fbab1d77ae..5069ad32df3d 100644 --- a/docs/data/charts/tooltip/ItemTooltip.tsx +++ b/docs/data/charts/tooltip/ItemTooltip.tsx @@ -1,23 +1,86 @@ import * as React from 'react'; import NoSsr from '@mui/material/NoSsr'; -import Popper from '@mui/material/Popper'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import Popper, { PopperProps } from '@mui/material/Popper'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; +import { useSvgRef } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; import { generateVirtualElement } from './generateVirtualElement'; +type PointerState = { + isActive: boolean; + isMousePointer: boolean; + pointerHeight: number; +}; + +function usePointer(): PointerState & Pick { + const svgRef = useSvgRef(); + const popperRef: PopperProps['popperRef'] = React.useRef(null); + const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event: PointerEvent) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event: PointerEvent) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + const handleMove = (event: PointerEvent) => { + virtualElement.current = generateVirtualElement({ + x: event.clientX, + y: event.clientY, + }); + popperRef.current?.update(); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef]); + + return { ...pointer, popperRef, anchorEl: virtualElement.current }; +} + export function ItemTooltip() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); // Track the mouse position on chart. + const { isActive, isMousePointer, pointerHeight, popperRef, anchorEl } = + usePointer(); - if (!tooltipData || !mousePosition) { + if (!tooltipData || !isActive) { // No data to display return null; } - // The pointer type can be used to have different behavior based on pointer type. - const isMousePointer = mousePosition?.pointerType === 'mouse'; // Adapt the tooltip offset to the size of the pointer. - const yOffset = isMousePointer ? 0 : 40 - mousePosition.height; + const yOffset = isMousePointer ? 0 : 40 - pointerHeight; return ( @@ -28,7 +91,8 @@ export function ItemTooltip() { }} open placement={isMousePointer ? 'top-end' : 'top'} - anchorEl={generateVirtualElement(mousePosition)} + anchorEl={anchorEl} + popperRef={popperRef} modifiers={[ { name: 'offset', diff --git a/docs/data/charts/tooltip/ItemTooltipFixedY.js b/docs/data/charts/tooltip/ItemTooltipFixedY.js index 4e19cd1bc9f6..11bfb8fe8668 100644 --- a/docs/data/charts/tooltip/ItemTooltipFixedY.js +++ b/docs/data/charts/tooltip/ItemTooltipFixedY.js @@ -1,28 +1,92 @@ import * as React from 'react'; import NoSsr from '@mui/material/NoSsr'; import Popper from '@mui/material/Popper'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useDrawingArea, useSvgRef } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; import { generateVirtualElement } from './generateVirtualElement'; +function usePointer() { + const svgRef = useSvgRef(); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + }; + }, [svgRef]); + + return pointer; +} + export function ItemTooltipFixedY() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); + const { isActive } = usePointer(); + + const popperRef = React.useRef(null); + const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); const svgRef = useSvgRef(); // Get the ref of the component. const drawingArea = useDrawingArea(); // Get the dimensions of the chart inside the . - if (!tooltipData || !mousePosition) { + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleMove = (event) => { + virtualElement.current = generateVirtualElement({ + x: event.clientX, + // Add the y-coordinate of the to the to margin between the and the drawing area + y: svgRef.current.getBoundingClientRect().top + drawingArea.top, + }); + popperRef.current?.update(); + }; + + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef, drawingArea.top]); + + if (!tooltipData || !isActive) { // No data to display return null; } - const tooltipPosition = { - ...mousePosition, - // Add the y-coordinate of the to the to margin between the and the drawing area - y: svgRef.current.getBoundingClientRect().top + drawingArea.top, - }; - return ( diff --git a/docs/data/charts/tooltip/ItemTooltipFixedY.tsx b/docs/data/charts/tooltip/ItemTooltipFixedY.tsx index 3059c260afca..b6feefe896f4 100644 --- a/docs/data/charts/tooltip/ItemTooltipFixedY.tsx +++ b/docs/data/charts/tooltip/ItemTooltipFixedY.tsx @@ -1,28 +1,98 @@ import * as React from 'react'; import NoSsr from '@mui/material/NoSsr'; -import Popper from '@mui/material/Popper'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import Popper, { PopperProps } from '@mui/material/Popper'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useDrawingArea, useSvgRef } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement, MousePosition } from './generateVirtualElement'; +import { generateVirtualElement } from './generateVirtualElement'; + +type PointerState = { + isActive: boolean; + isMousePointer: boolean; + pointerHeight: number; +}; + +function usePointer(): PointerState { + const svgRef = useSvgRef(); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event: PointerEvent) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event: PointerEvent) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + }; + }, [svgRef]); + + return pointer; +} export function ItemTooltipFixedY() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); + const { isActive } = usePointer(); + + const popperRef: PopperProps['popperRef'] = React.useRef(null); + const virtualElement = React.useRef(generateVirtualElement({ x: 0, y: 0 })); const svgRef = useSvgRef(); // Get the ref of the component. const drawingArea = useDrawingArea(); // Get the dimensions of the chart inside the . - if (!tooltipData || !mousePosition) { + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleMove = (event: PointerEvent) => { + virtualElement.current = generateVirtualElement({ + x: event.clientX, + // Add the y-coordinate of the to the to margin between the and the drawing area + y: svgRef.current.getBoundingClientRect().top + drawingArea.top, + }); + popperRef.current?.update(); + }; + + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef, drawingArea.top]); + + if (!tooltipData || !isActive) { // No data to display return null; } - const tooltipPosition: MousePosition = { - ...mousePosition, - // Add the y-coordinate of the to the to margin between the and the drawing area - y: svgRef.current.getBoundingClientRect().top + drawingArea.top, - }; - return ( diff --git a/docs/data/charts/tooltip/ItemTooltipTopElement.js b/docs/data/charts/tooltip/ItemTooltipTopElement.js index 7d86e69b81e4..a3b52483984e 100644 --- a/docs/data/charts/tooltip/ItemTooltipTopElement.js +++ b/docs/data/charts/tooltip/ItemTooltipTopElement.js @@ -2,14 +2,59 @@ import * as React from 'react'; import NoSsr from '@mui/material/NoSsr'; import Popper from '@mui/material/Popper'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useSvgRef, useXAxis, useXScale, useYScale } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; import { generateVirtualElement } from './generateVirtualElement'; +function usePointer() { + const svgRef = useSvgRef(); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + }; + }, [svgRef]); + + return pointer; +} + export function ItemTooltipTopElement() { const tooltipData = useItemTooltip(); - const mousePosition = useMouseTracker(); + const { isActive } = usePointer(); // Get xAxis config to access its data array. const xAxis = useXAxis(); // Get the scale which map values to SVG coordinates. @@ -21,7 +66,7 @@ export function ItemTooltipTopElement() { // Get the ref of the component. const svgRef = useSvgRef(); - if (!tooltipData || !mousePosition || !xAxis.data) { + if (!tooltipData || !isActive || !xAxis.data) { // No data to display return null; } @@ -41,7 +86,6 @@ export function ItemTooltipTopElement() { const svgXPosition = xScale(xValue) ?? 0; const tooltipPosition = { - ...mousePosition, // Add half of `yScale.step()` to be in the middle of the band. x: svgRef.current.getBoundingClientRect().left + svgXPosition + xScale.step() / 2, diff --git a/docs/data/charts/tooltip/ItemTooltipTopElement.tsx b/docs/data/charts/tooltip/ItemTooltipTopElement.tsx index d69ab33e96da..a84a45cdc39b 100644 --- a/docs/data/charts/tooltip/ItemTooltipTopElement.tsx +++ b/docs/data/charts/tooltip/ItemTooltipTopElement.tsx @@ -2,14 +2,65 @@ import * as React from 'react'; import { ScaleBand } from '@mui/x-charts-vendor/d3-scale'; import NoSsr from '@mui/material/NoSsr'; import Popper from '@mui/material/Popper'; -import { useItemTooltip, useMouseTracker } from '@mui/x-charts/ChartsTooltip'; +import { useItemTooltip } from '@mui/x-charts/ChartsTooltip'; import { useSvgRef, useXAxis, useXScale, useYScale } from '@mui/x-charts/hooks'; import { CustomItemTooltipContent } from './CustomItemTooltipContent'; -import { generateVirtualElement, MousePosition } from './generateVirtualElement'; +import { generateVirtualElement } from './generateVirtualElement'; + +type PointerState = { + isActive: boolean; + isMousePointer: boolean; + pointerHeight: number; +}; + +function usePointer(): PointerState { + const svgRef = useSvgRef(); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointer, setPointer] = React.useState({ + isActive: false, + isMousePointer: false, + pointerHeight: 0, + }); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event: PointerEvent) => { + if (event.pointerType !== 'mouse') { + setPointer((prev) => ({ + ...prev, + isActive: false, + })); + } + }; + + const handleEnter = (event: PointerEvent) => { + setPointer({ + isActive: true, + isMousePointer: event.pointerType === 'mouse', + pointerHeight: event.height, + }); + }; + + element.addEventListener('pointerenter', handleEnter); + element.addEventListener('pointerup', handleOut); + + return () => { + element.removeEventListener('pointerenter', handleEnter); + element.removeEventListener('pointerup', handleOut); + }; + }, [svgRef]); + + return pointer; +} export function ItemTooltipTopElement() { const tooltipData = useItemTooltip<'bar'>(); - const mousePosition = useMouseTracker(); + const { isActive } = usePointer(); // Get xAxis config to access its data array. const xAxis = useXAxis(); // Get the scale which map values to SVG coordinates. @@ -21,7 +72,7 @@ export function ItemTooltipTopElement() { // Get the ref of the component. const svgRef = useSvgRef(); - if (!tooltipData || !mousePosition || !xAxis.data) { + if (!tooltipData || !isActive || !xAxis.data) { // No data to display return null; } @@ -40,8 +91,7 @@ export function ItemTooltipTopElement() { const svgYPosition = yScale(tooltipData.value) ?? 0; const svgXPosition = xScale(xValue) ?? 0; - const tooltipPosition: MousePosition = { - ...mousePosition, + const tooltipPosition = { // Add half of `yScale.step()` to be in the middle of the band. x: svgRef.current.getBoundingClientRect().left + diff --git a/docs/data/charts/tooltip/tooltip.md b/docs/data/charts/tooltip/tooltip.md index 655886065223..f53171fca6e5 100644 --- a/docs/data/charts/tooltip/tooltip.md +++ b/docs/data/charts/tooltip/tooltip.md @@ -15,8 +15,8 @@ If you are using composition, you can add the `` component and The tooltip can be triggered by two kinds of events: -- `'item'`—when the user's mouse hovers over an item on the chart, the tooltip will display data about this specific item. -- `'axis'`—the user's mouse position is associated with a value of the x-axis. The tooltip will display data about all series at this specific x value. +- `'item'`—when the user's mouse hovers over an item on the chart, the tooltip displays data about this specific item. +- `'axis'`—the user's mouse position is associated with a value of the x-axis. The tooltip displays data about all series at this specific x value. - `'none'`—disable the tooltip. {{"demo": "Interaction.js"}} @@ -79,7 +79,7 @@ See [Label—Conditional formatting](/x/react-charts/label/#conditional-formatti ### Hiding values You can hide the axis value with `hideTooltip` in the `xAxis` props. -It will remove the header showing the x-axis value from the tooltip. +It removes the header showing the x-axis value from the tooltip. ```jsx ({ - color: ownerState.open ? 'secondary' : 'primary', + color: ownerState.isPickerOpen ? 'secondary' : 'primary', }), }} /> diff --git a/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx index e1272f2776ba..ea3b72c58ed1 100644 --- a/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx +++ b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx @@ -9,7 +9,7 @@ export default function CustomSlotPropsCallback() { ({ - color: ownerState.open ? 'secondary' : 'primary', + color: ownerState.isPickerOpen ? 'secondary' : 'primary', }), }} /> diff --git a/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx.preview b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx.preview index 100188718fae..70bc3e35f25e 100644 --- a/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx.preview +++ b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx.preview @@ -1,7 +1,7 @@ ({ - color: ownerState.open ? 'secondary' : 'primary', + color: ownerState.isPickerOpen ? 'secondary' : 'primary', }), }} /> \ No newline at end of file diff --git a/docs/data/common-concepts/custom-components/custom-components.md b/docs/data/common-concepts/custom-components/custom-components.md index cd89c78cbe62..1875037ac5bf 100644 --- a/docs/data/common-concepts/custom-components/custom-components.md +++ b/docs/data/common-concepts/custom-components/custom-components.md @@ -11,7 +11,7 @@ This is the role of all the `baseXXX` component on the Data Grid component (`bas These slots receive props that should be as generic as possible so that it is easy to interface any other design system. Other slots allow you to override parts of the MUI X UI components with a custom UI built specifically for this component. -This is the role of slots like `calendarHeader` on the `DateCalendar` component or `item` on the `RichTreeView` component. +This is the role of slots like `calendarHeader` on the `DateCalendar` component or `item` on the Rich Tree View component. These slots receive props specific to this part of the UI and will most likely not be re-use throughout your application. ## Basic usage diff --git a/docs/data/data-grid/column-definition/AutogeneratedRows.js b/docs/data/data-grid/column-definition/AutogeneratedRows.js index d5689bc6ecbd..b9059be151f0 100644 --- a/docs/data/data-grid/column-definition/AutogeneratedRows.js +++ b/docs/data/data-grid/column-definition/AutogeneratedRows.js @@ -1,6 +1,7 @@ import * as React from 'react'; import { DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, isAutogeneratedRow, useGridApiRef, useKeepGroupedColumnsHidden, @@ -8,7 +9,7 @@ import { import { useMovieData } from '@mui/x-data-grid-generator'; const columns = [ - { field: '__row_group_by_columns_group__', width: 200 }, + { field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, width: 200 }, { field: 'company', width: 200 }, { field: 'title', diff --git a/docs/data/data-grid/column-definition/AutogeneratedRows.tsx b/docs/data/data-grid/column-definition/AutogeneratedRows.tsx index c58c4bf79e2d..cb127be3cb88 100644 --- a/docs/data/data-grid/column-definition/AutogeneratedRows.tsx +++ b/docs/data/data-grid/column-definition/AutogeneratedRows.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, GridColDef, isAutogeneratedRow, useGridApiRef, @@ -9,7 +10,7 @@ import { import { useMovieData } from '@mui/x-data-grid-generator'; const columns: GridColDef[] = [ - { field: '__row_group_by_columns_group__', width: 200 }, + { field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, width: 200 }, { field: 'company', width: 200 }, { field: 'title', diff --git a/docs/data/data-grid/column-definition/column-definition.md b/docs/data/data-grid/column-definition/column-definition.md index 75364b0c7c81..89c245b09175 100644 --- a/docs/data/data-grid/column-definition/column-definition.md +++ b/docs/data/data-grid/column-definition/column-definition.md @@ -102,8 +102,8 @@ Read more in the [handling autogenerated rows](/x/react-data-grid/column-definit ::: :::warning -[Row grouping](/x/react-data-grid/row-grouping/) uses the [`groupingValueGetter`](/x/react-data-grid/row-grouping/#using-groupingvaluegetter-for-complex-grouping-value) instead of `valueGetter` to get the value for the grouping. -The value passed to the `groupingValueGetter` is the raw row value (`row[field]`) even if the column definition has a `valueGetter` defined. +[Row grouping](/x/react-data-grid/row-grouping/) uses the [`groupingValueGetter()`](/x/react-data-grid/row-grouping/#using-groupingvaluegetter-for-complex-grouping-value) instead of `valueGetter` to get the value for the grouping. +The value passed to the `groupingValueGetter()` is the raw row value (`row[field]`) even if the column definition has a `valueGetter` defined. ::: ### Value formatter diff --git a/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.js b/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.js index bfa80b2c2523..96ed3a295aa2 100644 --- a/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.js +++ b/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.js @@ -4,10 +4,11 @@ import { GridToolbar, useKeepGroupedColumnsHidden, useGridApiRef, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, } from '@mui/x-data-grid-premium'; import { useDemoData } from '@mui/x-data-grid-generator'; -const hiddenFields = ['id', '__row_group_by_columns_group__', 'status']; +const hiddenFields = ['id', GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, 'status']; const getTogglableColumns = (columns) => { return columns diff --git a/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.tsx b/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.tsx index 9c667a5f013e..83eb196585b7 100644 --- a/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.tsx +++ b/docs/data/data-grid/column-visibility/ColumnSelectorGridCustomizeColumns.tsx @@ -5,10 +5,11 @@ import { GridColDef, useKeepGroupedColumnsHidden, useGridApiRef, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, } from '@mui/x-data-grid-premium'; import { useDemoData } from '@mui/x-data-grid-generator'; -const hiddenFields = ['id', '__row_group_by_columns_group__', 'status']; +const hiddenFields = ['id', GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, 'status']; const getTogglableColumns = (columns: GridColDef[]) => { return columns diff --git a/docs/data/data-grid/column-visibility/column-visibility.md b/docs/data/data-grid/column-visibility/column-visibility.md index 08d912a07057..3b7a149dd701 100644 --- a/docs/data/data-grid/column-visibility/column-visibility.md +++ b/docs/data/data-grid/column-visibility/column-visibility.md @@ -86,8 +86,13 @@ In the following demo, the columns panel is disabled, and access to columns `id` To show or hide specific columns in the column visibility panel, use the `slotProps.columnsManagement.getTogglableColumns` prop. It should return an array of column field names. ```tsx -// stop `id`, `__row_group_by_columns_group__`, and `status` columns to be togglable -const hiddenFields = ['id', '__row_group_by_columns_group__', 'status']; +import { + DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, +} from '@mui/x-data-grid-premium'; + +// stop `id`, GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, and `status` columns to be togglable +const hiddenFields = ['id', GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, 'status']; const getTogglableColumns = (columns: GridColDef[]) => { return columns @@ -95,7 +100,7 @@ const getTogglableColumns = (columns: GridColDef[]) => { .map((column) => column.field); }; -; ``` -Note that `createTheme` accepts any number of arguments. +Note that `createTheme()` accepts any number of arguments. If you are already using the [translations of the core components](/material-ui/guides/localization/#locale-text), you can add `bgBG` as a new argument. The same import works for Data Grid Pro as it's an extension of Data Grid. @@ -86,7 +86,7 @@ const theme = createTheme( ; ``` -If you want to pass language translations directly to the Data Grid without using `createTheme` and `ThemeProvider`, you can directly load the language translations from `@mui/x-data-grid/locales`. +If you want to pass language translations directly to the Data Grid without using `createTheme()` and `ThemeProvider`, you can directly load the language translations from `@mui/x-data-grid/locales`. ```jsx import { DataGrid } from '@mui/x-data-grid'; diff --git a/docs/data/data-grid/overview/DataGridPremiumDemo.js b/docs/data/data-grid/overview/DataGridPremiumDemo.js index 98a6959cff62..3e99541b76cb 100644 --- a/docs/data/data-grid/overview/DataGridPremiumDemo.js +++ b/docs/data/data-grid/overview/DataGridPremiumDemo.js @@ -2,6 +2,7 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import { DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, GridToolbar, useGridApiRef, useKeepGroupedColumnsHidden, @@ -40,7 +41,7 @@ export default function DataGridPremiumDemo() { model: ['commodity'], }, sorting: { - sortModel: [{ field: '__row_group_by_columns_group__', sort: 'asc' }], + sortModel: [{ field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, sort: 'asc' }], }, aggregation: { model: { diff --git a/docs/data/data-grid/overview/DataGridPremiumDemo.tsx b/docs/data/data-grid/overview/DataGridPremiumDemo.tsx index 98a6959cff62..3e99541b76cb 100644 --- a/docs/data/data-grid/overview/DataGridPremiumDemo.tsx +++ b/docs/data/data-grid/overview/DataGridPremiumDemo.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import { DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, GridToolbar, useGridApiRef, useKeepGroupedColumnsHidden, @@ -40,7 +41,7 @@ export default function DataGridPremiumDemo() { model: ['commodity'], }, sorting: { - sortModel: [{ field: '__row_group_by_columns_group__', sort: 'asc' }], + sortModel: [{ field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, sort: 'asc' }], }, aggregation: { model: { diff --git a/docs/data/data-grid/pagination/PageSizeCustomOptions.js b/docs/data/data-grid/pagination/PageSizeCustomOptions.js index 667abfb9a412..2cdb2ddfa58b 100644 --- a/docs/data/data-grid/pagination/PageSizeCustomOptions.js +++ b/docs/data/data-grid/pagination/PageSizeCustomOptions.js @@ -17,7 +17,7 @@ export default function PageSizeCustomOptions() { ...data.initialState, pagination: { paginationModel: { pageSize: 5 } }, }} - pageSizeOptions={[5, 10, 25]} + pageSizeOptions={[5, 10, 25, { value: -1, label: 'All' }]} />
); diff --git a/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx b/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx index 667abfb9a412..2cdb2ddfa58b 100644 --- a/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx +++ b/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx @@ -17,7 +17,7 @@ export default function PageSizeCustomOptions() { ...data.initialState, pagination: { paginationModel: { pageSize: 5 } }, }} - pageSizeOptions={[5, 10, 25]} + pageSizeOptions={[5, 10, 25, { value: -1, label: 'All' }]} />
); diff --git a/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx.preview b/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx.preview index 88965a8c26f4..1f4dad3d8994 100644 --- a/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx.preview +++ b/docs/data/data-grid/pagination/PageSizeCustomOptions.tsx.preview @@ -4,5 +4,5 @@ ...data.initialState, pagination: { paginationModel: { pageSize: 5 } }, }} - pageSizeOptions={[5, 10, 25]} + pageSizeOptions={[5, 10, 25, { value: -1, label: 'All' }]} /> \ No newline at end of file diff --git a/docs/data/data-grid/pagination/pagination.md b/docs/data/data-grid/pagination/pagination.md index 90b26b56d8d7..7259d7fca5b0 100644 --- a/docs/data/data-grid/pagination/pagination.md +++ b/docs/data/data-grid/pagination/pagination.md @@ -41,10 +41,10 @@ You should provide an array of items, each item should be one of these types: ``` -- **object**, the `value` and `label` keys will be used respectively for the value and label of the option. +- **object**, the `value` and `label` keys will be used respectively for the value and label of the option. Define `value` as `-1` to display all results. ```jsx - + ``` {{"demo": "PageSizeCustomOptions.js", "bg": "inline"}} diff --git a/docs/data/data-grid/row-grouping/RowGroupingFullExample.js b/docs/data/data-grid/row-grouping/RowGroupingFullExample.js index d1a08e3ff062..52040b1639af 100644 --- a/docs/data/data-grid/row-grouping/RowGroupingFullExample.js +++ b/docs/data/data-grid/row-grouping/RowGroupingFullExample.js @@ -1,6 +1,7 @@ import * as React from 'react'; import { DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, useGridApiRef, useKeepGroupedColumnsHidden, } from '@mui/x-data-grid-premium'; @@ -23,7 +24,7 @@ export default function RowGroupingFullExample() { model: ['commodity'], }, sorting: { - sortModel: [{ field: '__row_group_by_columns_group__', sort: 'asc' }], + sortModel: [{ field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, sort: 'asc' }], }, }, }); diff --git a/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx b/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx index d1a08e3ff062..52040b1639af 100644 --- a/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx +++ b/docs/data/data-grid/row-grouping/RowGroupingFullExample.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { DataGridPremium, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, useGridApiRef, useKeepGroupedColumnsHidden, } from '@mui/x-data-grid-premium'; @@ -23,7 +24,7 @@ export default function RowGroupingFullExample() { model: ['commodity'], }, sorting: { - sortModel: [{ field: '__row_group_by_columns_group__', sort: 'asc' }], + sortModel: [{ field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, sort: 'asc' }], }, }, }); diff --git a/docs/data/data-grid/row-grouping/row-grouping.md b/docs/data/data-grid/row-grouping/row-grouping.md index b70899fb38b4..13f56a2ebac8 100644 --- a/docs/data/data-grid/row-grouping/row-grouping.md +++ b/docs/data/data-grid/row-grouping/row-grouping.md @@ -11,6 +11,10 @@ In the following example, movies are grouped based on their production `company` {{"demo": "RowGroupingBasicExample.js", "bg": "inline", "defaultCodeOpen": false}} +:::info +If you are looking for row grouping on the server-side, see [server-side row grouping](/x/react-data-grid/server-side-data/row-grouping/). +::: + ## Grouping criteria ### Initialize the row grouping @@ -252,6 +256,10 @@ Use the `setRowChildrenExpansion` method on `apiRef` to programmatically set the {{"demo": "RowGroupingSetChildrenExpansion.js", "bg": "inline", "defaultCodeOpen": false}} +:::warning +The `apiRef.current.setRowChildrenExpansion` method is not compatible with the [server-side tree data](/x/react-data-grid/server-side-data/tree-data/) and [server-side row grouping](/x/react-data-grid/server-side-data/row-grouping/). Use `apiRef.current.unstable_dataSource.fetchRows` instead. +::: + ### Customize grouping cell indent To change the default cell indent, you can use the `--DataGrid-cellOffsetMultiplier` CSS variable: @@ -280,10 +288,6 @@ If you are rendering leaves with the `leafField` property of `groupingColDef`, t You can force the filtering to be applied on another grouping criteria with the `mainGroupingCriteria` property of `groupingColDef` -:::warning -This feature is not yet compatible with `sortingMode = "server"` and `filteringMode = "server"`. -::: - {{"demo": "RowGroupingFilteringSingleGroupingColDef.js", "bg": "inline", "defaultCodeOpen": false}} ### Multiple grouping columns @@ -376,6 +380,10 @@ const rows = apiRef.current.getRowGroupChildren({ {{"demo": "RowGroupingGetRowGroupChildren.js", "bg": "inline", "defaultCodeOpen": false}} +:::warning +The `apiRef.current.getRowGroupChildren` method is not compatible with the [server-side row grouping](/x/react-data-grid/server-side-data/row-grouping/) since all the rows might not be available to get at a given instance. +::: + ## Row group panel 🚧 :::warning diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js new file mode 100644 index 000000000000..fc75932d136b --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.js @@ -0,0 +1,69 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingDataGrid() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns } = useMockServer({ + rowGrouping: true, + }); + + const dataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company', 'director'], + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx new file mode 100644 index 000000000000..91c7f66a6d99 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx @@ -0,0 +1,70 @@ +import * as React from 'react'; +import { + DataGridPremium, + GridDataSource, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingDataGrid() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns } = useMockServer({ + rowGrouping: true, + }); + + const dataSource: GridDataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company', 'director'], + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx.preview b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx.preview new file mode 100644 index 000000000000..920c80a41342 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingDataGrid.tsx.preview @@ -0,0 +1,16 @@ + + +
+ +
\ No newline at end of file diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js new file mode 100644 index 000000000000..793347a26301 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.js @@ -0,0 +1,137 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Snackbar from '@mui/material/Snackbar'; +import Button from '@mui/material/Button'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import { alpha, styled, darken, lighten } from '@mui/material/styles'; + +export default function ServerSideRowGroupingErrorHandling() { + const apiRef = useGridApiRef(); + const [rootError, setRootError] = React.useState(); + const [childrenError, setChildrenError] = React.useState(); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); + + const { fetchRows, columns } = useMockServer( + { + rowGrouping: true, + }, + {}, + shouldRequestsFail, + ); + + const dataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company', 'director'], + }, + }, + }); + + return ( +
+
+ + setShouldRequestsFail(event.target.checked)} + /> + } + label="Make the requests fail" + /> +
+
+ { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(error.message); + } else { + setChildrenError( + `${error.message} (Requested level: ${params.groupKeys.join(' > ')})`, + ); + } + }} + unstable_dataSourceCache={null} + apiRef={apiRef} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + /> +
+
+ ); +} + +function getBorderColor(theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }) { + if (!error) { + return null; + } + return {error}; +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx new file mode 100644 index 000000000000..621b74b052f4 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingErrorHandling.tsx @@ -0,0 +1,138 @@ +import * as React from 'react'; +import { + DataGridPremium, + GridDataSource, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +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'; + +export default function ServerSideRowGroupingErrorHandling() { + const apiRef = useGridApiRef(); + const [rootError, setRootError] = React.useState(); + const [childrenError, setChildrenError] = React.useState(); + const [shouldRequestsFail, setShouldRequestsFail] = React.useState(false); + + const { fetchRows, columns } = useMockServer( + { + rowGrouping: true, + }, + {}, + shouldRequestsFail, + ); + + const dataSource: GridDataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company', 'director'], + }, + }, + }); + + return ( +
+
+ + setShouldRequestsFail(event.target.checked)} + /> + } + label="Make the requests fail" + /> +
+
+ { + if (!params.groupKeys || params.groupKeys.length === 0) { + setRootError(error.message); + } else { + setChildrenError( + `${error.message} (Requested level: ${params.groupKeys.join(' > ')})`, + ); + } + }} + unstable_dataSourceCache={null} + apiRef={apiRef} + initialState={initialState} + /> + {rootError && } + setChildrenError('')} + message={childrenError} + /> +
+
+ ); +} + +function getBorderColor(theme: Theme) { + if (theme.palette.mode === 'light') { + return lighten(alpha(theme.palette.divider, 1), 0.88); + } + return darken(alpha(theme.palette.divider, 1), 0.68); +} + +const StyledDiv = styled('div')(({ theme: t }) => ({ + position: 'absolute', + zIndex: 10, + fontSize: '0.875em', + top: 0, + height: '100%', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + borderRadius: '4px', + border: `1px solid ${getBorderColor(t)}`, + backgroundColor: t.palette.background.default, +})); + +function ErrorOverlay({ error }: { error: string }) { + if (!error) { + return null; + } + return {error}; +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js new file mode 100644 index 000000000000..7db1ea09c9c3 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.js @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, + GridToolbar, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingFullDataGrid() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns, loadNewData } = useMockServer({ + rowGrouping: true, + rowLength: 1000, + dataSet: 'Commodity', + maxColumns: 20, + }); + + const dataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['commodity', 'status'], + }, + columns: { + columnVisibilityModel: { + id: false, + }, + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx new file mode 100644 index 000000000000..4545cf49f9e7 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingFullDataGrid.tsx @@ -0,0 +1,84 @@ +import * as React from 'react'; +import { + DataGridPremium, + GridDataSource, + useGridApiRef, + useKeepGroupedColumnsHidden, + GridToolbar, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingFullDataGrid() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns, loadNewData } = useMockServer({ + rowGrouping: true, + rowLength: 1000, + dataSet: 'Commodity', + maxColumns: 20, + }); + + const dataSource: GridDataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['commodity', 'status'], + }, + columns: { + columnVisibilityModel: { + id: false, + }, + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js new file mode 100644 index 000000000000..bdb42747fc07 --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.js @@ -0,0 +1,70 @@ +import * as React from 'react'; +import { + DataGridPremium, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingGroupExpansion() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns } = useMockServer({ + rowGrouping: true, + }); + + const dataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company'], + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx new file mode 100644 index 000000000000..64aa58a45d8e --- /dev/null +++ b/docs/data/data-grid/server-side-data/ServerSideRowGroupingGroupExpansion.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { + DataGridPremium, + GridDataSource, + useGridApiRef, + useKeepGroupedColumnsHidden, +} from '@mui/x-data-grid-premium'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import Button from '@mui/material/Button'; + +export default function ServerSideRowGroupingGroupExpansion() { + const apiRef = useGridApiRef(); + + const { fetchRows, columns } = useMockServer({ + rowGrouping: true, + }); + + const dataSource: GridDataSource = React.useMemo(() => { + return { + getRows: async (params) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row.group, + getChildrenCount: (row) => row.descendantCount, + }; + }, [fetchRows]); + + const initialState = useKeepGroupedColumnsHidden({ + apiRef, + initialState: { + rowGrouping: { + model: ['company'], + }, + }, + }); + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/server-side-data/row-grouping.md b/docs/data/data-grid/server-side-data/row-grouping.md index 537ef8ad0d54..25b6044f3f43 100644 --- a/docs/data/data-grid/server-side-data/row-grouping.md +++ b/docs/data/data-grid/server-side-data/row-grouping.md @@ -2,14 +2,90 @@ title: React Server-side row grouping --- -# Data Grid - Server-side row grouping [](/x/introduction/licensing/#pro-plan 'Pro plan')🚧 +# Data Grid - Server-side row grouping [](/x/introduction/licensing/#pro-plan 'Pro plan')

Lazy-loaded row grouping with server-side data source.

-:::warning -This feature isn't implemented yet. It's coming. +To dynamically load row grouping data from the server, including lazy-loading of children, create a data source and pass the `unstable_dataSource` prop to the Data Grid, as mentioned in the [overview](/x/react-data-grid/server-side-data/) section. + +:::info +If you are looking for row grouping on the client-side, see [client-side row grouping](/x/react-data-grid/row-grouping/). +::: + +Similar to the [tree data](/x/react-data-grid/server-side-data/tree-data/), you need to pass some additional properties to enable the data source row grouping feature: + +- `getGroupKey()`: Returns the group key for the row. +- `getChildrenCount()`: Returns the number of children for the row. If the children count is not available for some reason, but there are some children, returns `-1`. + +```tsx +const customDataSource: GridDataSource = { + getRows: async (params) => { + // Fetch the data from the server + }, + getGroupKey: (row) => { + // Return the group key for the row, e.g. `name` + return row.name; + }, + getChildrenCount: (row) => { + // Return the number of children for the row + return row.childrenCount; + }, +}; +``` -👍 Upvote [issue #10859](https://github.com/mui/mui-x/issues/10859) if you want to see it land faster. +In addition to `groupKeys`, the `getRows()` callback receives a `groupFields` parameter. This corresponds to the current `rowGroupingModel`. Use `groupFields` on the server to group the data for each `getRows()` call. -Don't hesitate to leave a comment on the same issue to influence what gets built. Especially if you already have a use case for this component, or if you are facing a pain point with your current solution. +```tsx +const getRows: async (params) => { + const urlParams = new URLSearchParams({ + // Example: JSON.stringify(['20th Century Fox', 'James Cameron']) + groupKeys: JSON.stringify(params.groupKeys), + // Example: JSON.stringify(['company', 'director']) + groupFields: JSON.stringify(params.groupFields), + }); + const getRowsResponse = await fetchRows( + // Server should group the data based on `groupFields` and + // extract the rows for the nested level based on `groupKeys` + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; +} +``` + +{{"demo": "ServerSideRowGroupingDataGrid.js", "bg": "inline"}} + +:::warning +For complex data, consider using `colDef.groupingValueGetter` to extract the grouping value. This value is passed in the `groupKeys` parameter when `getRows` is called. + +Ensure your backend can interpret the `groupKeys` parameter generated by `colDef.groupingValueGetter` to retrieve grouping values for child rows. ::: + +## Error handling + +If an error occurs during a `getRows` call, the Data Grid displays an error message in the row group cell. `unstable_onDataSourceError` is also triggered with the error and the fetch params. + +This example shows error handling with toast notifications and default error messages in grouping cells. Caching is disabled for simplicity. + +{{"demo": "ServerSideRowGroupingErrorHandling.js", "bg": "inline"}} + +## Group expansion + +The group expansion works similar to the [data source tree data](/x/react-data-grid/server-side-data/tree-data/#group-expansion). +The following demo uses `defaultGroupingExpansionDepth='-1'` to expand all the groups. + +{{"demo": "ServerSideRowGroupingGroupExpansion.js", "bg": "inline"}} + +## Demo + +In the following demo, use the auto generated data based on the `Commodities` dataset to simulate the server-side row grouping. + +{{"demo": "ServerSideRowGroupingFullDataGrid.js", "bg": "inline"}} + +## API + +- [DataGrid](/x/api/data-grid/data-grid/) +- [DataGridPro](/x/api/data-grid/data-grid-pro/) +- [DataGridPremium](/x/api/data-grid/data-grid-premium/) diff --git a/docs/data/data-grid/server-side-data/tree-data.md b/docs/data/data-grid/server-side-data/tree-data.md index 8e77fe0542bf..d5c725ac456e 100644 --- a/docs/data/data-grid/server-side-data/tree-data.md +++ b/docs/data/data-grid/server-side-data/tree-data.md @@ -8,8 +8,14 @@ title: React Server-side tree data To dynamically load tree data from the server, including lazy-loading of children, you must create a data source and pass the `unstable_dataSource` prop to the Data Grid, as detailed in the [overview section](/x/react-data-grid/server-side-data/). -The data source also requires some additional props to handle tree data, namely `getGroupKey` and `getChildrenCount`. -If the children count is not available for some reason, but there are some children, `getChildrenCount` should return `-1`. +:::info +If you are looking for tree data on the client-side, see [client-side tree data](/x/react-data-grid/tree-data/). +::: + +The data source also requires some additional props to handle tree data: + +- `getGroupKey()`: Returns the group key for the row. +- `getChildrenCount()`: Returns the number of children for the row. If the children count is not available for some reason, but there are some children, returns `-1`. ```tsx const customDataSource: GridDataSource = { @@ -27,6 +33,26 @@ const customDataSource: GridDataSource = { }; ``` +Like the other parameters such as `filterModel`, `sortModel`, and `paginationModel`, the `getRows()` callback receives a `groupKeys` parameter that corresponds to the keys provided for each nested level in `getGroupKey()`. +Use `groupKeys` on the server to extract the rows for a given nested level. + +```tsx +const getRows: async (params) => { + const urlParams = new URLSearchParams({ + // Example: JSON.stringify(['Billy Houston', 'Lora Dean']) + groupKeys: JSON.stringify(params.groupKeys), + }); + const getRowsResponse = await fetchRows( + // Server should extract the rows for the nested level based on `groupKeys` + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; +} +``` + The following tree data example supports filtering, sorting, and pagination on the server. It also caches the data by default. diff --git a/docs/data/data-grid-component-api-pages.ts b/docs/data/dataGridApiPages.ts similarity index 89% rename from docs/data/data-grid-component-api-pages.ts rename to docs/data/dataGridApiPages.ts index 5aed416321e1..37479bd4801b 100644 --- a/docs/data/data-grid-component-api-pages.ts +++ b/docs/data/dataGridApiPages.ts @@ -1,6 +1,6 @@ import type { MuiPage } from 'docs/src/MuiPage'; -const apiPages: MuiPage[] = [ +const dataGridApiPages: MuiPage[] = [ { pathname: '/x/api/data-grid/data-grid', title: 'DataGrid', @@ -28,4 +28,4 @@ const apiPages: MuiPage[] = [ title: 'GridToolbarQuickFilter', }, ]; -export default apiPages; +export default dataGridApiPages; diff --git a/docs/data/date-pickers/custom-field/BrowserV6Field.js b/docs/data/date-pickers/custom-field/BrowserV6Field.js deleted file mode 100644 index 63be3f56e884..000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6Field.js +++ /dev/null @@ -1,90 +0,0 @@ -import * as React from 'react'; - -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import Box from '@mui/material/Box'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { unstable_useDateField as useDateField } from '@mui/x-date-pickers/DateField'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; - -const BrowserField = React.forwardRef((props, ref) => { - const { - // Should be ignored - enableAccessibleFieldDOMStructure, - disabled, - id, - label, - inputRef, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, - // extracting `error`, 'focused', and `ownerState` as `input` does not support those props - error, - focused, - ownerState, - sx, - ...other - } = props; - - const handleRef = useForkRef(containerRef, ref); - - return ( - - {startAdornment} - - {endAdornment} - - ); -}); - -const BrowserDateField = React.forwardRef((props, ref) => { - const { slots, slotProps, ...textFieldProps } = props; - - const fieldResponse = useDateField({ - ...textFieldProps, - enableAccessibleFieldDOMStructure: false, - }); - - /* If you don't need a clear button, you can skip the use of this hook */ - const processedFieldProps = useClearableField({ - ...fieldResponse, - slots, - slotProps, - }); - - return ; -}); - -const BrowserDatePicker = React.forwardRef((props, ref) => { - return ( - - ); -}); - -export default function BrowserV6Field() { - return ( - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/BrowserV6Field.tsx b/docs/data/date-pickers/custom-field/BrowserV6Field.tsx deleted file mode 100644 index 49138cd42863..000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6Field.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import * as React from 'react'; -import { Dayjs } from 'dayjs'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import Box from '@mui/material/Box'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { DatePicker, DatePickerProps } from '@mui/x-date-pickers/DatePicker'; -import { - unstable_useDateField as useDateField, - UseDateFieldProps, -} from '@mui/x-date-pickers/DateField'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; -import { - BaseSingleInputFieldProps, - DateValidationError, - FieldSection, -} from '@mui/x-date-pickers/models'; - -interface BrowserFieldProps - extends Omit, 'size'> { - label?: React.ReactNode; - inputRef?: React.Ref; - InputProps?: { - ref?: React.Ref; - endAdornment?: React.ReactNode; - startAdornment?: React.ReactNode; - }; - error?: boolean; - focused?: boolean; - ownerState?: any; - sx?: any; - enableAccessibleFieldDOMStructure: boolean; -} - -type BrowserFieldComponent = (( - props: BrowserFieldProps & React.RefAttributes, -) => React.JSX.Element) & { propTypes?: any }; - -const BrowserField = React.forwardRef( - (props: BrowserFieldProps, ref: React.Ref) => { - const { - // Should be ignored - enableAccessibleFieldDOMStructure, - - disabled, - id, - label, - inputRef, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, - // extracting `error`, 'focused', and `ownerState` as `input` does not support those props - error, - focused, - ownerState, - sx, - ...other - } = props; - - const handleRef = useForkRef(containerRef, ref); - - return ( - - {startAdornment} - - {endAdornment} - - ); - }, -) as BrowserFieldComponent; - -interface BrowserDateFieldProps - extends UseDateFieldProps, - BaseSingleInputFieldProps< - Dayjs | null, - Dayjs, - FieldSection, - false, - DateValidationError - > {} - -const BrowserDateField = React.forwardRef( - (props: BrowserDateFieldProps, ref: React.Ref) => { - const { slots, slotProps, ...textFieldProps } = props; - - const fieldResponse = useDateField({ - ...textFieldProps, - enableAccessibleFieldDOMStructure: false, - }); - - /* If you don't need a clear button, you can skip the use of this hook */ - const processedFieldProps = useClearableField({ - ...fieldResponse, - slots, - slotProps, - }); - - return ; - }, -); - -const BrowserDatePicker = React.forwardRef( - (props: DatePickerProps, ref: React.Ref) => { - return ( - - ref={ref} - {...props} - slots={{ ...props.slots, field: BrowserDateField }} - /> - ); - }, -); - -export default function BrowserV6Field() { - return ( - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/BrowserV6Field.tsx.preview b/docs/data/date-pickers/custom-field/BrowserV6Field.tsx.preview deleted file mode 100644 index 0bb9e399d3cb..000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6Field.tsx.preview +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.js b/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.js deleted file mode 100644 index 5421fd0d4348..000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.js +++ /dev/null @@ -1,140 +0,0 @@ -import * as React from 'react'; - -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import useSlotProps from '@mui/utils/useSlotProps'; -import Box from '@mui/material/Box'; -import Stack from '@mui/material/Stack'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; -import { unstable_useMultiInputDateRangeField as useMultiInputDateRangeField } from '@mui/x-date-pickers-pro/MultiInputDateRangeField'; - -const BrowserField = React.forwardRef((props, ref) => { - const { - // Should be ignored - enableAccessibleFieldDOMStructure, - disabled, - id, - label, - inputRef, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, - // extracting `error`, 'focused', and `ownerState` as `input` does not support those props - error, - focused, - ownerState, - sx, - ...other - } = props; - - const handleRef = useForkRef(containerRef, ref); - - return ( - - {startAdornment} - - {endAdornment} - - ); -}); - -const BrowserMultiInputDateRangeField = React.forwardRef((props, ref) => { - const { - slotProps, - value, - defaultValue, - format, - onChange, - readOnly, - disabled, - onError, - shouldDisableDate, - minDate, - maxDate, - disableFuture, - disablePast, - selectedSections, - onSelectedSectionsChange, - className, - unstableStartFieldRef, - unstableEndFieldRef, - } = props; - - const startTextFieldProps = useSlotProps({ - elementType: 'input', - externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'start' }, - }); - - const endTextFieldProps = useSlotProps({ - elementType: 'input', - externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'end' }, - }); - - const fieldResponse = useMultiInputDateRangeField({ - sharedProps: { - value, - defaultValue, - format, - onChange, - readOnly, - disabled, - onError, - shouldDisableDate, - minDate, - maxDate, - disableFuture, - disablePast, - selectedSections, - onSelectedSectionsChange, - enableAccessibleFieldDOMStructure: false, - }, - startTextFieldProps, - endTextFieldProps, - unstableStartFieldRef, - unstableEndFieldRef, - }); - - return ( - - - - - - ); -}); - -const BrowserDateRangePicker = React.forwardRef((props, ref) => { - return ( - - ); -}); - -export default function BrowserV6MultiInputRangeField() { - return ( - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx deleted file mode 100644 index b52029d14cde..000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import * as React from 'react'; -import { Dayjs } from 'dayjs'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import useSlotProps from '@mui/utils/useSlotProps'; -import Box from '@mui/material/Box'; -import Stack from '@mui/material/Stack'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { - DateRangePicker, - DateRangePickerProps, -} from '@mui/x-date-pickers-pro/DateRangePicker'; -import { unstable_useMultiInputDateRangeField as useMultiInputDateRangeField } from '@mui/x-date-pickers-pro/MultiInputDateRangeField'; -import { - BaseMultiInputFieldProps, - DateRange, - DateRangeValidationError, - MultiInputFieldSlotTextFieldProps, - RangeFieldSection, - UseDateRangeFieldProps, -} from '@mui/x-date-pickers-pro/models'; - -interface BrowserFieldProps - extends Omit, 'size'> { - label?: React.ReactNode; - inputRef?: React.Ref; - InputProps?: { - ref?: React.Ref; - endAdornment?: React.ReactNode; - startAdornment?: React.ReactNode; - }; - error?: boolean; - focused?: boolean; - ownerState?: any; - sx?: any; - enableAccessibleFieldDOMStructure: boolean; -} - -type BrowserFieldComponent = (( - props: BrowserFieldProps & React.RefAttributes, -) => React.JSX.Element) & { propTypes?: any }; - -const BrowserField = React.forwardRef( - (props: BrowserFieldProps, ref: React.Ref) => { - const { - // Should be ignored - enableAccessibleFieldDOMStructure, - - disabled, - id, - label, - inputRef, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, - // extracting `error`, 'focused', and `ownerState` as `input` does not support those props - error, - focused, - ownerState, - sx, - ...other - } = props; - - const handleRef = useForkRef(containerRef, ref); - - return ( - - {startAdornment} - - {endAdornment} - - ); - }, -) as BrowserFieldComponent; - -interface BrowserMultiInputDateRangeFieldProps - extends UseDateRangeFieldProps, - BaseMultiInputFieldProps< - DateRange, - Dayjs, - RangeFieldSection, - false, - DateRangeValidationError - > {} - -type BrowserMultiInputDateRangeFieldComponent = (( - props: BrowserMultiInputDateRangeFieldProps & React.RefAttributes, -) => React.JSX.Element) & { propTypes?: any }; - -const BrowserMultiInputDateRangeField = React.forwardRef( - (props: BrowserMultiInputDateRangeFieldProps, ref: React.Ref) => { - const { - slotProps, - value, - defaultValue, - format, - onChange, - readOnly, - disabled, - onError, - shouldDisableDate, - minDate, - maxDate, - disableFuture, - disablePast, - selectedSections, - onSelectedSectionsChange, - className, - unstableStartFieldRef, - unstableEndFieldRef, - } = props; - - const startTextFieldProps = useSlotProps({ - elementType: 'input', - externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'start' }, - }) as MultiInputFieldSlotTextFieldProps; - - const endTextFieldProps = useSlotProps({ - elementType: 'input', - externalSlotProps: slotProps?.textField, - ownerState: { ...props, position: 'end' }, - }) as MultiInputFieldSlotTextFieldProps; - - const fieldResponse = useMultiInputDateRangeField< - Dayjs, - false, - MultiInputFieldSlotTextFieldProps - >({ - sharedProps: { - value, - defaultValue, - format, - onChange, - readOnly, - disabled, - onError, - shouldDisableDate, - minDate, - maxDate, - disableFuture, - disablePast, - selectedSections, - onSelectedSectionsChange, - enableAccessibleFieldDOMStructure: false, - }, - startTextFieldProps, - endTextFieldProps, - unstableStartFieldRef, - unstableEndFieldRef, - }); - - return ( - - - - - - ); - }, -) as BrowserMultiInputDateRangeFieldComponent; - -const BrowserDateRangePicker = React.forwardRef( - (props: DateRangePickerProps, ref: React.Ref) => { - return ( - - ); - }, -); - -export default function BrowserV6MultiInputRangeField() { - return ( - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx.preview b/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx.preview deleted file mode 100644 index d797406fa999..000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6MultiInputRangeField.tsx.preview +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.js b/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.js deleted file mode 100644 index 311029bbc6fa..000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.js +++ /dev/null @@ -1,137 +0,0 @@ -import * as React from 'react'; - -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import useSlotProps from '@mui/utils/useSlotProps'; -import Box from '@mui/material/Box'; -import IconButton from '@mui/material/IconButton'; -import InputAdornment from '@mui/material/InputAdornment'; -import { DateRangeIcon } from '@mui/x-date-pickers/icons'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { DateRangePicker } from '@mui/x-date-pickers-pro/DateRangePicker'; -import { unstable_useSingleInputDateRangeField as useSingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDateRangeField'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; - -const BrowserField = React.forwardRef((props, ref) => { - const { - // Should be ignored - enableAccessibleFieldDOMStructure, - disabled, - id, - label, - inputRef, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, - // extracting `error`, 'focused', and `ownerState` as `input` does not support those props - error, - focused, - ownerState, - sx, - ...other - } = props; - - const handleRef = useForkRef(containerRef, ref); - - return ( - - {startAdornment} - - {endAdornment} - - ); -}); - -const BrowserSingleInputDateRangeField = React.forwardRef((props, ref) => { - const { slots, slotProps, onAdornmentClick, ...other } = props; - - const textFieldProps = useSlotProps({ - elementType: 'input', - externalSlotProps: slotProps?.textField, - externalForwardedProps: other, - ownerState: props, - }); - - textFieldProps.InputProps = { - ...textFieldProps.InputProps, - endAdornment: ( - - - - - - ), - }; - - const fieldResponse = useSingleInputDateRangeField({ - ...textFieldProps, - enableAccessibleFieldDOMStructure: false, - }); - - /* If you don't need a clear button, you can skip the use of this hook */ - const processedFieldProps = useClearableField({ - ...fieldResponse, - slots, - slotProps, - }); - - return ( - - ); -}); - -BrowserSingleInputDateRangeField.fieldType = 'single-input'; - -const BrowserSingleInputDateRangePicker = React.forwardRef((props, ref) => { - const [isOpen, setIsOpen] = React.useState(false); - - const toggleOpen = () => setIsOpen((currentOpen) => !currentOpen); - - const handleOpen = () => setIsOpen(true); - - const handleClose = () => setIsOpen(false); - - return ( - - ); -}); - -export default function BrowserV6SingleInputRangeField() { - return ( - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx deleted file mode 100644 index 6db84b3f6c57..000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import * as React from 'react'; -import { Dayjs } from 'dayjs'; -import { unstable_useForkRef as useForkRef } from '@mui/utils'; -import useSlotProps from '@mui/utils/useSlotProps'; -import Box from '@mui/material/Box'; -import IconButton from '@mui/material/IconButton'; -import InputAdornment from '@mui/material/InputAdornment'; -import { DateRangeIcon } from '@mui/x-date-pickers/icons'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { - DateRangePicker, - DateRangePickerProps, -} from '@mui/x-date-pickers-pro/DateRangePicker'; -import { - unstable_useSingleInputDateRangeField as useSingleInputDateRangeField, - SingleInputDateRangeFieldProps, - UseSingleInputDateRangeFieldProps, -} from '@mui/x-date-pickers-pro/SingleInputDateRangeField'; -import { useClearableField } from '@mui/x-date-pickers/hooks'; -import { BaseSingleInputFieldProps } from '@mui/x-date-pickers/models'; -import { - DateRangeValidationError, - RangeFieldSection, - DateRange, - FieldType, -} from '@mui/x-date-pickers-pro/models'; - -interface BrowserFieldProps - extends Omit, 'size'> { - label?: React.ReactNode; - inputRef?: React.Ref; - InputProps?: { - ref?: React.Ref; - endAdornment?: React.ReactNode; - startAdornment?: React.ReactNode; - }; - error?: boolean; - focused?: boolean; - ownerState?: any; - sx?: any; - enableAccessibleFieldDOMStructure: boolean; -} - -type BrowserFieldComponent = (( - props: BrowserFieldProps & React.RefAttributes, -) => React.JSX.Element) & { propTypes?: any }; - -const BrowserField = React.forwardRef( - (props: BrowserFieldProps, ref: React.Ref) => { - const { - // Should be ignored - enableAccessibleFieldDOMStructure, - - disabled, - id, - label, - inputRef, - InputProps: { ref: containerRef, startAdornment, endAdornment } = {}, - // extracting `error`, 'focused', and `ownerState` as `input` does not support those props - error, - focused, - ownerState, - sx, - ...other - } = props; - - const handleRef = useForkRef(containerRef, ref); - - return ( - - {startAdornment} - - {endAdornment} - - ); - }, -) as BrowserFieldComponent; - -interface BrowserSingleInputDateRangeFieldProps - extends UseSingleInputDateRangeFieldProps, - BaseSingleInputFieldProps< - DateRange, - Dayjs, - RangeFieldSection, - false, - DateRangeValidationError - > { - onAdornmentClick?: () => void; -} - -type BrowserSingleInputDateRangeFieldComponent = (( - props: BrowserSingleInputDateRangeFieldProps & React.RefAttributes, -) => React.JSX.Element) & { fieldType?: FieldType }; - -const BrowserSingleInputDateRangeField = React.forwardRef( - (props: BrowserSingleInputDateRangeFieldProps, ref: React.Ref) => { - const { slots, slotProps, onAdornmentClick, ...other } = props; - - const textFieldProps: SingleInputDateRangeFieldProps = - useSlotProps({ - elementType: 'input', - externalSlotProps: slotProps?.textField, - externalForwardedProps: other, - ownerState: props as any, - }); - - textFieldProps.InputProps = { - ...textFieldProps.InputProps, - endAdornment: ( - - - - - - ), - }; - - const fieldResponse = useSingleInputDateRangeField< - Dayjs, - false, - typeof textFieldProps - >({ ...textFieldProps, enableAccessibleFieldDOMStructure: false }); - - /* If you don't need a clear button, you can skip the use of this hook */ - const processedFieldProps = useClearableField({ - ...fieldResponse, - slots, - slotProps, - }); - - return ( - - ); - }, -) as BrowserSingleInputDateRangeFieldComponent; - -BrowserSingleInputDateRangeField.fieldType = 'single-input'; - -const BrowserSingleInputDateRangePicker = React.forwardRef( - (props: DateRangePickerProps, ref: React.Ref) => { - const [isOpen, setIsOpen] = React.useState(false); - - const toggleOpen = () => setIsOpen((currentOpen) => !currentOpen); - - const handleOpen = () => setIsOpen(true); - - const handleClose = () => setIsOpen(false); - - return ( - - ); - }, -); - -export default function BrowserV6SingleInputRangeField() { - return ( - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx.preview b/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx.preview deleted file mode 100644 index bcaf8043948f..000000000000 --- a/docs/data/date-pickers/custom-field/BrowserV6SingleInputRangeField.tsx.preview +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx index c4ef19f2f15b..b45092eb35ef 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV7MultiInputRangeField.tsx @@ -201,7 +201,7 @@ const BrowserMultiInputDateRangeField = React.forwardRef( ) as BrowserMultiInputDateRangeFieldComponent; const BrowserDateRangePicker = React.forwardRef( - (props: DateRangePickerProps, ref: React.Ref) => { + (props: DateRangePickerProps, ref: React.Ref) => { return ( - - + + ); diff --git a/docs/data/date-pickers/custom-field/MaterialV6Field.tsx b/docs/data/date-pickers/custom-field/MaterialV6Field.tsx index 35d4e0ca73ea..98a5d2d58c27 100644 --- a/docs/data/date-pickers/custom-field/MaterialV6Field.tsx +++ b/docs/data/date-pickers/custom-field/MaterialV6Field.tsx @@ -9,8 +9,8 @@ export default function MaterialV6Field() { return ( - - + + ); diff --git a/docs/data/date-pickers/custom-field/MaterialV6Field.tsx.preview b/docs/data/date-pickers/custom-field/MaterialV6Field.tsx.preview index 027cb7beacfa..9041ed886d8b 100644 --- a/docs/data/date-pickers/custom-field/MaterialV6Field.tsx.preview +++ b/docs/data/date-pickers/custom-field/MaterialV6Field.tsx.preview @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.js b/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.js deleted file mode 100644 index df1b5053a745..000000000000 --- a/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.js +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from 'react'; -import MuiTextField from '@mui/material/TextField'; -import { DemoContainer } from '@mui/x-date-pickers/internals/demo'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DateField } from '@mui/x-date-pickers/DateField'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; - -const TextField = React.forwardRef((props, ref) => ( - -)); - -export default function MaterialV6FieldWrapped() { - return ( - - - - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.tsx b/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.tsx deleted file mode 100644 index 0c90908a7d4b..000000000000 --- a/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from 'react'; -import MuiTextField, { TextFieldProps } from '@mui/material/TextField'; -import { DemoContainer } from '@mui/x-date-pickers/internals/demo'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DateField } from '@mui/x-date-pickers/DateField'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; - -const TextField = React.forwardRef( - (props: TextFieldProps, ref: React.Ref) => ( - - ), -); - -export default function MaterialV6FieldWrapped() { - return ( - - - - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.tsx.preview b/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.tsx.preview deleted file mode 100644 index cd5ff731abbc..000000000000 --- a/docs/data/date-pickers/custom-field/MaterialV6FieldWrapped.tsx.preview +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/MaterialV7Field.js b/docs/data/date-pickers/custom-field/MaterialV7Field.js deleted file mode 100644 index 07ba449750d6..000000000000 --- a/docs/data/date-pickers/custom-field/MaterialV7Field.js +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; -import { DemoContainer } 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 { DateField } from '@mui/x-date-pickers/DateField'; - -export default function MaterialV7Field() { - return ( - - - - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/MaterialV7Field.tsx b/docs/data/date-pickers/custom-field/MaterialV7Field.tsx deleted file mode 100644 index 07ba449750d6..000000000000 --- a/docs/data/date-pickers/custom-field/MaterialV7Field.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; -import { DemoContainer } 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 { DateField } from '@mui/x-date-pickers/DateField'; - -export default function MaterialV7Field() { - return ( - - - - - - - ); -} diff --git a/docs/data/date-pickers/custom-field/MaterialV7Field.tsx.preview b/docs/data/date-pickers/custom-field/MaterialV7Field.tsx.preview deleted file mode 100644 index 9708ed359729..000000000000 --- a/docs/data/date-pickers/custom-field/MaterialV7Field.tsx.preview +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.js b/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.js index d125a8479b5c..22560a3bcc80 100644 --- a/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.js +++ b/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.js @@ -14,14 +14,8 @@ export default function MaterialV7FieldWrapped() { return ( - - + + ); diff --git a/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx b/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx index 82e06da7316b..d054e12c04de 100644 --- a/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx +++ b/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx @@ -19,14 +19,8 @@ export default function MaterialV7FieldWrapped() { return ( - - + + ); diff --git a/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx.preview b/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx.preview index 17535291f5f3..a31c9c5bc351 100644 --- a/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx.preview +++ b/docs/data/date-pickers/custom-field/MaterialV7FieldWrapped.tsx.preview @@ -1,8 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/PickerWithAutocompleteField.tsx b/docs/data/date-pickers/custom-field/PickerWithAutocompleteField.tsx index 1aac33d076c0..92e99e278e87 100644 --- a/docs/data/date-pickers/custom-field/PickerWithAutocompleteField.tsx +++ b/docs/data/date-pickers/custom-field/PickerWithAutocompleteField.tsx @@ -14,12 +14,12 @@ import { } from '@mui/x-date-pickers/models'; interface AutoCompleteFieldProps - extends UseDateFieldProps, + extends UseDateFieldProps, BaseSingleInputFieldProps< Dayjs | null, Dayjs, FieldSection, - false, + true, DateValidationError > { /** diff --git a/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx b/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx index aa09717fb29c..8acbab77d7c4 100644 --- a/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx +++ b/docs/data/date-pickers/custom-field/PickerWithButtonField.tsx @@ -12,12 +12,12 @@ import { } from '@mui/x-date-pickers/models'; interface ButtonFieldProps - extends UseDateFieldProps, + extends UseDateFieldProps, BaseSingleInputFieldProps< Dayjs | null, Dayjs, FieldSection, - false, + true, DateValidationError > { setOpen?: React.Dispatch>; diff --git a/docs/data/date-pickers/custom-field/custom-behavior/MaskedMaterialTextField.js b/docs/data/date-pickers/custom-field/custom-behavior/MaskedMaterialTextField.js new file mode 100644 index 000000000000..0723d4565c51 --- /dev/null +++ b/docs/data/date-pickers/custom-field/custom-behavior/MaskedMaterialTextField.js @@ -0,0 +1,164 @@ +import * as React from 'react'; +import dayjs from 'dayjs'; +import { useRifm } from 'rifm'; +import TextField from '@mui/material/TextField'; +import useControlled from '@mui/utils/useControlled'; +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 { useSplitFieldProps, useParsedFormat } from '@mui/x-date-pickers/hooks'; +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; + +const MASK_USER_INPUT_SYMBOL = '_'; +const ACCEPT_REGEX = /[\d]/gi; + +const staticDateWith2DigitTokens = dayjs('2019-11-21T11:30:00.000'); +const staticDateWith1DigitTokens = dayjs('2019-01-01T09:00:00.000'); + +function getValueStrFromValue(value, format) { + if (value == null) { + return ''; + } + + return value.isValid() ? value.format(format) : ''; +} + +function MaskedField(props) { + const { slots, slotProps, ...other } = props; + + const { forwardedProps, internalProps } = useSplitFieldProps(other, 'date'); + + const { + format, + value: valueProp, + defaultValue, + onChange, + timezone, + onError, + } = internalProps; + + const [value, setValue] = useControlled({ + controlled: valueProp, + default: defaultValue ?? null, + name: 'MaskedField', + state: 'value', + }); + + // Control the input text + const [inputValue, setInputValue] = React.useState(() => + getValueStrFromValue(value, format), + ); + + React.useEffect(() => { + if (value && value.isValid()) { + const newDisplayDate = getValueStrFromValue(value, format); + setInputValue(newDisplayDate); + } + }, [format, value]); + + const parsedFormat = useParsedFormat(internalProps); + + const { hasValidationError, getValidationErrorForNewValue } = useValidation({ + value, + timezone, + onError, + props: internalProps, + validator: validateDate, + }); + + const handleValueStrChange = (newValueStr) => { + setInputValue(newValueStr); + + const newValue = dayjs(newValueStr, format); + setValue(newValue); + + if (onChange) { + onChange(newValue, { + validationError: getValidationErrorForNewValue(newValue), + }); + } + }; + + const rifmFormat = React.useMemo(() => { + const formattedDateWith1Digit = staticDateWith1DigitTokens.format(format); + const inferredFormatPatternWith1Digits = formattedDateWith1Digit.replace( + ACCEPT_REGEX, + MASK_USER_INPUT_SYMBOL, + ); + const inferredFormatPatternWith2Digits = staticDateWith2DigitTokens + .format(format) + .replace(ACCEPT_REGEX, '_'); + + if (inferredFormatPatternWith1Digits !== inferredFormatPatternWith2Digits) { + throw new Error( + `Mask does not support numbers with variable length such as 'M'.`, + ); + } + + const maskToUse = inferredFormatPatternWith1Digits; + + return function formatMaskedDate(valueToFormat) { + let outputCharIndex = 0; + return valueToFormat + .split('') + .map((character, characterIndex) => { + ACCEPT_REGEX.lastIndex = 0; + + if (outputCharIndex > maskToUse.length - 1) { + return ''; + } + + const maskChar = maskToUse[outputCharIndex]; + const nextMaskChar = maskToUse[outputCharIndex + 1]; + + const acceptedChar = ACCEPT_REGEX.test(character) ? character : ''; + const formattedChar = + maskChar === MASK_USER_INPUT_SYMBOL + ? acceptedChar + : maskChar + acceptedChar; + + outputCharIndex += formattedChar.length; + + const isLastCharacter = characterIndex === valueToFormat.length - 1; + if ( + isLastCharacter && + nextMaskChar && + nextMaskChar !== MASK_USER_INPUT_SYMBOL + ) { + // when cursor at the end of mask part (e.g. month) prerender next symbol "21" -> "21/" + return formattedChar ? formattedChar + nextMaskChar : ''; + } + + return formattedChar; + }) + .join(''); + }; + }, [format]); + + const rifmProps = useRifm({ + value: inputValue, + onChange: handleValueStrChange, + format: rifmFormat, + }); + + return ( + + ); +} + +function MaskedFieldDatePicker(props) { + return ; +} + +export default function MaskedMaterialTextField() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/custom-behavior/MaskedMaterialTextField.tsx b/docs/data/date-pickers/custom-field/custom-behavior/MaskedMaterialTextField.tsx new file mode 100644 index 000000000000..a589022eada6 --- /dev/null +++ b/docs/data/date-pickers/custom-field/custom-behavior/MaskedMaterialTextField.tsx @@ -0,0 +1,168 @@ +import * as React from 'react'; +import dayjs, { Dayjs } from 'dayjs'; +import { useRifm } from 'rifm'; +import TextField from '@mui/material/TextField'; +import useControlled from '@mui/utils/useControlled'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { + DatePicker, + DatePickerProps, + DatePickerFieldProps, +} from '@mui/x-date-pickers/DatePicker'; +import { useSplitFieldProps, useParsedFormat } from '@mui/x-date-pickers/hooks'; +import { useValidation, validateDate } from '@mui/x-date-pickers/validation'; + +const MASK_USER_INPUT_SYMBOL = '_'; +const ACCEPT_REGEX = /[\d]/gi; + +const staticDateWith2DigitTokens = dayjs('2019-11-21T11:30:00.000'); +const staticDateWith1DigitTokens = dayjs('2019-01-01T09:00:00.000'); + +function getValueStrFromValue(value: Dayjs | null, format: string) { + if (value == null) { + return ''; + } + + return value.isValid() ? value.format(format) : ''; +} + +function MaskedField(props: DatePickerFieldProps) { + const { slots, slotProps, ...other } = props; + + const { forwardedProps, internalProps } = useSplitFieldProps(other, 'date'); + + const { + format, + value: valueProp, + defaultValue, + onChange, + timezone, + onError, + } = internalProps; + + const [value, setValue] = useControlled({ + controlled: valueProp, + default: defaultValue ?? null, + name: 'MaskedField', + state: 'value', + }); + + // Control the input text + const [inputValue, setInputValue] = React.useState(() => + getValueStrFromValue(value, format), + ); + + React.useEffect(() => { + if (value && value.isValid()) { + const newDisplayDate = getValueStrFromValue(value, format); + setInputValue(newDisplayDate); + } + }, [format, value]); + + const parsedFormat = useParsedFormat(internalProps); + + const { hasValidationError, getValidationErrorForNewValue } = useValidation({ + value, + timezone, + onError, + props: internalProps, + validator: validateDate, + }); + + const handleValueStrChange = (newValueStr: string) => { + setInputValue(newValueStr); + + const newValue = dayjs(newValueStr, format); + setValue(newValue); + + if (onChange) { + onChange(newValue, { + validationError: getValidationErrorForNewValue(newValue), + }); + } + }; + + const rifmFormat = React.useMemo(() => { + const formattedDateWith1Digit = staticDateWith1DigitTokens.format(format); + const inferredFormatPatternWith1Digits = formattedDateWith1Digit.replace( + ACCEPT_REGEX, + MASK_USER_INPUT_SYMBOL, + ); + const inferredFormatPatternWith2Digits = staticDateWith2DigitTokens + .format(format) + .replace(ACCEPT_REGEX, '_'); + + if (inferredFormatPatternWith1Digits !== inferredFormatPatternWith2Digits) { + throw new Error( + `Mask does not support numbers with variable length such as 'M'.`, + ); + } + + const maskToUse = inferredFormatPatternWith1Digits; + + return function formatMaskedDate(valueToFormat: string) { + let outputCharIndex = 0; + return valueToFormat + .split('') + .map((character, characterIndex) => { + ACCEPT_REGEX.lastIndex = 0; + + if (outputCharIndex > maskToUse.length - 1) { + return ''; + } + + const maskChar = maskToUse[outputCharIndex]; + const nextMaskChar = maskToUse[outputCharIndex + 1]; + + const acceptedChar = ACCEPT_REGEX.test(character) ? character : ''; + const formattedChar = + maskChar === MASK_USER_INPUT_SYMBOL + ? acceptedChar + : maskChar + acceptedChar; + + outputCharIndex += formattedChar.length; + + const isLastCharacter = characterIndex === valueToFormat.length - 1; + if ( + isLastCharacter && + nextMaskChar && + nextMaskChar !== MASK_USER_INPUT_SYMBOL + ) { + // when cursor at the end of mask part (e.g. month) prerender next symbol "21" -> "21/" + return formattedChar ? formattedChar + nextMaskChar : ''; + } + + return formattedChar; + }) + .join(''); + }; + }, [format]); + + const rifmProps = useRifm({ + value: inputValue, + onChange: handleValueStrChange, + format: rifmFormat, + }); + + return ( + + ); +} + +function MaskedFieldDatePicker(props: DatePickerProps) { + return ; +} + +export default function MaskedMaterialTextField() { + return ( + + + + ); +} diff --git a/docs/data/date-pickers/custom-field/custom-behavior/MaskedMaterialTextField.tsx.preview b/docs/data/date-pickers/custom-field/custom-behavior/MaskedMaterialTextField.tsx.preview new file mode 100644 index 000000000000..1340a82dd55d --- /dev/null +++ b/docs/data/date-pickers/custom-field/custom-behavior/MaskedMaterialTextField.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx b/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx index c9cdc16eae4e..04223bd73a6e 100644 --- a/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx +++ b/docs/data/date-pickers/custom-field/custom-behavior/ReadOnlyMaterialTextField.tsx @@ -16,7 +16,7 @@ import { } from '@mui/x-date-pickers/hooks'; import { CalendarIcon } from '@mui/x-date-pickers/icons'; -function ReadOnlyDateField(props: DatePickerFieldProps) { +function ReadOnlyDateField(props: DatePickerFieldProps) { const { internalProps, forwardedProps } = useSplitFieldProps(props, 'date'); const { value, timezone, format } = internalProps; const { InputProps, slotProps, slots, ...other } = forwardedProps; diff --git a/docs/data/date-pickers/custom-field/custom-field.md b/docs/data/date-pickers/custom-field/custom-field.md index 4f5b5f782fb3..9c4cc5216bb3 100644 --- a/docs/data/date-pickers/custom-field/custom-field.md +++ b/docs/data/date-pickers/custom-field/custom-field.md @@ -66,30 +66,9 @@ Setting `formatDensity` to `"spacious"` will add a space before and after each ` {{"demo": "FieldFormatDensity.js"}} -## Usage with Material UI +## With Material UI -### Using Material `TextField` - -You can import the `TextField` component to create custom wrappers: - -{{"demo": "MaterialV6FieldWrapped.js"}} - -:::success -This approach is only recommended if you need complex customizations on your `TextField`, -or if you already have a wrapper also used outside the Date and Time Pickers. - -If you just need to set some default props, you can use [the `slotProps` prop](/x/react-date-pickers/custom-field/#customize-the-textfield). -::: - -### Using Material `PickersTextField` - -Pass the `enableAccessibleFieldDOMStructure` to any Field or Picker component to enable the accessible DOM structure: - -{{"demo": "MaterialV7Field.js"}} - -:::success -Learn more about the [accessible DOM structure](/x/react-date-pickers/fields/#accessible-dom-structure). -::: +### Wrapping `PickersTextField` You can import the `PickersTextField` component to create custom wrappers: @@ -101,55 +80,50 @@ This approach is only recommended if you need complex customizations on your `Pi If you just need to set some default props, you can use [the `slotProps` prop](/x/react-date-pickers/custom-field/#customize-the-textfield). ::: -## Usage with Joy UI +### Using Material `TextField` -### Using Joy `Input` +Pass the `enableAccessibleFieldDOMStructure={false}` to any Field or Picker component to use an `` for the editing instead of the new accessible DOM structure: -You can use the [Joy UI](https://mui.com/joy-ui/getting-started/) components instead of the Material UI ones: +{{"demo": "MaterialV6Field.js"}} -:::info -A higher-level solution for _Joy UI_ will be provided in the near future for even simpler usage. +:::warning +The non-accessible DOM structure will be deprecated in a follow up minor version and remove in `v9.x`. +If you are unable to migrate for some reason, please open an issue to describe what is missing from the new DOM structure so that we can improve it before dropping the old one. ::: -{{"demo": "JoyV6Field.js", "defaultCodeOpen": false}} - -{{"demo": "JoyV6SingleInputRangeField.js", "defaultCodeOpen": false}} - -{{"demo": "JoyV6MultiInputRangeField.js", "defaultCodeOpen": false}} +## With another Design System -### Using Joy `PickersTextField` +### Using a custom input :::warning -This component is not available yet. +You will need to use a component that supports the `sx` prop as a wrapper for your input +to be able to benefit from the **hover** and **focus** behavior of the clear button. +You will have access to the `clearable` and `onClear` props using native HTML elements, +but the on **focus** and **hover** behavior depends on styles applied via the `sx` prop. ::: -## Usage with an unstyled input - -### Using the browser input +{{"demo": "BrowserV7Field.js", "defaultCodeOpen": false}} -{{"demo": "BrowserV6Field.js", "defaultCodeOpen": false}} +{{"demo": "BrowserV7SingleInputRangeField.js", "defaultCodeOpen": false}} -{{"demo": "BrowserV6SingleInputRangeField.js", "defaultCodeOpen": false}} +{{"demo": "BrowserV7MultiInputRangeField.js", "defaultCodeOpen": false}} -{{"demo": "BrowserV6MultiInputRangeField.js", "defaultCodeOpen": false}} +### Using Joy UI -:::warning -You will need to use a component that supports the `sx` prop as a wrapper for your input, in order to be able to benefit from the **hover** and **focus** behavior of the clear button. You will have access to the `clearable` and `onClear` props using native HTML elements, but the on **focus** and **hover** behavior depends on styles applied via the `sx` prop. -::: - -### Using custom `PickersTextField` +You can use the [Joy UI](https://mui.com/joy-ui/getting-started/) components instead of the Material UI ones: -:::success -Learn more about the accessible DOM structure and its difference compared to the current one on the [dedicated doc section](/x/react-date-pickers/fields/#accessible-dom-structure). -::: +{{"demo": "JoyV6Field.js", "defaultCodeOpen": false}} -{{"demo": "BrowserV7Field.js", "defaultCodeOpen": false}} +{{"demo": "JoyV6SingleInputRangeField.js", "defaultCodeOpen": false}} -{{"demo": "BrowserV7SingleInputRangeField.js", "defaultCodeOpen": false}} +{{"demo": "JoyV6MultiInputRangeField.js", "defaultCodeOpen": false}} -{{"demo": "BrowserV7MultiInputRangeField.js", "defaultCodeOpen": false}} +:::warning +All the Joy UI examples use the non-accessible DOM structure. +The new accessible DOM structure will become compatible with Joy UI in the future. +::: -## Usage with another UI +## With a custom editing experience ### Using an `Autocomplete` @@ -158,6 +132,12 @@ you can replace the field with an `Autocomplete` listing those dates: {{"demo": "PickerWithAutocompleteField.js", "defaultCodeOpen": false}} +### Using a masked Text Field + +If you want to use a simple mask approach for the field editing instead of the built-in logic, you can replace the default field with a Text Field using a masked input value built with the [rifm](https://github.com/realadvisor/rifm) package. + +{{"demo": "custom-behavior/MaskedMaterialTextField.js", "defaultCodeOpen": false}} + ### Using a read-only `TextField` If you want users to select a value exclusively through the views @@ -184,22 +164,22 @@ On the examples below, you can see that the typing of the props received by a cu ```tsx interface JoyDateFieldProps - extends UseDateFieldProps, // The headless field props + extends UseDateFieldProps, // The headless field props BaseSingleInputFieldProps< Dayjs | null, Dayjs, FieldSection, - false, // `true` for `enableAccessibleFieldDOMStructure` + true, // `false` for `enableAccessibleFieldDOMStructure={false}` DateValidationError > {} // The DOM field props interface JoyDateTimeFieldProps - extends UseDateTimeFieldProps, // The headless field props + extends UseDateTimeFieldProps, // The headless field props BaseSingleInputFieldProps< Dayjs | null, Dayjs, FieldSection, - false, // `true` for `enableAccessibleFieldDOMStructure` + true, // `false` for `enableAccessibleFieldDOMStructure={false}` DateTimeValidationError > {} // The DOM field props ``` diff --git a/docs/data/date-pickers/fields/BasicV7DOMStructure.js b/docs/data/date-pickers/fields/BasicV7DOMStructure.js index 1f32eca967da..62c0924c3c90 100644 --- a/docs/data/date-pickers/fields/BasicV7DOMStructure.js +++ b/docs/data/date-pickers/fields/BasicV7DOMStructure.js @@ -8,7 +8,7 @@ export default function BasicV7DOMStructure() { return ( - + ); diff --git a/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx b/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx index 1f32eca967da..62c0924c3c90 100644 --- a/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx +++ b/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx @@ -8,7 +8,7 @@ export default function BasicV7DOMStructure() { return ( - + ); diff --git a/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx.preview b/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx.preview index f33bc6c8fdc7..2dc079345c09 100644 --- a/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx.preview +++ b/docs/data/date-pickers/fields/BasicV7DOMStructure.tsx.preview @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/data/date-pickers/fields/fields.md b/docs/data/date-pickers/fields/fields.md index 5f54b1411f66..9bd7260fff46 100644 --- a/docs/data/date-pickers/fields/fields.md +++ b/docs/data/date-pickers/fields/fields.md @@ -25,201 +25,6 @@ All fields to edit a range are available in a single input version and in a mult {{"demo": "DateRangeFieldExamples.js", "defaultCodeOpen": false}} -## Accessible DOM structure - -By default, the fields' DOM structure consists of an ``, which holds the whole value for the component, but unfortunately presents a few limitations in terms of accessibility when managing multiple section values. - -From v7 version, you can opt-in for a new and experimental DOM structure on any field or picker component using the `enableAccessibleFieldDOMStructure` prop. - -```tsx - - - -``` - -This new feature allows the field component to set aria attributes on individual sections, providing a far better experience with screen readers. - -{{"demo": "BasicV7DOMStructure.js", "defaultCodeOpen": false }} - -### Usage with `slotProps.field` - -When using `slotProps.field` to pass props to your field component, -the field consumes some props (e.g: `shouldRespectLeadingZeros`) and forwards the rest to the `TextField`. - -- For the props consumed by the field, the behavior should remain exactly the same with both DOM structures. - - Both components below will respect the leading zeroes on digit sections: - - ```js - - - ``` - -- For the props forwarded to the `TextField`, - you can have a look at the next section to see how the migration impact them. - - Both components below will render a small size UI: - - ```js - - - ``` - -### Usage with `slotProps.textField` - -If you are passing props to `slotProps.textField`, -these props will now be received by `PickersTextField` and should keep working the same way as before. - -Both components below will render a small size UI: - -```js - - -``` - -:::info -If you are passing `inputProps` to `slotProps.textField`, -these props will now be passed to the hidden `` element. -::: - -### Usage with `slots.field` - -If you are passing a custom field component to your pickers, you need to create a new one that is using the accessible DOM structure. -This new component will need to use the `PickersSectionList` component instead of an `` HTML element. - -You can have a look at the [custom PickersTextField](/x/react-date-pickers/custom-field/#using-custom-pickerstextfield) to have a concrete example. - -:::info -If your custom field was used to create a Joy UI design component, -you may want to wait a few weeks for the release of an out-of-the-box Joy `PickersTextField` component instead of implementing it yourself. -::: - -### Usage with `slots.textField` - -If you are passing a custom `TextField` component to your fields and pickers, -you need to create a new one that is using the accessible DOM structure. - -You can have a look at the second demo of the [Material PickersTextField section](/x/react-date-pickers/custom-field/#using-material-pickerstextfield) to have a concrete example. - -:::info -If your custom `TextField` was used to apply a totally different input that did not use `@mui/material/TextField`, -please consider having a look at the [custom PickersTextField](/x/react-date-pickers/custom-field/#using-custom-pickerstextfield) section which uses `slots.field`. -This approach can be more appropriate for deeper changes. -::: - -### Usage with theme - -If you are using the theme to customize `MuiTextField`, -you need to pass the same config to `MuiPickersTextField`: - -```js -const theme = createTheme({ - components: { - MuiTextField: { - defaultProps: { - variant: 'outlined', - }, - styleOverrides: { - root: { - '& .MuiInputLabel-outlined.Mui-focused': { - color: 'red', - }, - }, - }, - }, - MuiPickersTextField: { - defaultProps: { - variant: 'outlined', - }, - styleOverrides: { - root: { - '& .MuiInputLabel-outlined.Mui-focused': { - color: 'red', - }, - }, - }, - }, - }, -}); -``` - -If you are using the theme to customize `MuiInput`, `MuiOutlinedInput` or `MuiFilledInput`, -you need to pass the same config to `MuiPickersInput`, `MuiPickersOutlinedInput` or `MuiPickersFilledInput`: - -```js -const theme = createTheme({ - components: { - // Replace with `MuiOutlinedInput` or `MuiFilledInput` if needed - MuiInput: { - defaultProps: { - margin: 'dense', - }, - styleOverrides: { - root: { - color: 'red', - }, - }, - }, - // Replace with `MuiPickersOutlinedInput` or `MuiPickersFilledInput` if needed - MuiPickersInput: { - defaultProps: { - margin: 'dense', - }, - styleOverrides: { - root: { - color: 'red', - }, - }, - }, - }, -}); -``` - -If you are using the theme to customize `MuiInputBase`, -you need to pass the same config to `MuiPickersInputBase`: - -```js -const theme = createTheme({ - components: { - MuiInputBase: { - defaultProps: { - margin: 'dense', - }, - styleOverrides: { - root: { - color: 'red', - }, - }, - }, - MuiPickersInputBase: { - defaultProps: { - margin: 'dense', - }, - styleOverrides: { - root: { - color: 'red', - }, - }, - }, - }, -}); -``` - ## Advanced ### What is a section? diff --git a/docs/data/date-pickers/localization/localization.md b/docs/data/date-pickers/localization/localization.md index 9c17517d832a..dab97f4bcb5d 100644 --- a/docs/data/date-pickers/localization/localization.md +++ b/docs/data/date-pickers/localization/localization.md @@ -46,7 +46,7 @@ function App({ children }) { } ``` -Note that `createTheme` accepts any number of arguments. +Note that `createTheme()` accepts any number of arguments. If you are already using the [translations of the core components](/material-ui/guides/localization/#locale-text) or the [translations of the Data Grid](/x/react-data-grid/localization/#locale-text), you can add `deDE` as a new argument. ```jsx @@ -73,7 +73,7 @@ function App({ children }) { ### Using LocalizationProvider -If you want to pass language translations without using `createTheme` and `ThemeProvider`, +If you want to pass language translations without using `createTheme()` and `ThemeProvider`, you can directly load the language translations from the `@mui/x-date-pickers` or `@mui/x-date-pickers-pro` package and pass them to the `LocalizationProvider`. ```jsx diff --git a/docs/data/date-pickers-component-api-pages.ts b/docs/data/datePickersApiPages.ts similarity index 98% rename from docs/data/date-pickers-component-api-pages.ts rename to docs/data/datePickersApiPages.ts index f7d4b339ac4f..339472cbea73 100644 --- a/docs/data/date-pickers-component-api-pages.ts +++ b/docs/data/datePickersApiPages.ts @@ -1,6 +1,6 @@ import type { MuiPage } from 'docs/src/MuiPage'; -const apiPages: MuiPage[] = [ +const datePickersApiPages: MuiPage[] = [ { pathname: '/x/api/date-pickers/date-calendar', title: 'DateCalendar', @@ -233,4 +233,4 @@ const apiPages: MuiPage[] = [ title: 'YearCalendar', }, ]; -export default apiPages; +export default datePickersApiPages; diff --git a/docs/data/introduction/support/support.md b/docs/data/introduction/support/support.md index 3f65903d7dfb..ebba1a99214b 100644 --- a/docs/data/introduction/support/support.md +++ b/docs/data/introduction/support/support.md @@ -42,7 +42,7 @@ You can browse the documentation, find an example close to your use case, and th - [Data Grid](/x/react-data-grid/#mit-license-free-forever) - [Date Pickers](/x/react-date-pickers/getting-started/#render-your-first-component) -- [Charts](/x/react-charts/getting-started/#single-charts) +- [Charts](/x/react-charts/getting-started/#self-contained-charts) - [Tree View](/x/react-tree-view/#simple-tree-view) #### Use starter templates diff --git a/docs/data/migration/migration-charts-v7/migration-charts-v7.md b/docs/data/migration/migration-charts-v7/migration-charts-v7.md new file mode 100644 index 000000000000..a23f9406feff --- /dev/null +++ b/docs/data/migration/migration-charts-v7/migration-charts-v7.md @@ -0,0 +1,34 @@ +--- +productId: x-charts +--- + +# Migration from v7 to v8 + +

This guide describes the changes needed to migrate Charts from v7 to v8.

+ +## Introduction + +This is a reference guide for upgrading `@mui/x-charts` from v7 to v8. +The change between v7 and v8 is mostly here to match the version with other MUI X packages. +No big breaking changes are expected. + +## Start using the new release + +In `package.json`, change the version of the charts package to `next`. + +```diff +-"@mui/x-charts": "^7.0.0", ++"@mui/x-charts": "next", +``` + +Using `next` ensures that it will always use the latest v8 pre-release version, but you can also use a fixed version, like `8.0.0-alpha.0`. + +## Breaking changes + +Since v8 is a major release, it contains some changes that affect the public API. +These changes were done for consistency, improve stability and make room for new features. +Below are described the steps you need to make to migrate from v7 to v8. + +:::info +The list is currently empty, but as we move forward with development during the alpha and beta phases, we'll feed this page with all changes in the API. +::: diff --git a/docs/data/migration/migration-data-grid-v7/migration-data-grid-v7.md b/docs/data/migration/migration-data-grid-v7/migration-data-grid-v7.md new file mode 100644 index 000000000000..eea548d0fbc0 --- /dev/null +++ b/docs/data/migration/migration-data-grid-v7/migration-data-grid-v7.md @@ -0,0 +1,78 @@ +--- +productId: x-data-grid +--- + +# Migration from v7 to v8 + +

This guide describes the changes needed to migrate the Data Grid from v7 to v8.

+ +## Introduction + +This is a reference guide for upgrading `@mui/x-data-grid` from v7 to v8. + +## Start using the new release + +In `package.json`, change the version of the Data Grid package to `next`. + +```diff +-"@mui/x-data-grid": "^7.0.0", ++"@mui/x-data-grid": "next", + +-"@mui/x-data-grid-pro": "^7.0.0", ++"@mui/x-data-grid-pro": "next", + +-"@mui/x-data-grid-premium": "^7.0.0", ++"@mui/x-data-grid-premium": "next", +``` + +Using `next` ensures that it will always use the latest v8 pre-release version, but you can also use a fixed version, like `8.0.0-alpha.0`. + +## Breaking changes + +Since v8 is a major release, it contains some changes that affect the public API. +These changes were done for consistency, improve stability and make room for new features. +Below are described the steps you need to make to migrate from v7 to v8. + +:::info +The list is currently empty, but as we move forward with development during the alpha and beta phases, we'll feed this page with all changes in the API. +::: + + diff --git a/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md b/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md index 787bbefb3182..ab73ca9e1900 100644 --- a/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md +++ b/docs/data/migration/migration-pickers-v6/migration-pickers-v6.md @@ -440,7 +440,7 @@ The headless field hooks (e.g.: `useDateField`) now return a new prop called `en This is used to know if the current UI expected is built using the accessible DOM structure or not. :::info -See [Fields—Accessible DOM structure](/x/react-date-pickers/fields/#accessible-dom-structure) for more details. +See [Migration from v7 to v8—New DOM structure for the field](/x/migration/migration-pickers-v7/#new-dom-structure-for-the-field) for more details. ::: When building a custom UI, you are most-likely only supporting one DOM structure, so you can remove `enableAccessibleFieldDOMStructure` before it is passed to the DOM: diff --git a/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md b/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md new file mode 100644 index 000000000000..18a1f57af80f --- /dev/null +++ b/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md @@ -0,0 +1,316 @@ +--- +productId: x-date-pickers +--- + +# Migration from v7 to v8 + +

This guide describes the changes needed to migrate the Date and Time Pickers from v7 to v8.

+ +## Introduction + +This is a reference guide for upgrading `@mui/x-date-pickers` from v7 to v8. + +## Start using the new release + +In `package.json`, change the version of the date pickers package to `next`. + +```diff +-"@mui/x-date-pickers": "7.x.x", ++"@mui/x-date-pickers": "next", +``` + +Using `next` ensures that it will always use the latest v8 pre-release version, but you can also use a fixed version, like `8.0.0-alpha.0`. + +Since `v8` is a major release, it contains changes that affect the public API. +These changes were done for consistency, improved stability and to make room for new features. +Described below are the steps needed to migrate from v7 to v8. + +## Run codemods + +The `preset-safe` codemod will automatically adjust the bulk of your code to account for breaking changes in v8. You can run `v8.0.0/pickers/preset-safe` targeting only Date and Time Pickers or `v8.0.0/preset-safe` to target the other packages as well. + +You can either run it on a specific file, folder, or your entire codebase when choosing the `` argument. + + + +```bash +// Date and Time Pickers specific +npx @mui/x-codemod@latest v8.0.0/pickers/preset-safe + +// Target the other packages as well +npx @mui/x-codemod@latest v8.0.0/preset-safe +``` + +:::info +If you want to run the transformers one by one, check out the transformers included in the [preset-safe codemod for pickers](https://github.com/mui/mui-x/blob/HEAD/packages/x-codemod/README.md#preset-safe-for-pickers-v800) for more details. +::: + +Breaking changes that are handled by this codemod are denoted by a ✅ emoji in the table of contents on the right side of the screen. + +If you have already applied the `v8.0.0/pickers/preset-safe` (or `v8.0.0/preset-safe`) codemod, then you should not need to take any further action on these items. + +All other changes must be handled manually. + +:::warning +Not all use cases are covered by codemods. In some scenarios, like props spreading, cross-file dependencies, etc., the changes are not properly identified and therefore must be handled manually. + +For example, if a codemod tries to rename a prop, but this prop is hidden with the spread operator, it won't be transformed as expected. + +```tsx + +``` + +After running the codemods, make sure to test your application and that you don't have any console errors. + +Feel free to [open an issue](https://github.com/mui/mui-x/issues/new/choose) for support if you need help to proceed with your migration. +::: + +## New DOM structure for the field + +Before version `v8.x`, the fields' DOM structure consisted of an ``, which held the whole value for the component, +but unfortunately presents a few limitations in terms of accessibility when managing multiple section values. + +Starting with version `v8.x`, all the field and picker components come with a new DOM structure that allows the field component to set aria attributes on individual sections, providing a far better experience with screen readers. + +### Fallback to the non-accessible DOM structure + +```tsx + + + +``` + +### Migrate `slotProps.field` + +When using `slotProps.field` to pass props to your field component, +the field consumes some props (e.g: `shouldRespectLeadingZeros`) and forwards the rest to the `TextField`. + +- For the props consumed by the field, the behavior should remain exactly the same with both DOM structures. + + Both components below will respect the leading zeroes on digit sections: + + ```js + + + ``` + +- For the props forwarded to the `TextField`, + you can have a look at the next section to see how the migration impact them. + + Both components below will render a small size UI: + + ```js + + + ``` + +### Migrate `slotProps.textField` + +If you are passing props to `slotProps.textField`, +these props will now be received by `PickersTextField` and should keep working the same way as before. + +Both components below will render a small size UI: + +```js + + +``` + +:::info +If you are passing `inputProps` to `slotProps.textField`, +these props will now be passed to the hidden `` element. +::: + +### Migrate `slots.field` + +If you are passing a custom field component to your pickers, you need to create a new one that is using the accessible DOM structure. +This new component will need to use the `PickersSectionList` component instead of an `` HTML element. + +You can have a look at the [Using a custom input](/x/react-date-pickers/custom-field/#using-a-custom-input) to have a concrete example. + +:::info +If your custom field was used to create a Joy UI design component, +you may want to wait a few weeks for the release of an out-of-the-box Joy `PickersTextField` component instead of implementing it yourself. +::: + +### Migrate `slots.textField` + +If you are passing a custom `TextField` component to your fields and pickers, +you need to create a new one that is using the accessible DOM structure. + +You can have a look at the second demo of the [Wrapping PickersTextField](/x/react-date-pickers/custom-field/#wrapping-pickerstextfield) to have a concrete example. + +:::info +If your custom `TextField` was used to apply a totally different input that did not use `@mui/material/TextField`, +please consider having a look at the [Using a custom input](/x/react-date-pickers/custom-field/#using-a-custom-input) section which uses `slots.field`. +This approach can be more appropriate for deeper changes. +::: + +### Migrate the theme + +If you are using the theme to customize `MuiTextField`, +you need to pass the same config to `MuiPickersTextField`: + +```js +const theme = createTheme({ + components: { + MuiTextField: { + defaultProps: { + variant: 'outlined', + }, + styleOverrides: { + root: { + '& .MuiInputLabel-outlined.Mui-focused': { + color: 'red', + }, + }, + }, + }, + MuiPickersTextField: { + defaultProps: { + variant: 'outlined', + }, + styleOverrides: { + root: { + '& .MuiInputLabel-outlined.Mui-focused': { + color: 'red', + }, + }, + }, + }, + }, +}); +``` + +If you are using the theme to customize `MuiInput`, `MuiOutlinedInput` or `MuiFilledInput`, +you need to pass the same config to `MuiPickersInput`, `MuiPickersOutlinedInput` or `MuiPickersFilledInput`: + +```js +const theme = createTheme({ + components: { + // Replace with `MuiOutlinedInput` or `MuiFilledInput` if needed + MuiInput: { + defaultProps: { + margin: 'dense', + }, + styleOverrides: { + root: { + color: 'red', + }, + }, + }, + // Replace with `MuiPickersOutlinedInput` or `MuiPickersFilledInput` if needed + MuiPickersInput: { + defaultProps: { + margin: 'dense', + }, + styleOverrides: { + root: { + color: 'red', + }, + }, + }, + }, +}); +``` + +If you are using the theme to customize `MuiInputBase`, +you need to pass the same config to `MuiPickersInputBase`: + +```js +const theme = createTheme({ + components: { + MuiInputBase: { + defaultProps: { + margin: 'dense', + }, + styleOverrides: { + root: { + color: 'red', + }, + }, + }, + MuiPickersInputBase: { + defaultProps: { + margin: 'dense', + }, + styleOverrides: { + root: { + color: 'red', + }, + }, + }, + }, +}); +``` + +## Removed types + +The following types are no longer exported by `@mui/x-date-pickers` and/or `@mui/x-date-pickers-pro`. +If you were using them, you need to replace them with the following code: + +- `UseDateFieldComponentProps` + + ```ts + import { UseDateFieldProps } from '@mui/x-date-pickers/DateField'; + import { PickerValidDate } from '@mui/x-date-pickers/models'; + + type UseDateFieldComponentProps< + TDate extends PickerValidDate, + TEnableAccessibleFieldDOMStructure extends boolean, + TChildProps extends {}, + > = Omit< + TChildProps, + keyof UseDateFieldProps + > & + UseDateFieldProps; + ``` + +- `UseTimeFieldComponentProps` + + ```ts + import { UseTimeFieldProps } from '@mui/x-date-pickers/TimeField'; + import { PickerValidDate } from '@mui/x-date-pickers/models'; + + type UseTimeFieldComponentProps< + TDate extends PickerValidDate, + TEnableAccessibleFieldDOMStructure extends boolean, + TChildProps extends {}, + > = Omit< + TChildProps, + keyof UseTimeFieldProps + > & + UseTimeFieldProps; + ``` + +- `UseDateTimeFieldComponentProps` + + ```ts + import { UseDateTimeFieldProps } from '@mui/x-date-pickers/DateTimeField'; + import { PickerValidDate } from '@mui/x-date-pickers/models'; + + type UseDateTimeFieldComponentProps< + TDate extends PickerValidDate, + TEnableAccessibleFieldDOMStructure extends boolean, + TChildProps extends {}, + > = Omit< + TChildProps, + keyof UseDateTimeFieldProps + > & + UseDateTimeFieldProps; + ``` diff --git a/docs/data/migration/migration-tree-view-lab/migration-tree-view-lab.md b/docs/data/migration/migration-tree-view-lab/migration-tree-view-lab.md index 2b1a932e33ff..3fc640a64832 100644 --- a/docs/data/migration/migration-tree-view-lab/migration-tree-view-lab.md +++ b/docs/data/migration/migration-tree-view-lab/migration-tree-view-lab.md @@ -8,7 +8,7 @@ productId: x-tree-view ## Introduction -This is a reference for migrating your site's tree view from `@mui/lab` to `@mui/x-tree-view`. +This is a reference for migrating your site's Tree View from `@mui/lab` to `@mui/x-tree-view`. This migration is about the npm packages used, it **does not** affect the behavior of the components in your application. [//]: # 'You can find why we are moving in this direction in the [announcement blog post](/blog/lab-tree-view-to-mui-x/).' diff --git a/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md b/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md index 7d979f8af087..2efbd13455fd 100644 --- a/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md +++ b/docs/data/migration/migration-tree-view-v6/migration-tree-view-v6.md @@ -13,7 +13,7 @@ To read more about the changes from the new major, check out [the blog post abou ## Start using the new release -In `package.json`, change the version of the tree view package to `^7.0.0`. +In `package.json`, change the version of the Tree View package to `^7.0.0`. ```diff -"@mui/x-tree-view": "^6.0.0", @@ -113,7 +113,7 @@ Here is an example of how you can transpile these features on Webpack 4 using th ### ✅ Rename `nodeId` to `itemId` -The required `nodeId` prop used by the `TreeItem` has been renamed to `itemId` for consistency: +The required `nodeId` prop used by the Tree Item has been renamed to `itemId` for consistency: ```diff @@ -140,10 +140,10 @@ The same change has been applied to the `ContentComponent` prop: } ``` -### ✅ Use `SimpleTreeView` instead of `TreeView` +### ✅ Use Simple Tree View instead of Tree View -The `TreeView` component has been deprecated and will be removed in the next major. -You can start replacing it with the new `SimpleTreeView` component which has exactly the same API: +The `` component has been deprecated and will be removed in the next major. +You can start replacing it with the new `` component which has exactly the same API: ```diff -import { TreeView } from '@mui/x-tree-view'; @@ -193,7 +193,7 @@ If you were using the `treeViewClasses` object, you can replace it with the new #### Define `expandIcon` The icon used to expand the children of an item (rendered when this item is collapsed) -is now defined as a slot both on the Tree View and the `TreeItem` components. +is now defined as a slot both on the `` and the `` components. If you were using the `ChevronRight` icon from `@mui/icons-material`, you can stop passing it to your component because it is now the default value: @@ -224,7 +224,7 @@ you need to use the new `expandIcon` slot on this component: Note that the `slots` prop expects a React component, not the JSX element returned when rendering this component. ::: -If you were passing another icon to your `TreeItem` component, +If you were passing another icon to your `` component, you need to use the new `expandIcon` slot on this component: ```diff @@ -241,7 +241,7 @@ you need to use the new `expandIcon` slot on this component: #### Define `collapseIcon` The icon used to collapse the children of an item (rendered when this item is expanded) -is now defined as a slot both on the Tree View and the `TreeItem` components. +is now defined as a slot both on the `` and `` components. If you were using the `ExpandMore` icon from `@mui/icons-material`, you can stop passing it to your component because it is now the default value: @@ -272,7 +272,7 @@ you need to use the new `collapseIcon` slot on this component: Note that the `slots` prop expects a React component, not the JSX element returned when rendering this component. ::: -If you were passing another icon to your `TreeItem` component, +If you were passing another icon to your `` component, you need to use the new `collapseIcon` slot on this component: ```diff @@ -306,7 +306,7 @@ by passing the same icon to both the `collapseIcon` and the `expandIcon` slots o #### Define `endIcon` The icon rendered next to an item without children -is now defined as a slot both on the Tree View and the `TreeItem` components. +is now defined as a slot both on the `` and `` components. If you were passing an icon to your Tree View component, you need to use the new `endIcon` slot on this component: @@ -324,7 +324,7 @@ you need to use the new `endIcon` slot on this component: Note that the `slots` prop expects a React component, not the JSX element returned when rendering this component. ::: -If you were passing an icon to your `TreeItem` component, +If you were passing an icon to your `` component, you need to use the new `endIcon` slot on this component: ```diff @@ -341,9 +341,9 @@ you need to use the new `endIcon` slot on this component: #### Define `icon` The icon rendered next to an item -is now defined as a slot on the `TreeItem` component. +is now defined as a slot on the `` component. -If you were passing an icon to your `TreeItem` component, +If you were passing an icon to your `` component, you need to use the new `icon` slot on this component: ```diff @@ -364,9 +364,9 @@ Note that the `slots` prop expects a React component, not the JSX element return ### ✅ Use slots to define the group transition The component used to animate the item children -is now defined as a slot on the `TreeItem` component. +is now defined as a slot on the `` component. -If you were passing a `TransitionComponent` or `TransitionProps` to your `TreeItem` component, +If you were passing a `TransitionComponent` or `TransitionProps` to your `` component, you need to use the new `groupTransition` slot on this component: ```diff @@ -382,9 +382,9 @@ you need to use the new `groupTransition` slot on this component: ``` -### Rename the `group` class of the `TreeItem` component +### Rename the `group` class of the Tree Item component -The `group` class of the `TreeItem` component has been renamed to `groupTransition` to match with its new slot name. +The `group` class of the `` component has been renamed to `groupTransition` to match with its new slot name. ```diff const StyledTreeItem = styled(TreeItem)({ @@ -423,7 +423,7 @@ If you were using the `onNodeToggle` prop to react to the expansion or collapse you can use the new `onItemExpansionToggle` prop which is called whenever an item is expanded or collapsed with its id and expansion status ```tsx -// It is also available on the deprecated `TreeView` component +// It is also available on the deprecated Tree View component console.log(itemId, isExpanded) @@ -461,7 +461,7 @@ If you were using the `onNodeSelect` prop to react to the selection or deselecti you can use the new `onItemSelectionToggle` prop which is called whenever an item is selected or deselected with its id and selection status. ```tsx -// It is also available on the deprecated `TreeView` component +// It is also available on the deprecated `` component console.log(itemId, isSelected) @@ -512,7 +512,7 @@ For example, if you were writing a test with `react-testing-library`, here is wh ### ✅ Use `useTreeItemState` instead of `useTreeItem` The `useTreeItem` hook has been renamed `useTreeItemState`. -This will help create a new headless version of the `TreeItem` component based on a future `useTreeItem` hook. +This will help create a new headless version of the Tree Item component based on a future `useTreeItem` hook. ```diff -import { TreeItem, useTreeItem } from '@mui/x-tree-view/TreeItem'; diff --git a/docs/data/migration/migration-tree-view-v7/migration-tree-view-v7.md b/docs/data/migration/migration-tree-view-v7/migration-tree-view-v7.md new file mode 100644 index 000000000000..c76698100726 --- /dev/null +++ b/docs/data/migration/migration-tree-view-v7/migration-tree-view-v7.md @@ -0,0 +1,180 @@ +--- +productId: x-tree-view +--- + +# Migration from v7 to v8 + +

This guide describes the changes needed to migrate the Tree View from v7 to v8.

+ +## Introduction + +This is a reference guide for upgrading `@mui/x-tree-view` from v7 to v8. + +## Start using the new release + +In `package.json`, change the version of the Tree View package to `next`. + +```diff +-"@mui/x-tree-view": "7.x.x", ++"@mui/x-tree-view": "next", +``` + +Using `next` ensures that it will always use the latest v8 pre-release version, but you can also use a fixed version, like `8.0.0-alpha.0`. + +Since `v8` is a major release, it contains changes that affect the public API. +These changes were done for consistency, improved stability and to make room for new features. +Described below are the steps needed to migrate from v7 to v8. + +## Run codemods + +The `preset-safe` codemod will automatically adjust the bulk of your code to account for breaking changes in v8. You can run `v8.0.0/tree-view/preset-safe` targeting only Tree View or `v8.0.0/preset-safe` to target the other packages as well. + +You can either run it on a specific file, folder, or your entire codebase when choosing the `` argument. + + + +```bash +// Tree View specific +npx @mui/x-codemod@latest v8.0.0/tree-view/preset-safe + +// Target the other packages as well +npx @mui/x-codemod@latest v8.0.0/preset-safe +``` + +:::info +If you want to run the transformers one by one, check out the transformers included in the [preset-safe codemod for the Tree View](https://github.com/mui/mui-x/blob/HEAD/packages/x-codemod/README.md#preset-safe-for-tree-view-v800) for more details. +::: + +Breaking changes that are handled by this codemod are denoted by a ✅ emoji in the table of contents on the right side of the screen. + +If you have already applied the `v8.0.0/tree-view/preset-safe` (or `v8.0.0/preset-safe`) codemod, then you should not need to take any further action on these items. + +All other changes must be handled manually. + +:::warning +Not all use cases are covered by codemods. In some scenarios, like props spreading, cross-file dependencies, etc., the changes are not properly identified and therefore must be handled manually. + +For example, if a codemod tries to rename a prop, but this prop is hidden with the spread operator, it won't be transformed as expected. + +```tsx + +``` + +After running the codemods, make sure to test your application and that you don't have any console errors. + +Feel free to [open an issue](https://github.com/mui/mui-x/issues/new/choose) for support if you need help to proceed with your migration. +::: + +## New API to customize the Tree Item + +The `ContentComponent` or `ContentProps` props of the `TreeItem` component have been removed in favor of the new `slots`, `slotProps` props and of the `useTreeItem` hook. + +Learn more about the anatomy of the Tree Items and the customization utilities provided on the [Tree Item Customization page](/x/react-tree-view/tree-item-customization/). + +## Behavior change on the `onClick` and `onMouseDown` props of `TreeItem` + +The `onClick` and `onMouseDown` were the only event callback that were passed to the content of the Tree Item instead of its root. +The goal was to make sure that the callback was not fired when clicking on a descendant of a giving item. +This inconsistency has been solved, all the event manager now target the root of the item, and you can use the `onItemClick` prop on the Tree View component to target the content of an item: + +```diff +- ++ +- ++ + +``` + +## Rename the `TreeItem2` (and related utils) + +All the new Tree Item-related components and utils (introduced in the previous major to improve the DX of the Tree Item component) are becoming the default way of using the Tree Item and are therefore losing their `2` suffix: + +```diff + import * as React from 'react'; + import { +- TreeItem2, ++ TreeItem, +- TreeItem2Root, ++ TreeItemRoot, +- TreeItem2Content, ++ TreeItemContent, +- TreeItem2IconContainer, ++ TreeItemIconContainer, +- TreeItem2GroupTransition, ++ TreeItemGroupTransition, +- TreeItem2Checkbox, ++ TreeItemCheckbox, +- TreeItem2Label, ++ TreeItemLabel, +- TreeItem2Props, ++ TreeItemProps, +- TreeItem2Slots, ++ TreeItemSlots, +- TreeItem2SlotProps, ++ TreeItemSlotProps, +- } from '@mui/x-tree-view/TreeItem2'; ++ } from '@mui/x-tree-view/TreeItem'; + import { +- useTreeItem2, ++ useTreeItem, +- unstable_useTreeItem2 as useAliasedTreeItem, ++ unstable_useTreeItem as useAliasedTreeItem, +- UseTreeItem2Parameters, ++ UseTreeItemParameters, +- UseTreeItem2ReturnValue, ++ UseTreeItemReturnValue, +- UseTreeItem2Status, ++ UseTreeItemStatus, +- UseTreeItem2RootSlotOwnProps, ++ UseTreeItemRootSlotOwnProps, +- UseTreeItem2ContentSlotOwnProps, ++ UseTreeItemContentSlotOwnProps, +- UseTreeItem2LabelInputSlotOwnProps, ++ UseTreeItemLabelInputSlotOwnProps, +- UseTreeItem2LabelSlotOwnProps, ++ UseTreeItemLabelSlotOwnProps, +- UseTreeItem2CheckboxSlotOwnProps, ++ UseTreeItemCheckboxSlotOwnProps, +- UseTreeItem2IconContainerSlotOwnProps, ++ UseTreeItemIconContainerSlotOwnProps, +- UseTreeItem2GroupTransitionSlotOwnProps, ++ UseTreeItemGroupTransitionSlotOwnProps, +- UseTreeItem2DragAndDropOverlaySlotOwnProps, ++ UseTreeItemDragAndDropOverlaySlotOwnProps, +- } from '@mui/x-tree-view/useTreeItem2'; ++ } from '@mui/x-tree-view/useTreeItem'; +- import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; ++ import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; + import { +- TreeItem2Provider, ++ TreeItemProvider, +- TreeItem2ProviderProps, ++ TreeItemProviderProps, +- } from '@mui/x-tree-view/TreeItem2Provider'; ++ } from '@mui/x-tree-view/TreeItemProvider'; + import { +- TreeItem2Icon, ++ TreeItemIcon, +- TreeItem2IconProps, ++ TreeItemIconProps, +- TreeItem2IconSlots, ++ TreeItemIconSlots, +- TreeItem2IconSlotProps, ++ TreeItemIconSlotProps, +- } from '@mui/x-tree-view/TreeItem2Icon'; ++ } from '@mui/x-tree-view/TreeItemIcon'; + import { +- TreeItem2DragAndDropOverlay, ++ TreeItemDragAndDropOverlay, +- TreeItem2DragAndDropOverlayProps, ++ TreeItemDragAndDropOverlayProps, +- } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; ++ } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; + import { +- TreeItem2LabelInput, ++ TreeItemLabelInput, +- TreeItem2LabelInputProps, ++ TreeItemLabelInputProps, +- } from '@mui/x-tree-view/TreeItem2LabelInput'; ++ } from '@mui/x-tree-view/TreeItemLabelInput'; +``` diff --git a/docs/data/pages.ts b/docs/data/pages.ts index b952b8a5b722..361b46837c94 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -1,8 +1,8 @@ import type { MuiPage } from 'docs/src/MuiPage'; -import chartsComponentApi from './charts-component-api-pages'; -import dataGridComponentApi from './data-grid-component-api-pages'; -import pickersComponentApi from './date-pickers-component-api-pages'; -import treeViewComponentApi from './tree-view-component-api-pages'; +import chartsComponentApi from './chartsApiPages'; +import dataGridComponentApi from './dataGridApiPages'; +import pickersComponentApi from './datePickersApiPages'; +import treeViewComponentApi from './treeViewApiPages'; const pages: MuiPage[] = [ { @@ -120,8 +120,9 @@ const pages: MuiPage[] = [ pathname: '/x/react-data-grid/server-side-data-group', title: 'Server-side data', plan: 'pro', + newFeature: true, children: [ - { pathname: '/x/react-data-grid/server-side-data', title: 'Overview' }, + { pathname: '/x/react-data-grid/server-side-data', title: 'Overview', plan: 'pro' }, { pathname: '/x/react-data-grid/server-side-data/tree-data', plan: 'pro' }, { pathname: '/x/react-data-grid/server-side-data/lazy-loading', @@ -135,8 +136,7 @@ const pages: MuiPage[] = [ }, { pathname: '/x/react-data-grid/server-side-data/row-grouping', - plan: 'pro', - planned: true, + plan: 'premium', }, { pathname: '/x/react-data-grid/server-side-data/aggregation', @@ -537,9 +537,28 @@ const pages: MuiPage[] = [ pathname: '/x/migration-group', title: 'Migration', children: [ + { + pathname: '/x/migration-v8', + subheader: 'Upgrade to v8', + children: [ + { pathname: '/x/migration/migration-data-grid-v7', title: 'Breaking changes: Data Grid' }, + { + pathname: '/x/migration/migration-pickers-v7', + title: 'Breaking changes: Date and Time Pickers', + }, + { + pathname: '/x/migration/migration-tree-view-v7', + title: 'Breaking changes: Tree View', + }, + { + pathname: '/x/migration/migration-charts-v7', + title: 'Breaking changes: Charts', + }, + ], + }, { pathname: '/x/migration-v7', - subheader: 'Upgrade to v7', + title: 'Upgrade to v7', children: [ { pathname: '/x/migration/migration-data-grid-v6', title: 'Breaking changes: Data Grid' }, { diff --git a/docs/data/tree-view/accessibility/accessibility.md b/docs/data/tree-view/accessibility/accessibility.md index b1fc45bfc690..cc9c2306612b 100644 --- a/docs/data/tree-view/accessibility/accessibility.md +++ b/docs/data/tree-view/accessibility/accessibility.md @@ -54,7 +54,7 @@ Type-ahead is supported for single characters. When typing a character, focus mo ## Selection -The tree view supports both single and multi-selection. To learn more about the selection API, visit the dedicated page for the [Simple Tree View](/x/react-tree-view/simple-tree-view/selection/) or the [Rich Tree View](/x/react-tree-view/rich-tree-view/selection/). +The Tree View supports both single and multi-selection. To learn more about the selection API, visit the dedicated page for the [Simple Tree View](/x/react-tree-view/simple-tree-view/selection/) or the [Rich Tree View](/x/react-tree-view/rich-tree-view/selection/). To read more about the distinction between selection and focus, you can refer to the [WAI-ARIA Authoring Practices guide](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_focus_vs_selection). diff --git a/docs/data/tree-view/datasets/employees.ts b/docs/data/tree-view/datasets/employees.ts new file mode 100644 index 000000000000..2af62416f36e --- /dev/null +++ b/docs/data/tree-view/datasets/employees.ts @@ -0,0 +1,40 @@ +import { TreeViewBaseItem } from '@mui/x-tree-view/models'; + +export const EMPLOYEES_DATASET: TreeViewBaseItem[] = [ + { + id: '0', + label: 'Sarah', + }, + { + id: '1', + label: 'Thomas', + children: [ + { id: '2', label: 'Robert' }, + { id: '3', label: 'Karen' }, + { id: '4', label: 'Nancy' }, + { id: '5', label: 'Daniel' }, + { id: '6', label: 'Christopher' }, + { id: '7', label: 'Donald' }, + ], + }, + { + id: '8', + label: 'Mary', + children: [ + { + id: '9', + label: 'Jennifer', + children: [{ id: '10', label: 'Anna' }], + }, + { id: '11', label: 'Michael' }, + { + id: '12', + label: 'Linda', + children: [ + { id: '13', label: 'Elizabeth' }, + { id: '14', label: 'William' }, + ], + }, + ], + }, +]; diff --git a/docs/data/tree-view/getting-started/getting-started.md b/docs/data/tree-view/getting-started/getting-started.md index 79dc191bdc92..6acc9e5382c1 100644 --- a/docs/data/tree-view/getting-started/getting-started.md +++ b/docs/data/tree-view/getting-started/getting-started.md @@ -72,7 +72,7 @@ Take a look at the [Styled engine guide](/material-ui/integrations/styled-compon ## Render your first component -To make sure that everything is set up correctly, try rendering a `SimpleTreeView` component: +To make sure that everything is set up correctly, try rendering a Simple Tree View component: {{"demo": "FirstComponent.js"}} @@ -82,7 +82,7 @@ To make sure that everything is set up correctly, try rendering a `SimpleTreeVie The component follows the WAI-ARIA authoring practices. -To have an accessible tree view you must use `aria-labelledby` +To have an accessible Tree View you must use `aria-labelledby` or `aria-label` to reference or provide a label on the TreeView, otherwise, screen readers will announce it as "tree", making it hard to understand the context of a specific tree item. diff --git a/docs/data/tree-view/overview/overview.md b/docs/data/tree-view/overview/overview.md index 64c374965652..e1c49a7e2bd6 100644 --- a/docs/data/tree-view/overview/overview.md +++ b/docs/data/tree-view/overview/overview.md @@ -41,75 +41,3 @@ This is the recommended version for larger trees, as well as those that require :::info At the moment, the Simple and Rich Tree Views are similar in terms of feature support. But as the component grows, you can expect to see the more advanced ones appear primarily on the Rich Tree View. ::: - -### Tree Item components - -The `@mui/x-tree-view` package exposes two different components to define your tree items: - -- `TreeItem` -- `TreeItem2` - -#### `TreeItem` - -This is the long-standing component that is very similar to the one used in previous versions (`@mui/x-tree-view@6` and `@mui/lab`). - -When using `SimpleTreeView`, -you can import it from `@mui/x-tree-view/TreeItem` and use it as a child of the `SimpleTreeView` component: - -```tsx -import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; -import { TreeItem } from '@mui/x-tree-view/TreeItem'; - -export default function App() { - return ( - - - - - ); -} -``` - -When using `RichTreeView`, -you don't have to import anything; it's the default component used to render the items: - -```tsx -import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; - -export default function App() { - return ; -} -``` - -#### `TreeItem2` - -This is a new component that provides a more powerful customization API, and will eventually replace `TreeItem`. - -When using `SimpleTreeView`, -you can import it from `@mui/x-tree-view/TreeItem2` and use it as a child of the `SimpleTreeView` component: - -```tsx -import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; - -export default function App() { - return ( - - - - - ); -} -``` - -When using `RichTreeView`, -you can import it from `@mui/x-tree-view/TreeItem2` and pass it as a slot of the `RichTreeView` component: - -```tsx -import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; - -export default function App() { - return ; -} -``` 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 305d77fc61a3..304f82faaf74 100644 --- a/docs/data/tree-view/rich-tree-view/customization/FileExplorer.js +++ b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.js @@ -14,18 +14,18 @@ import ImageIcon from '@mui/icons-material/Image'; import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import VideoCameraBackIcon from '@mui/icons-material/VideoCameraBack'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { treeItemClasses } from '@mui/x-tree-view/TreeItem'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Checkbox, - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Label, - TreeItem2Root, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemCheckbox, + TreeItemContent, + TreeItemIconContainer, + TreeItemLabel, + TreeItemRoot, + treeItemClasses, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; const ITEMS = [ { @@ -79,7 +79,7 @@ function DotIcon() { ); } -const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ +const StyledTreeItemRoot = styled(TreeItemRoot)(({ theme }) => ({ color: theme.palette.grey[400], position: 'relative', [`& .${treeItemClasses.groupTransition}`]: { @@ -90,7 +90,7 @@ const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ }), })); -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ flexDirection: 'row-reverse', borderRadius: theme.spacing(0.7), marginBottom: theme.spacing(0.5), @@ -156,7 +156,7 @@ const StyledTreeItemLabelText = styled(Typography)({ function CustomLabel({ icon: Icon, expandable, children, ...other }) { return ( - {children} {expandable && } - + ); } @@ -219,7 +219,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getDragAndDropOverlayProps, status, publicAPI, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const item = publicAPI.getItem(itemId); const expandable = isExpandable(children); @@ -231,7 +231,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { } return ( - + - - - - + + + + - + {children && } - + ); }); 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 6711c554ba9d..09291542de68 100644 --- a/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx +++ b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx @@ -14,18 +14,18 @@ import ImageIcon from '@mui/icons-material/Image'; import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import VideoCameraBackIcon from '@mui/icons-material/VideoCameraBack'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { treeItemClasses } from '@mui/x-tree-view/TreeItem'; -import { useTreeItem2, UseTreeItem2Parameters } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem, UseTreeItemParameters } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Checkbox, - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Label, - TreeItem2Root, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemCheckbox, + TreeItemContent, + TreeItemIconContainer, + TreeItemLabel, + TreeItemRoot, + treeItemClasses, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; import { TreeViewBaseItem } from '@mui/x-tree-view/models'; type FileType = 'image' | 'pdf' | 'doc' | 'video' | 'folder' | 'pinned' | 'trash'; @@ -94,7 +94,7 @@ declare module 'react' { } } -const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ +const StyledTreeItemRoot = styled(TreeItemRoot)(({ theme }) => ({ color: theme.palette.grey[400], position: 'relative', [`& .${treeItemClasses.groupTransition}`]: { @@ -103,9 +103,9 @@ const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ ...theme.applyStyles('light', { color: theme.palette.grey[800], }), -})) as unknown as typeof TreeItem2Root; +})) as unknown as typeof TreeItemRoot; -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ flexDirection: 'row-reverse', borderRadius: theme.spacing(0.7), marginBottom: theme.spacing(0.5), @@ -182,7 +182,7 @@ function CustomLabel({ ...other }: CustomLabelProps) { return ( - {children} {expandable && } - + ); } @@ -233,7 +233,7 @@ const getIconFromFileType = (fileType: FileType) => { }; interface CustomTreeItemProps - extends Omit, + extends Omit, Omit, 'onFocus'> {} const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -252,7 +252,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getDragAndDropOverlayProps, status, publicAPI, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const item = publicAPI.getItem(itemId); const expandable = isExpandable(children); @@ -264,7 +264,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( } return ( - + - - - - + + + + - + {children && } - + ); }); diff --git a/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.js b/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.js index afb1f90aec17..5619fbf9b31d 100644 --- a/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.js +++ b/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.js @@ -4,18 +4,18 @@ import Box from '@mui/material/Box'; import Avatar from '@mui/material/Avatar'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; const ITEMS = [ { @@ -37,7 +37,7 @@ const ITEMS = [ }, ]; -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ padding: theme.spacing(0.5, 1), })); @@ -53,15 +53,15 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getGroupTransitionProps, getDragAndDropOverlayProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); return ( - - + + - - - + + + ({ @@ -73,14 +73,14 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { > {label[0]} - - + + - + - {children && } - - + {children && } + + ); }); diff --git a/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.tsx b/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.tsx index d07cd21249f8..c4ac1965cf83 100644 --- a/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.tsx +++ b/docs/data/tree-view/rich-tree-view/customization/HeadlessAPI.tsx @@ -4,18 +4,18 @@ import Box from '@mui/material/Box'; import Avatar from '@mui/material/Avatar'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; import { TreeViewBaseItem } from '@mui/x-tree-view/models'; -import { useTreeItem2, UseTreeItem2Parameters } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem, UseTreeItemParameters } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; const ITEMS: TreeViewBaseItem[] = [ { @@ -37,12 +37,12 @@ const ITEMS: TreeViewBaseItem[] = [ }, ]; -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ padding: theme.spacing(0.5, 1), })); interface CustomTreeItemProps - extends Omit, + extends Omit, Omit, 'onFocus'> {} const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -60,15 +60,15 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getGroupTransitionProps, getDragAndDropOverlayProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); return ( - - + + - - - + + + ({ @@ -80,14 +80,14 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( > {(label as string)[0]} - - + + - + - {children && } - - + {children && } + + ); }); diff --git a/docs/data/tree-view/rich-tree-view/customization/customization.md b/docs/data/tree-view/rich-tree-view/customization/customization.md index adfe395f9c19..506acec5ae8c 100644 --- a/docs/data/tree-view/rich-tree-view/customization/customization.md +++ b/docs/data/tree-view/rich-tree-view/customization/customization.md @@ -1,7 +1,7 @@ --- productId: x-tree-view title: Rich Tree View - Customization -components: RichTreeView, TreeItem, TreeItem2 +components: RichTreeView, TreeItem packageName: '@mui/x-tree-view' githubLabel: 'component: tree view' waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ @@ -26,7 +26,7 @@ The demo below shows how to add icons using both an existing icon library, such ### Custom toggle animations -Use the `groupTransition` slot on the `TreeItem` to pass a component that handles your animation. +Use the `groupTransition` slot on the `` to pass a component that handles your animation. The demo below is animated using Material UI's [Collapse](/material-ui/transitions/#collapse) component together with the [react-spring](https://www.react-spring.dev/) library. @@ -45,7 +45,7 @@ Learn more about the anatomy of the Tree Item components and the customization u ### Headless API -Use the `useTreeItem2` hook to create your own component. +Use the `useTreeItem` hook to create your own component. The demo below shows how to add an avatar and custom typography elements. {{"demo": "HeadlessAPI.js", "defaultCodeOpen": false}} @@ -54,13 +54,6 @@ The demo below shows how to add an avatar and custom typography elements. ### File explorer -:::warning -This example is built using the new `TreeItem2` component -which adds several slots to modify the content of the Tree Item or change its behavior. - -You can learn more about this new component in the [Overview page](/x/react-tree-view/#tree-item-components). -::: - The demo below shows many of the previous customization examples brought together to make the Tree View component look completely different than its default design. {{"demo": "FileExplorer.js", "defaultCodeOpen": false}} diff --git a/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.js b/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.js index 4c52e4c94ec8..e6ed064a1613 100644 --- a/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.js +++ b/docs/data/tree-view/rich-tree-view/editing/CustomBehavior.js @@ -1,13 +1,13 @@ 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 { useTreeItemUtils } from '@mui/x-tree-view/hooks'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; import { MUI_X_PRODUCTS } from './products'; -const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { - const { interactions } = useTreeItem2Utils({ +const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -17,7 +17,7 @@ const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { }; return ( - , ) { - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); - const handleInputBlur: UseTreeItem2LabelInputSlotOwnProps['onBlur'] = (event) => { + const handleInputBlur: UseTreeItemLabelInputSlotOwnProps['onBlur'] = (event) => { interactions.handleCancelItemLabelEditing(event); }; return ( - ({ ...theme.typography.body1, @@ -53,7 +53,7 @@ export const ITEMS = [ function Label({ children, ...other }) { return ( - {children} - + ); } @@ -136,12 +136,12 @@ const LabelInput = React.forwardRef(function LabelInput( ); }); -const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { - const { interactions } = useTreeItem2Utils({ +const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); - const { publicAPI } = useTreeItem2(props); + const { publicAPI } = useTreeItem(props); const handleInputBlur = (event) => { event.defaultMuiPrevented = true; @@ -152,7 +152,7 @@ const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { }; return ( - ({ @@ -67,9 +63,9 @@ export const ITEMS: TreeViewBaseItem[] = [ }, ]; -function Label({ children, ...other }: UseTreeItem2LabelSlotOwnProps) { +function Label({ children, ...other }: UseTreeItemLabelSlotOwnProps) { return ( - {children} - + ); } -interface CustomLabelInputProps extends UseTreeItem2LabelInputSlotOwnProps { +interface CustomLabelInputProps extends UseTreeItemLabelInputSlotOwnProps { handleCancelItemLabelEditing: (event: React.SyntheticEvent) => void; handleSaveItemLabel: (event: React.SyntheticEvent, label: string) => void; item: TreeViewBaseItem; @@ -163,28 +159,28 @@ const LabelInput = React.forwardRef(function LabelInput( ); }); -const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( - props: TreeItem2Props, +const CustomTreeItem = React.forwardRef(function CustomTreeItem( + props: TreeItemProps, ref: React.Ref, ) { - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); - const { publicAPI } = useTreeItem2(props); + const { publicAPI } = useTreeItem(props); - const handleInputBlur: UseTreeItem2LabelInputSlotOwnProps['onBlur'] = (event) => { + const handleInputBlur: UseTreeItemLabelInputSlotOwnProps['onBlur'] = (event) => { event.defaultMuiPrevented = true; }; - const handleInputKeyDown: UseTreeItem2LabelInputSlotOwnProps['onKeyDown'] = ( + const handleInputKeyDown: UseTreeItemLabelInputSlotOwnProps['onKeyDown'] = ( event, ) => { event.defaultMuiPrevented = true; }; return ( - )} - + ); } @@ -43,7 +43,7 @@ function CustomLabelInput(props) { return ( - + void; @@ -32,7 +28,7 @@ function CustomLabel({ ...other }: CustomLabelProps) { return ( - )} - + ); } -interface CustomLabelInputProps extends UseTreeItem2LabelInputSlotOwnProps { +interface CustomLabelInputProps extends UseTreeItemLabelInputSlotOwnProps { handleCancelItemLabelEditing: (event: React.SyntheticEvent) => void; handleSaveItemLabel: (event: React.SyntheticEvent, label: string) => void; value: string; @@ -68,7 +64,7 @@ function CustomLabelInput(props: Omit) { return ( - + ) { ); } -const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( - props: TreeItem2Props, +const CustomTreeItem = React.forwardRef(function CustomTreeItem( + props: TreeItemProps, ref: React.Ref, ) { - const { interactions, status } = useTreeItem2Utils({ + const { interactions, status } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); - const handleContentDoubleClick: UseTreeItem2LabelSlotOwnProps['onDoubleClick'] = ( + const handleContentDoubleClick: UseTreeItemLabelSlotOwnProps['onDoubleClick'] = ( event, ) => { event.defaultMuiPrevented = true; }; - const handleInputBlur: UseTreeItem2LabelInputSlotOwnProps['onBlur'] = (event) => { + const handleInputBlur: UseTreeItemLabelInputSlotOwnProps['onBlur'] = (event) => { event.defaultMuiPrevented = true; }; - const handleInputKeyDown: UseTreeItem2LabelInputSlotOwnProps['onKeyDown'] = ( + const handleInputKeyDown: UseTreeItemLabelInputSlotOwnProps['onKeyDown'] = ( event, ) => { event.defaultMuiPrevented = true; }; return ( - - + {error ? ( @@ -34,9 +34,9 @@ function CustomLabelInput(props) { ); } -const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { +const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { const [error, setError] = React.useState(null); - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -77,7 +77,7 @@ const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2(props, ref) { }; return ( - ) { return ( - + {error ? ( @@ -38,12 +38,12 @@ function CustomLabelInput(props: Omit) { ); } -const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( - props: TreeItem2Props, +const CustomTreeItem = React.forwardRef(function CustomTreeItem( + props: TreeItemProps, ref: React.Ref, ) { const [error, setError] = React.useState(null); - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -57,13 +57,13 @@ const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( } }; - const handleInputBlur: UseTreeItem2LabelInputSlotOwnProps['onBlur'] = (event) => { + const handleInputBlur: UseTreeItemLabelInputSlotOwnProps['onBlur'] = (event) => { if (error) { event.defaultMuiPrevented = true; } }; - const handleInputKeyDown: UseTreeItem2LabelInputSlotOwnProps['onKeyDown'] = ( + const handleInputKeyDown: UseTreeItemLabelInputSlotOwnProps['onKeyDown'] = ( event, ) => { event.defaultMuiPrevented = true; @@ -86,7 +86,7 @@ const CustomTreeItem2 = React.forwardRef(function CustomTreeItem2( }; return ( - Create your custom tree view.

+

Create your custom Tree View.

:::warning The `useTreeView` hook is not public API for now, @@ -143,7 +143,7 @@ Once `focusedItemId` becomes a model, we could consider removing the notion of s ### Populate the Tree View instance -The Tree View instance is an object accessible in all the plugins and in the `TreeItem`. +The Tree View instance is an object accessible in all the plugins and in the Tree Item. It is the main way a plugin can provide features to the rest of the component. ```ts @@ -268,7 +268,7 @@ type UseCustomPluginSignature = TreeViewPluginSignature<{ params: UseCustomPluginParams; // The params specific to your plugins after running `getDefaultizedParams` defaultizedParams: UseCustomPluginDefaultizedParams; - // The methods added to the tree view instance by your plugin + // The methods added to the Tree View instance by your plugin instance: UseCustomPluginInstance; // The events emitted by your plugin events: UseCustomPluginEvents; @@ -325,6 +325,6 @@ type UseCustomPluginSignature = TreeViewPluginSignature<{ ### Log expanded items -Interact with the tree view to see the expanded items being logged: +Interact with the Tree View to see the expanded items being logged: {{"demo": "LogExpandedItems.js"}} diff --git a/docs/data/tree-view/rich-tree-view/items/items.md b/docs/data/tree-view/rich-tree-view/items/items.md index fbf179e3685e..49db9da0a4e2 100644 --- a/docs/data/tree-view/rich-tree-view/items/items.md +++ b/docs/data/tree-view/rich-tree-view/items/items.md @@ -28,7 +28,7 @@ Each item must have a unique identifier. This identifier is used internally to identify the item in the various models and to track the item across updates. -By default, the `RichTreeView` component looks for a property named `id` in the data set to get that identifier: +By default, the Rich Tree View component looks for a property named `id` in the data set to get that identifier: ```tsx const ITEMS = [{ id: 'tree-view-community' }]; @@ -36,7 +36,7 @@ const ITEMS = [{ id: 'tree-view-community' }]; ; ``` -If the item's identifier is not called `id`, then you need to use the `getItemId` prop to tell the `RichTreeView` component where it is located. +If the item's identifier is not called `id`, then you need to use the `getItemId` prop to tell the Rich Tree View component where it is located. The following demo shows how to use `getItemId` to grab the unique identifier from a property named `internalId`: @@ -63,7 +63,7 @@ It could be achieved by either defining the prop outside the component scope or Each item must have a label which does not need to be unique. -By default, the `RichTreeView` component looks for a property named `label` in the data set to get that label: +By default, the Rich Tree View component looks for a property named `label` in the data set to get that label: ```tsx const ITEMS = [{ label: '@mui/x-tree-view' }]; @@ -71,7 +71,7 @@ const ITEMS = [{ label: '@mui/x-tree-view' }]; ; ``` -If the item's label is not called `label`, then you need to use the `getItemLabel` prop to tell the `RichTreeView` component where it's located: +If the item's label is not called `label`, then you need to use the `getItemLabel` prop to tell the Rich Tree View component where it's located: The following demo shows how to use `getItemLabel` to grab the unique identifier from a property named `name`: @@ -95,7 +95,7 @@ It could be achieved by either defining the prop outside the component scope or ::: :::warning -Unlike the `SimpleTreeView` component, the `RichTreeView` component only supports string labels, you cannot pass React nodes to it. +Unlike the Simple Tree View component, the Rich Tree View component only supports string labels, you cannot pass React nodes to it. ::: ## Disabled items @@ -179,7 +179,7 @@ const item = apiRef.current.getItem( ### Get an item's DOM element by ID -Use the `getItemDOMElement` API method to get an item's DOM element by its ID. +Use the `getItemDOMElement()` API method to get an item's DOM element by its ID. ```ts const itemElement = apiRef.current.getItemDOMElement( diff --git a/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.js b/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.js index 9a05f402be2c..423926095ed7 100644 --- a/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.js +++ b/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.js @@ -10,19 +10,19 @@ import ImageIcon from '@mui/icons-material/Image'; import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import VideoCameraBackIcon from '@mui/icons-material/VideoCameraBack'; import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro'; -import { treeItemClasses } from '@mui/x-tree-view/TreeItem'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Checkbox, - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Label, - TreeItem2Root, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemCheckbox, + TreeItemContent, + TreeItemIconContainer, + TreeItemLabel, + TreeItemRoot, + TreeItemGroupTransition, + treeItemClasses, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; import { useTreeViewApiRef } from '@mui/x-tree-view/hooks'; @@ -80,7 +80,7 @@ function DotIcon() { ); } -const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ +const StyledTreeItemRoot = styled(TreeItemRoot)(({ theme }) => ({ color: theme.palette.grey[400], position: 'relative', [`& .${treeItemClasses.groupTransition}`]: { @@ -90,7 +90,7 @@ const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ color: theme.palette.grey[800], }), })); -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ flexDirection: 'row-reverse', borderRadius: theme.spacing(0.7), marginBottom: theme.spacing(0.5), @@ -142,7 +142,7 @@ const StyledTreeItemLabelText = styled(Typography)({ function CustomLabel({ icon: Icon, expandable, children, ...other }) { return ( - {children} {expandable && } - + ); } @@ -203,14 +203,14 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getDragAndDropOverlayProps, status, publicAPI, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const item = publicAPI.getItem(itemId); const expandable = isExpandable(children); const icon = getIconFromFileType(item.fileType); return ( - + - - - - + + + + - + - {children && } + {children && } - + ); }); 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 f555c2afd64b..5f5a0434956b 100644 --- a/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.tsx +++ b/docs/data/tree-view/rich-tree-view/ordering/FileExplorer.tsx @@ -10,19 +10,19 @@ import ImageIcon from '@mui/icons-material/Image'; import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; import VideoCameraBackIcon from '@mui/icons-material/VideoCameraBack'; import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro'; -import { treeItemClasses } from '@mui/x-tree-view/TreeItem'; -import { useTreeItem2, UseTreeItem2Parameters } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem, UseTreeItemParameters } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Checkbox, - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Label, - TreeItem2Root, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemCheckbox, + TreeItemContent, + TreeItemIconContainer, + TreeItemLabel, + TreeItemRoot, + TreeItemGroupTransition, + treeItemClasses, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; import { TreeViewBaseItem } from '@mui/x-tree-view/models'; import { useTreeViewApiRef } from '@mui/x-tree-view/hooks'; @@ -94,7 +94,7 @@ declare module 'react' { } } -const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ +const StyledTreeItemRoot = styled(TreeItemRoot)(({ theme }) => ({ color: theme.palette.grey[400], position: 'relative', [`& .${treeItemClasses.groupTransition}`]: { @@ -103,8 +103,8 @@ const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ ...theme.applyStyles('light', { color: theme.palette.grey[800], }), -})) as unknown as typeof TreeItem2Root; -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +})) as unknown as typeof TreeItemRoot; +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ flexDirection: 'row-reverse', borderRadius: theme.spacing(0.7), marginBottom: theme.spacing(0.5), @@ -167,7 +167,7 @@ function CustomLabel({ ...other }: CustomLabelProps) { return ( - {children} {expandable && } - + ); } @@ -216,7 +216,7 @@ const getIconFromFileType = (fileType: FileType) => { }; interface CustomTreeItemProps - extends Omit, + extends Omit, Omit, 'onFocus'> {} const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -235,14 +235,14 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getDragAndDropOverlayProps, status, publicAPI, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const item = publicAPI.getItem(itemId); const expandable = isExpandable(children); const icon = getIconFromFileType(item.fileType); return ( - + - - - - + + + + - + - {children && } + {children && } - + ); }); diff --git a/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.js b/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.js index c9f03403a38f..500411bd806c 100644 --- a/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.js +++ b/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.js @@ -3,18 +3,18 @@ import Box from '@mui/material/Box'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; const MUI_X_PRODUCTS = [ { @@ -58,7 +58,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getGroupTransitionProps, getDragAndDropOverlayProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const { draggable, onDragStart, onDragOver, onDragEnd, ...otherRootProps } = getRootProps(other); @@ -73,27 +73,27 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { }; return ( - - - - - - - + + + + + + - - - - - - {children && } - - + + + + + + {children && } + + ); }); diff --git a/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.tsx b/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.tsx index 8780d381c49b..8b0395890c15 100644 --- a/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.tsx +++ b/docs/data/tree-view/rich-tree-view/ordering/OnlyReorderFromDragHandle.tsx @@ -3,18 +3,18 @@ import Box from '@mui/material/Box'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro'; import { TreeViewBaseItem } from '@mui/x-tree-view/models'; -import { useTreeItem2, UseTreeItem2Parameters } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem, UseTreeItemParameters } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; const MUI_X_PRODUCTS: TreeViewBaseItem[] = [ { @@ -47,7 +47,7 @@ const MUI_X_PRODUCTS: TreeViewBaseItem[] = [ ]; interface CustomTreeItemProps - extends Omit, + extends Omit, Omit, 'onFocus'> {} const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -65,7 +65,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getGroupTransitionProps, getDragAndDropOverlayProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const { draggable, onDragStart, onDragOver, onDragEnd, ...otherRootProps } = getRootProps(other); @@ -84,27 +84,27 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }; return ( - - - - - - - + + + + + + - - - - - - {children && } - - + + + + + + {children && } + + ); }); diff --git a/docs/data/tree-view/rich-tree-view/ordering/ordering.md b/docs/data/tree-view/rich-tree-view/ordering/ordering.md index ed28ace0806b..be62d10fd51b 100644 --- a/docs/data/tree-view/rich-tree-view/ordering/ordering.md +++ b/docs/data/tree-view/rich-tree-view/ordering/ordering.md @@ -1,7 +1,7 @@ --- productId: x-tree-view title: Rich Tree View - Ordering -components: TreeItem2, TreeItem, RichTreeViewPro +components: TreeItem, RichTreeViewPro, TreeItemDragAndDropOverlay packageName: '@mui/x-tree-view' githubLabel: 'component: tree view' waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ @@ -50,7 +50,7 @@ You can use the `onItemPositionChange` to send the new position of an item to yo {{"demo": "OnItemPositionChange.js"}} If you want to send the entire dataset to your backend, you can use the [`getItemTree`](/x/react-tree-view/rich-tree-view/items/#get-the-current-item-tree) API method. -The following demo demonstrates it by synchronizing the first tree view with the second one whenever you do a re-ordering: +The following demo demonstrates it by synchronizing the first Tree View with the second one whenever you do a re-ordering: {{"demo": "SendAllItemsToServer.js"}} diff --git a/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.js b/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.js deleted file mode 100644 index 8f9dd61e4df9..000000000000 --- a/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.js +++ /dev/null @@ -1,98 +0,0 @@ -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' }], - }, -]; - -function getItemDescendantsIds(item) { - const ids = []; - item.children?.forEach((child) => { - ids.push(child.id); - ids.push(...getItemDescendantsIds(child)); - }); - - return ids; -} - -export default function ParentChildrenSelectionRelationship() { - const [selectedItems, setSelectedItems] = React.useState([]); - const toggledItemRef = React.useRef({}); - const apiRef = useTreeViewApiRef(); - - const handleItemSelectionToggle = (event, itemId, isSelected) => { - toggledItemRef.current[itemId] = isSelected; - }; - - const handleSelectedItemsChange = (event, newSelectedItems) => { - setSelectedItems(newSelectedItems); - - // Select / unselect the children of the toggled item - const itemsToSelect = []; - const itemsToUnSelect = {}; - Object.entries(toggledItemRef.current).forEach(([itemId, isSelected]) => { - const item = apiRef.current.getItem(itemId); - if (isSelected) { - itemsToSelect.push(...getItemDescendantsIds(item)); - } else { - getItemDescendantsIds(item).forEach((descendantId) => { - itemsToUnSelect[descendantId] = true; - }); - } - }); - - const newSelectedItemsWithChildren = Array.from( - new Set( - [...newSelectedItems, ...itemsToSelect].filter( - (itemId) => !itemsToUnSelect[itemId], - ), - ), - ); - - setSelectedItems(newSelectedItemsWithChildren); - - toggledItemRef.current = {}; - }; - - return ( - - - - ); -} diff --git a/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.tsx b/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.tsx deleted file mode 100644 index e45bdbb4fb3c..000000000000 --- a/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.tsx +++ /dev/null @@ -1,106 +0,0 @@ -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'; - -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' }], - }, -]; - -function getItemDescendantsIds(item: TreeViewBaseItem) { - const ids: string[] = []; - item.children?.forEach((child) => { - ids.push(child.id); - ids.push(...getItemDescendantsIds(child)); - }); - - return ids; -} - -export default function ParentChildrenSelectionRelationship() { - const [selectedItems, setSelectedItems] = React.useState([]); - const toggledItemRef = React.useRef<{ [itemId: string]: boolean }>({}); - const apiRef = useTreeViewApiRef(); - - const handleItemSelectionToggle = ( - event: React.SyntheticEvent, - itemId: string, - isSelected: boolean, - ) => { - toggledItemRef.current[itemId] = isSelected; - }; - - const handleSelectedItemsChange = ( - event: React.SyntheticEvent, - newSelectedItems: string[], - ) => { - setSelectedItems(newSelectedItems); - - // Select / unselect the children of the toggled item - const itemsToSelect: string[] = []; - const itemsToUnSelect: { [itemId: string]: boolean } = {}; - Object.entries(toggledItemRef.current).forEach(([itemId, isSelected]) => { - const item = apiRef.current!.getItem(itemId); - if (isSelected) { - itemsToSelect.push(...getItemDescendantsIds(item)); - } else { - getItemDescendantsIds(item).forEach((descendantId) => { - itemsToUnSelect[descendantId] = true; - }); - } - }); - - const newSelectedItemsWithChildren = Array.from( - new Set( - [...newSelectedItems, ...itemsToSelect].filter( - (itemId) => !itemsToUnSelect[itemId], - ), - ), - ); - - setSelectedItems(newSelectedItemsWithChildren); - - toggledItemRef.current = {}; - }; - - return ( - - - - ); -} diff --git a/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.tsx.preview b/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.tsx.preview deleted file mode 100644 index 6fa38db9c231..000000000000 --- a/docs/data/tree-view/rich-tree-view/selection/ParentChildrenSelectionRelationship.tsx.preview +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/docs/data/tree-view/rich-tree-view/selection/SelectionPropagation.js b/docs/data/tree-view/rich-tree-view/selection/SelectionPropagation.js new file mode 100644 index 000000000000..2c54a087f326 --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/selection/SelectionPropagation.js @@ -0,0 +1,59 @@ +import * as React from 'react'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import Stack from '@mui/material/Stack'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; + +import { EMPLOYEES_DATASET } from '../../datasets/employees'; + +export default function SelectionPropagation() { + const [selectionPropagation, setSelectionPropagation] = React.useState({ + parents: true, + descendants: true, + }); + + return ( +
+ + + setSelectionPropagation((prev) => ({ + ...prev, + descendants: event.target.checked, + })) + } + /> + } + label="Auto select descendants" + /> + + setSelectionPropagation((prev) => ({ + ...prev, + parents: event.target.checked, + })) + } + /> + } + label="Auto select parents" + /> + + + + +
+ ); +} diff --git a/docs/data/tree-view/rich-tree-view/selection/SelectionPropagation.tsx b/docs/data/tree-view/rich-tree-view/selection/SelectionPropagation.tsx new file mode 100644 index 000000000000..c41780b8fb9c --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/selection/SelectionPropagation.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import Stack from '@mui/material/Stack'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { TreeViewSelectionPropagation } from '@mui/x-tree-view/models'; +import { EMPLOYEES_DATASET } from '../../datasets/employees'; + +export default function SelectionPropagation() { + const [selectionPropagation, setSelectionPropagation] = + React.useState({ + parents: true, + descendants: true, + }); + + return ( +
+ + + setSelectionPropagation((prev) => ({ + ...prev, + descendants: event.target.checked, + })) + } + /> + } + label="Auto select descendants" + /> + + setSelectionPropagation((prev) => ({ + ...prev, + parents: event.target.checked, + })) + } + /> + } + label="Auto select parents" + /> + + + + +
+ ); +} diff --git a/docs/data/tree-view/rich-tree-view/selection/selection.md b/docs/data/tree-view/rich-tree-view/selection/selection.md index 2175d7ad3978..02036050d502 100644 --- a/docs/data/tree-view/rich-tree-view/selection/selection.md +++ b/docs/data/tree-view/rich-tree-view/selection/selection.md @@ -75,25 +75,36 @@ Use the `onItemSelectionToggle` prop if you want to react to an item selection c {{"demo": "TrackItemSelectionToggle.js"}} -## Parent / children selection relationship +## Automatic parents and children selection -Automatically select an item when all of its children are selected and automatically select all children when the parent is selected. +By default, selecting a parent item does not select its children. You can override this behavior using the `selectionPropagation` prop. -:::warning -This feature isn't implemented yet. It's coming. +Here's how it's structured: -👍 Upvote [issue #4821](https://github.com/mui/mui-x/issues/4821) if you want to see it land faster. +```ts +type TreeViewSelectionPropagation = { + descendants?: boolean; // default: false + parents?: boolean; // default: false +}; +``` -Don't hesitate to leave a comment on the same issue to influence what gets built. -Especially if you already have a use case for this component, -or if you are facing a pain point with your current solution. -::: +When `selectionPropagation.descendants` is set to `true`. + +- Selecting a parent selects all its descendants automatically. +- Deselecting a parent deselects all its descendants automatically. -If you cannot wait for the official implementation, -you can create your own custom solution using the `selectedItems`, -`onSelectedItemsChange` and `onItemSelectionToggle` props: +When `selectionPropagation.parents` is set to `true`. -{{"demo": "ParentChildrenSelectionRelationship.js"}} +- Selecting all the descendants of a parent selects the parent automatically. +- Deselecting a descendant of a selected parent deselects the parent automatically. + +The example below demonstrates the usage of the `selectionPropagation` prop. + +{{"demo": "SelectionPropagation.js", "defaultCodeOpen": false}} + +:::warning +This feature only works when multi selection is enabled using `props.multiSelect`. +::: ## Imperative API @@ -106,13 +117,13 @@ const apiRef = useTreeViewApiRef(); return ; ``` -When your component first renders, `apiRef` will be `undefined`. +When your component first renders, `apiRef` is `undefined`. After this initial render, `apiRef` holds methods to interact imperatively with the Tree View. ::: ### Select or deselect an item -Use the `selectItem` API method to select or deselect an item: +Use the `selectItem()` API method to select or deselect an item: ```ts apiRef.current.selectItem({ diff --git a/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.js b/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.js index 7dc64dbbe9b0..b12875731714 100644 --- a/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.js +++ b/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.js @@ -15,20 +15,20 @@ import ArrowRightIcon from '@mui/icons-material/ArrowRight'; import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Root, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; + TreeItemContent, + TreeItemIconContainer, + TreeItemRoot, + TreeItemGroupTransition, +} from '@mui/x-tree-view/TreeItem'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; -const CustomTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ +const CustomTreeItemRoot = styled(TreeItemRoot)(({ theme }) => ({ color: theme.palette.text.secondary, })); -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ marginBottom: theme.spacing(0.3), color: theme.palette.text.secondary, borderRadius: theme.spacing(2), @@ -46,11 +46,11 @@ const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ }, })); -const CustomTreeItemIconContainer = styled(TreeItem2IconContainer)(({ theme }) => ({ +const CustomTreeItemIconContainer = styled(TreeItemIconContainer)(({ theme }) => ({ marginRight: theme.spacing(1), })); -const CustomTreeItemGroupTransition = styled(TreeItem2GroupTransition)( +const CustomTreeItemGroupTransition = styled(TreeItemGroupTransition)( ({ theme }) => ({ marginLeft: 0, [`& .content`]: { @@ -83,7 +83,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getLabelProps, getGroupTransitionProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const style = { '--tree-view-color': theme.palette.mode !== 'dark' ? color : colorForDarkMode, @@ -92,7 +92,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { }; return ( - + - + )} - + ); }); diff --git a/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.tsx b/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.tsx index 88d4d92db453..c2c669a25182 100644 --- a/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.tsx +++ b/docs/data/tree-view/simple-tree-view/customization/GmailTreeView.tsx @@ -15,14 +15,14 @@ import ArrowRightIcon from '@mui/icons-material/ArrowRight'; import { SvgIconProps } from '@mui/material/SvgIcon'; import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Root, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { useTreeItem2, UseTreeItem2Parameters } from '@mui/x-tree-view/useTreeItem2'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; + TreeItemContent, + TreeItemIconContainer, + TreeItemRoot, + TreeItemGroupTransition, +} from '@mui/x-tree-view/TreeItem'; +import { useTreeItem, UseTreeItemParameters } from '@mui/x-tree-view/useTreeItem'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; declare module 'react' { interface CSSProperties { @@ -32,7 +32,7 @@ declare module 'react' { } interface StyledTreeItemProps - extends Omit, + extends Omit, React.HTMLAttributes { bgColor?: string; bgColorForDarkMode?: string; @@ -42,11 +42,11 @@ interface StyledTreeItemProps labelInfo?: string; } -const CustomTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ +const CustomTreeItemRoot = styled(TreeItemRoot)(({ theme }) => ({ color: theme.palette.text.secondary, })); -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ marginBottom: theme.spacing(0.3), color: theme.palette.text.secondary, borderRadius: theme.spacing(2), @@ -64,11 +64,11 @@ const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ }, })); -const CustomTreeItemIconContainer = styled(TreeItem2IconContainer)(({ theme }) => ({ +const CustomTreeItemIconContainer = styled(TreeItemIconContainer)(({ theme }) => ({ marginRight: theme.spacing(1), })); -const CustomTreeItemGroupTransition = styled(TreeItem2GroupTransition)( +const CustomTreeItemGroupTransition = styled(TreeItemGroupTransition)( ({ theme }) => ({ marginLeft: 0, [`& .content`]: { @@ -104,7 +104,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getLabelProps, getGroupTransitionProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); const style = { '--tree-view-color': theme.palette.mode !== 'dark' ? color : colorForDarkMode, @@ -113,7 +113,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }; return ( - + - + )} - + ); }); diff --git a/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.js b/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.js index c6e201274016..b8af5dff4380 100644 --- a/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.js +++ b/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.js @@ -3,19 +3,19 @@ import { styled } from '@mui/material/styles'; import Box from '@mui/material/Box'; import Avatar from '@mui/material/Avatar'; import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ padding: theme.spacing(0.5, 1), })); @@ -30,16 +30,16 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getLabelProps, getGroupTransitionProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); return ( - - + + - - - - + + + + ({ @@ -51,12 +51,12 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { > {label[0]} - + - {children && } - - + {children && } + + ); }); diff --git a/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.tsx b/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.tsx index a2e9d06a5714..42d5fa6e3768 100644 --- a/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.tsx +++ b/docs/data/tree-view/simple-tree-view/customization/HeadlessAPI.tsx @@ -3,24 +3,24 @@ import { styled } from '@mui/material/styles'; import Box from '@mui/material/Box'; import Avatar from '@mui/material/Avatar'; import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; -import { useTreeItem2, UseTreeItem2Parameters } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem, UseTreeItemParameters } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; -const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ padding: theme.spacing(0.5, 1), })); interface CustomTreeItemProps - extends Omit, + extends Omit, Omit, 'onFocus'> {} const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -37,16 +37,16 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getLabelProps, getGroupTransitionProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); return ( - - + + - - - - + + + + ({ @@ -58,12 +58,12 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( > {(label as string)[0]} - + - {children && } - - + {children && } + + ); }); diff --git a/docs/data/tree-view/simple-tree-view/customization/customization.md b/docs/data/tree-view/simple-tree-view/customization/customization.md index 4a18e36b438b..c1f7d336bfd7 100644 --- a/docs/data/tree-view/simple-tree-view/customization/customization.md +++ b/docs/data/tree-view/simple-tree-view/customization/customization.md @@ -1,7 +1,7 @@ --- productId: x-tree-view title: Simple Tree View - Customization -components: SimpleTreeView, TreeItem, TreeItem2 +components: SimpleTreeView, TreeItem packageName: '@mui/x-tree-view' githubLabel: 'component: tree view' waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ @@ -26,7 +26,7 @@ The demo below shows how to add icons using both an existing icon library, such ### Custom toggle animations -Use the `groupTransition` slot on the `TreeItem` to pass a component that handles your animation. +Use the `groupTransition` slot on the Tree Item to pass a component that handles your animation. The demo below is animated using Material UI's [Collapse](/material-ui/transitions/#collapse) component together with the [react-spring](https://www.react-spring.dev/) library. @@ -46,7 +46,7 @@ Learn more about the anatomy of the Tree Items and the customization utilities p ### Headless API -Use the `useTreeItem2` hook to create your own component. +Use the `useTreeItem` hook to create your own component. The demo below shows how to add an avatar and custom typography elements. {{"demo": "HeadlessAPI.js", "defaultCodeOpen": false}} @@ -61,13 +61,6 @@ Target the `treeItemClasses.groupTransition` class to add connection borders bet ### Gmail clone -:::warning -This example is built using the new `TreeItem2` component -which adds several slots to modify the content of the Tree Item or change its behavior. - -You can learn more about this new component in the [Overview page](/x/react-tree-view/#tree-item-components). -::: - Google's Gmail side nav is potentially one of the web's most famous tree view components. The demo below shows how to replicate it. diff --git a/docs/data/tree-view/simple-tree-view/focus/focus.md b/docs/data/tree-view/simple-tree-view/focus/focus.md index ef9cd7d5558e..8cc4576f617e 100644 --- a/docs/data/tree-view/simple-tree-view/focus/focus.md +++ b/docs/data/tree-view/simple-tree-view/focus/focus.md @@ -41,7 +41,7 @@ apiRef.current.focusItem( :::info This method only works with items that are currently visible. -Calling `apiRef.focusItem` on an item whose parent is collapsed will do nothing. +Calling `apiRef.focusItem()` on an item whose parent is collapsed does nothing. ::: {{"demo": "ApiMethodFocusItem.js"}} diff --git a/docs/data/tree-view/simple-tree-view/items/items.md b/docs/data/tree-view/simple-tree-view/items/items.md index c177b4769e2f..cd516f83665a 100644 --- a/docs/data/tree-view/simple-tree-view/items/items.md +++ b/docs/data/tree-view/simple-tree-view/items/items.md @@ -51,7 +51,7 @@ Use the `disabled` prop on the Tree Item component to disable interaction and fo #### The disabledItemsFocusable prop -Note that the demo above also includes a switch. +Note that the demo below also includes a switch. This toggles the `disabledItemsFocusable` prop, which controls whether or not a disabled Tree Item can be focused. When this prop is set to false: @@ -91,13 +91,13 @@ const apiRef = useTreeViewApiRef(); return {children}; ``` -When your component first renders, `apiRef` will be `undefined`. +When your component first renders, `apiRef` is `undefined`. After this initial render, `apiRef` holds methods to interact imperatively with the Tree View. ::: ### Get an item's DOM element by ID -Use the `getItemDOMElement` API method to get an item's DOM element by its ID. +Use the `getItemDOMElement()` API method to get an item's DOM element by its ID. ```ts const itemElement = apiRef.current.getItemDOMElement( diff --git a/docs/data/tree-view/simple-tree-view/selection/selection.md b/docs/data/tree-view/simple-tree-view/selection/selection.md index aceb02cb7215..c9f44a4e63ef 100644 --- a/docs/data/tree-view/simple-tree-view/selection/selection.md +++ b/docs/data/tree-view/simple-tree-view/selection/selection.md @@ -91,7 +91,7 @@ After this initial render, `apiRef` holds methods to interact imperatively with ### Select or deselect an item -Use the `selectItem` API method to select or deselect an item: +Use the `selectItem()` API method to select or deselect an item: ```ts apiRef.current.selectItem({ diff --git a/docs/data/tree-view/tree-item-customization/CheckboxSlot.js b/docs/data/tree-view/tree-item-customization/CheckboxSlot.js index 81245357631e..fe14e7b79ae3 100644 --- a/docs/data/tree-view/tree-item-customization/CheckboxSlot.js +++ b/docs/data/tree-view/tree-item-customization/CheckboxSlot.js @@ -1,7 +1,7 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; import { MUI_X_PRODUCTS } from './products'; const CustomCheckbox = React.forwardRef(function CustomCheckbox(props, ref) { @@ -10,7 +10,7 @@ const CustomCheckbox = React.forwardRef(function CustomCheckbox(props, ref) { const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { return ( - , ) { return ( - , ) { return ( - , checkedIcon: , }, - } as TreeItem2SlotProps + } as TreeItemSlotProps } /> ); diff --git a/docs/data/tree-view/tree-item-customization/ContentSlot.js b/docs/data/tree-view/tree-item-customization/ContentSlot.js index 78857c927c61..5404af12ac70 100644 --- a/docs/data/tree-view/tree-item-customization/ContentSlot.js +++ b/docs/data/tree-view/tree-item-customization/ContentSlot.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { alpha, styled } from '@mui/material/styles'; import Stack from '@mui/material/Stack'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; import { MUI_X_PRODUCTS } from './products'; @@ -34,7 +34,7 @@ const CustomContent = styled('div')(({ theme }) => ({ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { return ( - ({ @@ -17,14 +17,14 @@ const CustomContent = styled('div')(({ theme }) => ({ }, variants: [ { - props: ({ status }: UseTreeItem2ContentSlotOwnProps) => status.disabled, + props: ({ status }: UseTreeItemContentSlotOwnProps) => status.disabled, style: { opacity: 0.5, backgroundColor: theme.palette.action.disabledBackground, }, }, { - props: ({ status }: UseTreeItem2ContentSlotOwnProps) => status.selected, + props: ({ status }: UseTreeItemContentSlotOwnProps) => status.selected, style: { backgroundColor: alpha((theme.vars || theme).palette.primary.main, 0.4), }, @@ -33,11 +33,11 @@ const CustomContent = styled('div')(({ theme }) => ({ })); const CustomTreeItem = React.forwardRef(function CustomTreeItem( - props: TreeItem2Props, + props: TreeItemProps, ref: React.Ref, ) { return ( - , ) { return ( - ); diff --git a/docs/data/tree-view/tree-item-customization/CustomTreeItemDemo.js b/docs/data/tree-view/tree-item-customization/CustomTreeItemDemo.js index b5e9421b1df5..785462f87910 100644 --- a/docs/data/tree-view/tree-item-customization/CustomTreeItemDemo.js +++ b/docs/data/tree-view/tree-item-customization/CustomTreeItemDemo.js @@ -5,19 +5,19 @@ import Popover from '@mui/material/Popover'; import Typography from '@mui/material/Typography'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Label, - TreeItem2Root, - TreeItem2Checkbox, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; -import { TreeItem2LabelInput } from '@mui/x-tree-view/TreeItem2LabelInput'; + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemLabel, + TreeItemRoot, + TreeItemCheckbox, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; +import { TreeItemLabelInput } from '@mui/x-tree-view/TreeItemLabelInput'; const ITEMS = [ { @@ -35,7 +35,7 @@ const AnnotationText = styled(Typography)(({ theme }) => ({ fontSize: theme.typography.pxToRem(11), })); -const CustomTreeItem2Transition = styled(TreeItem2GroupTransition)(({ theme }) => ({ +const CustomTreeItemTransition = styled(TreeItemGroupTransition)(({ theme }) => ({ padding: 6, border: '1px solid transparent', '&:hover:not(:has(:hover))': { @@ -43,34 +43,34 @@ const CustomTreeItem2Transition = styled(TreeItem2GroupTransition)(({ theme }) = }, })); -const CustomTreeItem2LabelInput = styled(TreeItem2LabelInput)(({ theme }) => ({ +const CustomTreeItemLabelInput = styled(TreeItemLabelInput)(({ theme }) => ({ color: theme.palette.text.primary, border: '1px solid transparent', '&:hover': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2Content = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ border: '1px solid transparent', '&:hover:not(:has(:hover))': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2IconContainer = styled(TreeItem2IconContainer)(({ theme }) => ({ +const CustomTreeItemIconContainer = styled(TreeItemIconContainer)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, border: '1px solid transparent', '&:hover': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2Label = styled(TreeItem2Label)(({ theme }) => ({ +const CustomTreeItemLabel = styled(TreeItemLabel)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, border: '1px solid transparent', '&:hover': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2Checkbox = styled(TreeItem2Checkbox)(({ theme }) => ({ +const CustomTreeItemCheckbox = styled(TreeItemCheckbox)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, border: '1px solid transparent', '&:hover': { @@ -100,65 +100,65 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { getDragAndDropOverlayProps, getLabelInputProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); return ( - - + - - - - - + + {status?.editable ? ( - ) : ( - )} - - + {children && ( - )} - - + + ({ fontSize: theme.typography.pxToRem(11), })); -const CustomTreeItem2Transition = styled(TreeItem2GroupTransition)(({ theme }) => ({ +const CustomTreeItemTransition = styled(TreeItemGroupTransition)(({ theme }) => ({ padding: 6, border: '1px solid transparent', '&:hover:not(:has(:hover))': { @@ -48,34 +48,34 @@ const CustomTreeItem2Transition = styled(TreeItem2GroupTransition)(({ theme }) = }, })); -const CustomTreeItem2LabelInput = styled(TreeItem2LabelInput)(({ theme }) => ({ +const CustomTreeItemLabelInput = styled(TreeItemLabelInput)(({ theme }) => ({ color: theme.palette.text.primary, border: '1px solid transparent', '&:hover': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2Content = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ border: '1px solid transparent', '&:hover:not(:has(:hover))': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2IconContainer = styled(TreeItem2IconContainer)(({ theme }) => ({ +const CustomTreeItemIconContainer = styled(TreeItemIconContainer)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, border: '1px solid transparent', '&:hover': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2Label = styled(TreeItem2Label)(({ theme }) => ({ +const CustomTreeItemLabel = styled(TreeItemLabel)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, border: '1px solid transparent', '&:hover': { borderColor: theme.palette.primary.main, }, })); -const CustomTreeItem2Checkbox = styled(TreeItem2Checkbox)(({ theme }) => ({ +const CustomTreeItemCheckbox = styled(TreeItemCheckbox)(({ theme }) => ({ borderRadius: theme.shape.borderRadius, border: '1px solid transparent', '&:hover': { @@ -84,7 +84,7 @@ const CustomTreeItem2Checkbox = styled(TreeItem2Checkbox)(({ theme }) => ({ })); interface CustomTreeItemProps - extends Omit, + extends Omit, Omit, 'onFocus'> {} const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -112,65 +112,65 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getDragAndDropOverlayProps, getLabelInputProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); return ( - - + - - - - - + + {status?.editable ? ( - ) : ( - )} - - + {children && ( - )} - - + + , ) { - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -23,7 +23,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( // Do something when the checkbox is clicked }; - const handleCheckboxOnChange: UseTreeItem2CheckboxSlotOwnProps['onChange'] = ( + const handleCheckboxOnChange: UseTreeItemCheckboxSlotOwnProps['onChange'] = ( event, ) => { event.defaultMuiPrevented = true; @@ -32,13 +32,13 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }; return ( - ); diff --git a/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.js b/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.js index 07f41849946f..e160321aa89c 100644 --- a/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.js +++ b/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.js @@ -4,16 +4,16 @@ import IconButton from '@mui/material/IconButton'; import AddBoxOutlinedIcon from '@mui/icons-material/AddBoxOutlined'; import IndeterminateCheckBoxOutlinedIcon from '@mui/icons-material/IndeterminateCheckBoxOutlined'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2Label, - TreeItem2Root, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; -import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; + TreeItemContent, + TreeItemLabel, + TreeItemRoot, + TreeItemGroupTransition, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; +import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; import { MUI_X_PRODUCTS } from './products'; const CustomTreeItem = React.forwardRef(function CustomTreeItem( @@ -27,9 +27,9 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getGroupTransitionProps, getDragAndDropOverlayProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId, children, }); @@ -39,8 +39,8 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }; return ( - - + + {status.expandable && ( )} - - - - - {children && } - - + + + + + {children && } + + ); }); diff --git a/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.tsx b/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.tsx index 59d5869eeff8..9b4dad21c33d 100644 --- a/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.tsx +++ b/docs/data/tree-view/tree-item-customization/HandleExpansionDemo.tsx @@ -4,21 +4,21 @@ import IconButton from '@mui/material/IconButton'; import AddBoxOutlinedIcon from '@mui/icons-material/AddBoxOutlined'; import IndeterminateCheckBoxOutlinedIcon from '@mui/icons-material/IndeterminateCheckBoxOutlined'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2Label, - TreeItem2Root, - TreeItem2Props, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; -import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; + TreeItemContent, + TreeItemLabel, + TreeItemRoot, + TreeItemProps, + TreeItemGroupTransition, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; +import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; import { MUI_X_PRODUCTS } from './products'; const CustomTreeItem = React.forwardRef(function CustomTreeItem( - { id, itemId, label, disabled, children }: TreeItem2Props, + { id, itemId, label, disabled, children }: TreeItemProps, ref: React.Ref, ) { const { @@ -28,9 +28,9 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getGroupTransitionProps, getDragAndDropOverlayProps, status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); - const { interactions } = useTreeItem2Utils({ + const { interactions } = useTreeItemUtils({ itemId, children, }); @@ -40,8 +40,8 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }; return ( - - + + {status.expandable && ( )} - - + + - - - {children && } - - + + + {children && } + + ); }); diff --git a/docs/data/tree-view/tree-item-customization/HandleSelectionDemo.js b/docs/data/tree-view/tree-item-customization/HandleSelectionDemo.js index 09caf51788b1..8723527c35dc 100644 --- a/docs/data/tree-view/tree-item-customization/HandleSelectionDemo.js +++ b/docs/data/tree-view/tree-item-customization/HandleSelectionDemo.js @@ -5,10 +5,10 @@ import IconButton from '@mui/material/IconButton'; import Typography from '@mui/material/Typography'; import PanoramaFishEyeIcon from '@mui/icons-material/PanoramaFishEye'; import CircleIcon from '@mui/icons-material/Circle'; -import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; +import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; import { MUI_X_PRODUCTS } from './products'; function CustomLabel({ children, status, onClick, ...props }) { @@ -33,7 +33,7 @@ function CustomLabel({ children, status, onClick, ...props }) { } const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { - const { interactions, status } = useTreeItem2Utils({ + const { interactions, status } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -46,7 +46,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { }; return ( - ; } @@ -46,14 +46,14 @@ function CustomLabel({ children, status, onClick, ...props }: CustomLabelProps) } const CustomTreeItem = React.forwardRef(function CustomTreeItem( - props: TreeItem2Props, + props: TreeItemProps, ref: React.Ref, ) { - const { interactions, status } = useTreeItem2Utils({ + const { interactions, status } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); - const handleContentClick: UseTreeItem2ContentSlotOwnProps['onClick'] = (event) => { + const handleContentClick: UseTreeItemContentSlotOwnProps['onClick'] = (event) => { event.defaultMuiPrevented = true; }; @@ -62,7 +62,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }; return ( - ); diff --git a/docs/data/tree-view/tree-item-customization/LabelSlot.js b/docs/data/tree-view/tree-item-customization/LabelSlot.js index de54cf4eb424..fcac64f4f9e4 100644 --- a/docs/data/tree-view/tree-item-customization/LabelSlot.js +++ b/docs/data/tree-view/tree-item-customization/LabelSlot.js @@ -2,9 +2,9 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; -import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; +import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; export const MUI_X_PRODUCTS = [ { @@ -70,7 +70,7 @@ function CustomLabel({ children, className, secondaryLabel }) { } const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { - const { publicAPI } = useTreeItem2Utils({ + const { publicAPI } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -78,7 +78,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { const item = publicAPI.getItem(props.itemId); return ( - , ) { - const { publicAPI } = useTreeItem2Utils({ + const { publicAPI } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -95,7 +95,7 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( const item = publicAPI.getItem(props.itemId); return ( - , ) { return ( - - - -{{"component": "modules/components/TreeItem2Anatomy.js"}} + +Tree Item anatomy + + +Tree Item anatomy + ### Content @@ -81,12 +83,11 @@ By default, a nested item is indented by `12px` from its parent item. {{"demo": "ItemChildrenIndentationProp.js"}} :::success -This feature is compatible with both the `TreeItem` and `TreeItem2` components If you are using a custom Tree Item component, and you want to override the padding, then apply the following padding to your `groupTransition` element: ```ts -const CustomTreeItem2GroupTransition = styled(TreeItem2GroupTransition)(({ theme }) => ({ +const CustomTreeItemGroupTransition = styled(TreeItemGroupTransition)(({ theme }) => ({ // ...other styles paddingLeft: `var(--TreeView-itemChildrenIndentation)`, } @@ -95,7 +96,7 @@ const CustomTreeItem2GroupTransition = styled(TreeItem2GroupTransition)(({ theme If you are using the `indentationAtItemLevel` prop, then instead apply the following padding to your `content` element: ```ts -const CustomTreeItem2Content = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ // ...other styles paddingLeft: `calc(${theme.spacing(1)} + var(--TreeView-itemChildrenIndentation) * var(--TreeView-itemDepth))`, @@ -116,12 +117,11 @@ It will become the default behavior in the next major version of the Tree View c {{"demo": "IndentationAtItemLevel.js"}} :::success -This feature is compatible with both the `TreeItem` and `TreeItem2` components and with the `itemChildrenIndentation` prop. If you are using a custom Tree Item component, and you want to override the padding, then apply the following padding to your `content` element: ```ts -const CustomTreeItem2Content = styled(TreeItem2Content)(({ theme }) => ({ +const CustomTreeItemContent = styled(TreeItemContent)(({ theme }) => ({ // ...other styles paddingLeft: `calc(${theme.spacing(1)} + var(--TreeView-itemChildrenIndentation) * var(--TreeView-itemDepth))`, @@ -132,19 +132,19 @@ const CustomTreeItem2Content = styled(TreeItem2Content)(({ theme }) => ({ ## Hooks -### useTreeItem2 +### useTreeItem -The `useTreeItem2` hook lets you manage and customize individual Tree Items. +The `useTreeItem` hook lets you manage and customize individual Tree Items. You can use it to get the properties needed for all slots, the status of any given Item, or to tap into the interactive API of the Tree View. #### Slot properties -The `useTreeItem2` hook gives you granular control over an Item's layout by providing resolvers to get the appropriate props for each slot. +The `useTreeItem` hook gives you granular control over an Item's layout by providing resolvers to get the appropriate props for each slot. This makes it possible to build a fully custom layout for your Tree Items. The demo below shows how to get the props needed for each slot, and how to pass them correctly. -{{"demo": "useTreeItem2HookProperties.js"}} +{{"demo": "useTreeItemHookProperties.js"}} You can pass additional props to a slot—or override existing slots—by passing an object argument to the slot's props resolver, as shown below: @@ -159,34 +159,34 @@ You can pass additional props to a slot—or override existing slots—by passin #### Item status -The `useTreeItem2` hook also returns a `status` object that holds boolean values for each possible state of a Tree Item. +The `useTreeItem` hook also returns a `status` object that holds boolean values for each possible state of a Tree Item. ```jsx const { status: { expanded, expandable, focused, selected, disabled, editable, editing }, -} = useTreeItem2(props); +} = useTreeItem(props); ``` You can use these statuses to apply custom styling to the item or conditionally render subcomponents. -{{"demo": "useTreeItem2HookStatus.js"}} +{{"demo": "useTreeItemHookStatus.js"}} #### Imperative API The `publicAPI` object provides a number of methods to programmatically interact with the Tree View. -You can use the `useTreeItem2` hook to access the `publicAPI` object from within a Tree Item. +You can use the `useTreeItem` hook to access the `publicAPI` object from within a Tree Item. -{{"demo": "useTreeItem2HookPublicAPI.js"}} +{{"demo": "useTreeItemHookPublicAPI.js"}} See the **Imperative API** section on each feature page to learn more about the public API methods available on the Tree View. -### `useTreeItem2Utils` +### `useTreeItemUtils` -The `useTreeItem2Utils` hook provides a set of interaction methods for implementing custom behaviors for the Tree View. +The `useTreeItemUtils` hook provides a set of interaction methods for implementing custom behaviors for the Tree View. It also returns the status of the Item. ```jsx -const { interactions, status } = useTreeItem2Utils({ +const { interactions, status } = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); @@ -225,7 +225,7 @@ The demo below shows how to introduce a new element that expands and collapses t #### Label editing -The `useTreeItem2Utils` hook provides the following interaction methods relevant to label editing behavior: +The `useTreeItemUtils` hook provides the following interaction methods relevant to label editing behavior: ```jsx const { @@ -234,7 +234,7 @@ const { handleCancelItemLabelEditing, handleSaveItemLabel, }, -} = useTreeItem2Utils({ +} = useTreeItemUtils({ itemId: props.itemId, children: props.children, }); diff --git a/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.js b/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.js deleted file mode 100644 index b93e60f39e00..000000000000 --- a/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.js +++ /dev/null @@ -1,72 +0,0 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; -import { - TreeItem2Checkbox, - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Label, - TreeItem2Root, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; -import { TreeItem2LabelInput } from '@mui/x-tree-view/TreeItem2LabelInput'; -import { MUI_X_PRODUCTS } from './products'; - -const CustomTreeItem = React.forwardRef(function CustomTreeItem( - { id, itemId, label, disabled, children }, - ref, -) { - const { - getRootProps, - getContentProps, - getIconContainerProps, - getCheckboxProps, - getLabelProps, - getLabelInputProps, - getGroupTransitionProps, - getDragAndDropOverlayProps, - status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); - - return ( - - - - - - - - {status.editing ? ( - - ) : ( - - )} - - - - {children && } - - - ); -}); - -export default function useTreeItem2HookProperties() { - return ( - - - - ); -} diff --git a/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.tsx b/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.tsx deleted file mode 100644 index e2ec284be456..000000000000 --- a/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; -import { - TreeItem2Checkbox, - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2Label, - TreeItem2Root, - TreeItem2Props, - TreeItem2GroupTransition, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2DragAndDropOverlay } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; -import { TreeItem2LabelInput } from '@mui/x-tree-view/TreeItem2LabelInput'; -import { MUI_X_PRODUCTS } from './products'; - -const CustomTreeItem = React.forwardRef(function CustomTreeItem( - { id, itemId, label, disabled, children }: TreeItem2Props, - ref: React.Ref, -) { - const { - getRootProps, - getContentProps, - getIconContainerProps, - getCheckboxProps, - getLabelProps, - getLabelInputProps, - getGroupTransitionProps, - getDragAndDropOverlayProps, - status, - } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); - - return ( - - - - - - - - {status.editing ? ( - - ) : ( - - )} - - - - {children && } - - - ); -}); - -export default function useTreeItem2HookProperties() { - return ( - - - - ); -} diff --git a/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.js b/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.js new file mode 100644 index 000000000000..f9683d989e7b --- /dev/null +++ b/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.js @@ -0,0 +1,72 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; +import { + TreeItemCheckbox, + TreeItemContent, + TreeItemIconContainer, + TreeItemLabel, + TreeItemRoot, + TreeItemGroupTransition, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; +import { TreeItemLabelInput } from '@mui/x-tree-view/TreeItemLabelInput'; +import { MUI_X_PRODUCTS } from './products'; + +const CustomTreeItem = React.forwardRef(function CustomTreeItem( + { id, itemId, label, disabled, children }, + ref, +) { + const { + getRootProps, + getContentProps, + getIconContainerProps, + getCheckboxProps, + getLabelProps, + getLabelInputProps, + getGroupTransitionProps, + getDragAndDropOverlayProps, + status, + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); + + return ( + + + + + + + + {status.editing ? ( + + ) : ( + + )} + + + + {children && } + + + ); +}); + +export default function useTreeItemHookProperties() { + return ( + + + + ); +} diff --git a/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.tsx b/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.tsx new file mode 100644 index 000000000000..8e49e71df547 --- /dev/null +++ b/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.tsx @@ -0,0 +1,73 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; +import { + TreeItemCheckbox, + TreeItemContent, + TreeItemIconContainer, + TreeItemLabel, + TreeItemRoot, + TreeItemProps, + TreeItemGroupTransition, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; +import { TreeItemLabelInput } from '@mui/x-tree-view/TreeItemLabelInput'; +import { MUI_X_PRODUCTS } from './products'; + +const CustomTreeItem = React.forwardRef(function CustomTreeItem( + { id, itemId, label, disabled, children }: TreeItemProps, + ref: React.Ref, +) { + const { + getRootProps, + getContentProps, + getIconContainerProps, + getCheckboxProps, + getLabelProps, + getLabelInputProps, + getGroupTransitionProps, + getDragAndDropOverlayProps, + status, + } = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref }); + + return ( + + + + + + + + {status.editing ? ( + + ) : ( + + )} + + + + {children && } + + + ); +}); + +export default function useTreeItemHookProperties() { + return ( + + + + ); +} diff --git a/docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.tsx.preview b/docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.tsx.preview similarity index 100% rename from docs/data/tree-view/tree-item-customization/useTreeItem2HookProperties.tsx.preview rename to docs/data/tree-view/tree-item-customization/useTreeItemHookProperties.tsx.preview diff --git a/docs/data/tree-view/tree-item-customization/useTreeItem2HookPublicAPI.js b/docs/data/tree-view/tree-item-customization/useTreeItemHookPublicAPI.js similarity index 84% rename from docs/data/tree-view/tree-item-customization/useTreeItem2HookPublicAPI.js rename to docs/data/tree-view/tree-item-customization/useTreeItemHookPublicAPI.js index 086d8d6ff086..72886928944d 100644 --- a/docs/data/tree-view/tree-item-customization/useTreeItem2HookPublicAPI.js +++ b/docs/data/tree-view/tree-item-customization/useTreeItemHookPublicAPI.js @@ -4,8 +4,8 @@ import Typography from '@mui/material/Typography'; import Chip from '@mui/material/Chip'; import Stack from '@mui/material/Stack'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; -import { useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; +import { useTreeItem } from '@mui/x-tree-view/useTreeItem'; import { MUI_X_PRODUCTS } from './products'; function CustomLabel({ children, className, numberOfChildren }) { @@ -25,12 +25,12 @@ function CustomLabel({ children, className, numberOfChildren }) { } const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { - const { publicAPI } = useTreeItem2(props); + const { publicAPI } = useTreeItem(props); const childrenNumber = publicAPI.getItemOrderedChildrenIds(props.itemId).length; return ( - , ) { - const { publicAPI } = useTreeItem2(props); + const { publicAPI } = useTreeItem(props); const childrenNumber = publicAPI.getItemOrderedChildrenIds(props.itemId).length; return ( - - - - - - + + + + + + {status.editing ? ( - + ) : ( - + )} @@ -123,14 +123,14 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( return null; })} - - {children && } - - + + {children && } + + ); }); -export default function useTreeItem2HookStatus() { +export default function useTreeItemHookStatus() { return ( diff --git a/docs/data/tree-view/tree-item-customization/useTreeItem2HookStatus.tsx b/docs/data/tree-view/tree-item-customization/useTreeItemHookStatus.tsx similarity index 76% rename from docs/data/tree-view/tree-item-customization/useTreeItem2HookStatus.tsx rename to docs/data/tree-view/tree-item-customization/useTreeItemHookStatus.tsx index 2ea3153a42ce..d03d53ca63d0 100644 --- a/docs/data/tree-view/tree-item-customization/useTreeItem2HookStatus.tsx +++ b/docs/data/tree-view/tree-item-customization/useTreeItemHookStatus.tsx @@ -11,18 +11,18 @@ import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import DrawOutlinedIcon from '@mui/icons-material/DrawOutlined'; import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; -import { useTreeItem2, UseTreeItem2Status } from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem, UseTreeItemStatus } from '@mui/x-tree-view/useTreeItem'; import { - TreeItem2Content, - TreeItem2Root, - TreeItem2Props, - TreeItem2GroupTransition, - TreeItem2IconContainer, - TreeItem2Label, -} from '@mui/x-tree-view/TreeItem2'; -import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; -import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; -import { TreeItem2LabelInput } from '@mui/x-tree-view/TreeItem2LabelInput'; + TreeItemContent, + TreeItemRoot, + TreeItemProps, + TreeItemGroupTransition, + TreeItemIconContainer, + TreeItemLabel, +} from '@mui/x-tree-view/TreeItem'; +import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon'; +import { TreeItemProvider } from '@mui/x-tree-view/TreeItemProvider'; +import { TreeItemLabelInput } from '@mui/x-tree-view/TreeItemLabelInput'; import { MUI_X_PRODUCTS } from './products'; function StatusLegend() { @@ -75,7 +75,7 @@ function StatusLegend() { } const STATUS_ICONS: { - [K in keyof UseTreeItem2Status]: React.ReactNode; + [K in keyof UseTreeItemStatus]: React.ReactNode; } = { focused: , selected: , @@ -87,7 +87,7 @@ const STATUS_ICONS: { }; const CustomTreeItem = React.forwardRef(function CustomTreeItem( - { id, itemId, label, disabled, children }: TreeItem2Props, + { id, itemId, label, disabled, children }: TreeItemProps, ref: React.Ref, ) { const { @@ -98,24 +98,24 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( getIconContainerProps, getLabelInputProps, status, - } = useTreeItem2({ id, itemId, label, disabled, children, rootRef: ref }); + } = useTreeItem({ id, itemId, label, disabled, children, rootRef: ref }); return ( - - - - - - + + + + + + {status.editing ? ( - + ) : ( - + )} - {(Object.keys(STATUS_ICONS) as [keyof UseTreeItem2Status]).map( + {(Object.keys(STATUS_ICONS) as [keyof UseTreeItemStatus]).map( (iconKey, index) => { if (status[iconKey]) { return ( @@ -128,14 +128,14 @@ const CustomTreeItem = React.forwardRef(function CustomTreeItem( }, )} - - {children && } - - + + {children && } + + ); }); -export default function useTreeItem2HookStatus() { +export default function useTreeItemHookStatus() { return ( diff --git a/docs/data/tree-view/tree-item-customization/useTreeItem2HookStatus.tsx.preview b/docs/data/tree-view/tree-item-customization/useTreeItemHookStatus.tsx.preview similarity index 100% rename from docs/data/tree-view/tree-item-customization/useTreeItem2HookStatus.tsx.preview rename to docs/data/tree-view/tree-item-customization/useTreeItemHookStatus.tsx.preview diff --git a/docs/data/tree-view-component-api-pages.ts b/docs/data/treeViewApiPages.ts similarity index 65% rename from docs/data/tree-view-component-api-pages.ts rename to docs/data/treeViewApiPages.ts index c46698f0ed3d..b503ea53e2c8 100644 --- a/docs/data/tree-view-component-api-pages.ts +++ b/docs/data/treeViewApiPages.ts @@ -1,6 +1,6 @@ import type { MuiPage } from 'docs/src/MuiPage'; -const apiPages: MuiPage[] = [ +const treeViewApiPages: MuiPage[] = [ { pathname: '/x/api/tree-view/rich-tree-view', title: 'RichTreeView', @@ -19,12 +19,16 @@ const apiPages: MuiPage[] = [ title: 'TreeItem', }, { - pathname: '/x/api/tree-view/tree-item-2', - title: 'TreeItem2', + pathname: '/x/api/tree-view/tree-item-drag-and-drop-overlay', + title: 'TreeItemDragAndDropOverlay', + }, + { + pathname: '/x/api/tree-view/tree-item-icon', + title: 'TreeItemIcon', }, { pathname: '/x/api/tree-view/tree-view', title: 'TreeView', }, ]; -export default apiPages; +export default treeViewApiPages; diff --git a/docs/package.json b/docs/package.json index cfe7ab58704a..7c1d665c25d3 100644 --- a/docs/package.json +++ b/docs/package.json @@ -9,7 +9,7 @@ "build:clean": "rimraf .next && pnpm build", "build-sw": "node ./scripts/buildServiceWorker.js", "dev": "next dev --port 3001", - "deploy": "git push -f upstream master:docs-v7", + "deploy": "git push -f upstream master:docs-next", "icons": "rimraf public/static/icons/* && node ./scripts/buildIcons.js", "serve": "serve ./export -l 3010", "create-playground": "cpy --cwd=scripts playground.template.tsx ../../pages/playground --rename=index.tsx", @@ -84,13 +84,14 @@ "react": "^18.3.1", "react-docgen": "^5.4.3", "react-dom": "^18.3.1", - "react-hook-form": "^7.53.0", + "react-hook-form": "^7.53.1", "react-is": "^18.3.1", "react-router": "^6.27.0", "react-router-dom": "^6.27.0", "react-runner": "^1.0.5", "react-simple-code-editor": "^0.14.1", "recast": "^0.23.9", + "rifm": "0.12.1", "rimraf": "^6.0.1", "rxjs": "^7.8.1", "styled-components": "^6.1.13", @@ -108,7 +109,7 @@ "@types/d3-scale-chromatic": "^3.0.3", "@types/doctrine": "^0.0.9", "@types/gtag.js": "^0.0.20", - "@types/lodash": "^4.17.10", + "@types/lodash": "^4.17.12", "@types/luxon": "^3.4.2", "@types/moment-hijri": "^2.1.4", "@types/moment-jalaali": "^0.7.9", @@ -118,6 +119,6 @@ "@types/stylis": "^4.2.6", "@types/webpack-bundle-analyzer": "^4.7.0", "gm": "^1.25.0", - "serve": "^14.2.3" + "serve": "^14.2.4" } } diff --git a/docs/pages/_app.js b/docs/pages/_app.js index 08a025daad50..8f1471a414c7 100644 --- a/docs/pages/_app.js +++ b/docs/pages/_app.js @@ -30,9 +30,9 @@ LicenseInfo.setLicenseKey(process.env.NEXT_PUBLIC_MUI_LICENSE); function getMuiPackageVersion(packageName, commitRef) { if (commitRef === undefined) { // #default-branch-switch - // Use the "latest" npm tag for the master git branch - // Use the "next" npm tag for the next git branch - return 'latest'; + // Use the "next" tag for the master git branch after we start working on the next major version + // Once the major release is finished we can go back to "latest" + return 'next'; } const shortSha = commitRef.slice(0, 8); return `https://pkg.csb.dev/mui/mui-x/commit/${shortSha}/@mui/${packageName}`; @@ -53,7 +53,7 @@ ponyfillGlobal.muiDocConfig = { return newDeps; }, csbGetVersions: (versions, { muiCommitRef }) => { - const output = { + return { ...versions, '@mui/x-data-grid': getMuiPackageVersion('x-data-grid', muiCommitRef), '@mui/x-data-grid-pro': getMuiPackageVersion('x-data-grid-pro', muiCommitRef), @@ -68,7 +68,6 @@ ponyfillGlobal.muiDocConfig = { '@mui/x-internals': getMuiPackageVersion('x-internals', muiCommitRef), exceljs: 'latest', }; - return output; }, postProcessImport, }; @@ -206,17 +205,34 @@ function AppWrapper(props) { const pageContextValue = React.useMemo(() => { const { activePage, activePageParents } = findActivePage(pages, router.pathname); const languagePrefix = pageProps.userLanguage === 'en' ? '' : `/${pageProps.userLanguage}`; + const productIdSubpathMap = { + introduction: '/x/introduction', + 'x-data-grid': '/x/react-data-grid', + 'x-date-pickers': '/x/react-date-pickers', + 'x-charts': '/x/react-charts', + 'x-tree-view': '/x/react-tree-view', + }; + + const getVersionOptions = (id, versions) => + versions.map((version) => { + if (version === process.env.LIB_VERSION) { + return { + current: true, + text: `v${version}`, + href: `${languagePrefix}${productIdSubpathMap[id]}/`, + }; + } + return { + text: version, + href: `https://${version}.mui.com${languagePrefix}${productIdSubpathMap[id]}/`, + }; + }); let productIdentifier = { metadata: '', name: 'MUI X', versions: [ - { - text: `v${process.env.LIB_VERSION}`, - current: true, - }, - { text: 'v6', href: `https://v6.mui.com${languagePrefix}/x/introduction/` }, - { text: 'v5', href: `https://v5.mui.com${languagePrefix}/x/introduction/` }, + ...getVersionOptions('introduction', ['next', process.env.LIB_VERSION, 'v6', 'v5']), { text: 'v4', href: `https://v4.mui.com${languagePrefix}/components/data-grid/` }, ], }; @@ -226,12 +242,7 @@ function AppWrapper(props) { metadata: 'MUI X', name: 'Data Grid', versions: [ - { - text: `v${process.env.DATA_GRID_VERSION}`, - current: true, - }, - { text: 'v6', href: `https://v6.mui.com${languagePrefix}/x/react-data-grid/` }, - { text: 'v5', href: `https://v5.mui.com${languagePrefix}/x/react-data-grid/` }, + ...getVersionOptions('x-data-grid', ['next', process.env.DATA_GRID_VERSION, 'v6', 'v5']), { text: 'v4', href: `https://v4.mui.com${languagePrefix}/components/data-grid/` }, ], }; @@ -240,14 +251,7 @@ function AppWrapper(props) { metadata: 'MUI X', name: 'Date Pickers', versions: [ - { - text: `v${process.env.DATE_PICKERS_VERSION}`, - current: true, - }, - { - text: 'v6', - href: `https://v6.mui.com${languagePrefix}/x/react-date-pickers/`, - }, + ...getVersionOptions('x-date-pickers', ['next', process.env.DATE_PICKERS_VERSION, 'v6']), { text: 'v5', href: `https://v5.mui.com${languagePrefix}/x/react-date-pickers/getting-started/`, @@ -258,23 +262,14 @@ function AppWrapper(props) { productIdentifier = { metadata: 'MUI X', name: 'Charts', - versions: [ - { - text: `v${process.env.CHARTS_VERSION}`, - current: true, - }, - { text: 'v6', href: `https://v6.mui.com${languagePrefix}/x/react-charts/` }, - ], + versions: getVersionOptions('x-charts', ['next', process.env.CHARTS_VERSION, 'v6']), }; } else if (productId === 'x-tree-view') { productIdentifier = { metadata: 'MUI X', name: 'Tree View', versions: [ - { - text: `v${process.env.TREE_VIEW_VERSION}`, - current: true, - }, + ...getVersionOptions('x-tree-view', ['next', process.env.TREE_VIEW_VERSION]), { text: 'v6', href: `https://v6.mui.com${languagePrefix}/x/react-tree-view/getting-started`, 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 478f8854a196..ded5c1bd3c90 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -796,12 +796,36 @@ "default": "GridRow", "class": "MuiDataGridPremium-row" }, + { + "name": "baseBadge", + "description": "The custom Badge component used in the grid for both header and cells.", + "default": "Badge", + "class": null + }, { "name": "baseCheckbox", "description": "The custom Checkbox component used in the grid for both header and cells.", "default": "Checkbox", "class": null }, + { + "name": "baseDivider", + "description": "The custom Divider component used in the grid.", + "default": "Divider", + "class": null + }, + { + "name": "baseMenuList", + "description": "The custom MenuList component used in the grid.", + "default": "MenuList", + "class": null + }, + { + "name": "baseMenuItem", + "description": "The custom MenuItem component used in the grid.", + "default": "MenuItem", + "class": null + }, { "name": "baseInputAdornment", "description": "The custom InputAdornment component used in the grid.", 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 54a8ab54294e..42df3c139311 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -728,12 +728,36 @@ "default": "GridRow", "class": "MuiDataGridPro-row" }, + { + "name": "baseBadge", + "description": "The custom Badge component used in the grid for both header and cells.", + "default": "Badge", + "class": null + }, { "name": "baseCheckbox", "description": "The custom Checkbox component used in the grid for both header and cells.", "default": "Checkbox", "class": null }, + { + "name": "baseDivider", + "description": "The custom Divider component used in the grid.", + "default": "Divider", + "class": null + }, + { + "name": "baseMenuList", + "description": "The custom MenuList component used in the grid.", + "default": "MenuList", + "class": null + }, + { + "name": "baseMenuItem", + "description": "The custom MenuItem component used in the grid.", + "default": "MenuItem", + "class": null + }, { "name": "baseInputAdornment", "description": "The custom InputAdornment component used in the grid.", diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index 25e56f8402bc..9994e0dc6317 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -615,12 +615,36 @@ "default": "GridRow", "class": "MuiDataGrid-row" }, + { + "name": "baseBadge", + "description": "The custom Badge component used in the grid for both header and cells.", + "default": "Badge", + "class": null + }, { "name": "baseCheckbox", "description": "The custom Checkbox component used in the grid for both header and cells.", "default": "Checkbox", "class": null }, + { + "name": "baseDivider", + "description": "The custom Divider component used in the grid.", + "default": "Divider", + "class": null + }, + { + "name": "baseMenuList", + "description": "The custom MenuList component used in the grid.", + "default": "MenuList", + "class": null + }, + { + "name": "baseMenuItem", + "description": "The custom MenuItem component used in the grid.", + "default": "MenuItem", + "class": null + }, { "name": "baseInputAdornment", "description": "The custom InputAdornment component used in the grid.", diff --git a/docs/pages/x/api/data-grid/grid-print-export-options.json b/docs/pages/x/api/data-grid/grid-print-export-options.json index 6d19c1df3ffc..09e234b76a5a 100644 --- a/docs/pages/x/api/data-grid/grid-print-export-options.json +++ b/docs/pages/x/api/data-grid/grid-print-export-options.json @@ -18,6 +18,6 @@ "hideFooter": { "type": { "description": "boolean" }, "default": "false" }, "hideToolbar": { "type": { "description": "boolean" }, "default": "false" }, "includeCheckboxes": { "type": { "description": "boolean" }, "default": "false" }, - "pageStyle": { "type": { "description": "string | Function" } } + "pageStyle": { "type": { "description": "string | (() => string)" } } } } 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 7cd100636eb8..79efa8560520 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 @@ -134,6 +134,10 @@ } }, "selectedItems": { "type": { "name": "any" } }, + "selectionPropagation": { + "type": { "name": "shape", "description": "{ descendants?: bool, parents?: bool }" }, + "default": "{ parents: false, descendants: false }" + }, "slotProps": { "type": { "name": "object" }, "default": "{}" }, "slots": { "type": { "name": "object" }, @@ -178,7 +182,7 @@ }, { "name": "endIcon", - "description": "The default icon displayed next to an end item.\nThis is applied to all tree items and can be overridden by the TreeItem `icon` slot prop.", + "description": "The default icon displayed next to an end item.\nThis is applied to all Tree Items and can be overridden by the TreeItem `icon` slot prop.", "class": null } ], 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 53335f98fc5e..83ada41f5bc5 100644 --- a/docs/pages/x/api/tree-view/rich-tree-view.json +++ b/docs/pages/x/api/tree-view/rich-tree-view.json @@ -109,6 +109,10 @@ } }, "selectedItems": { "type": { "name": "any" } }, + "selectionPropagation": { + "type": { "name": "shape", "description": "{ descendants?: bool, parents?: bool }" }, + "default": "{ parents: false, descendants: false }" + }, "slotProps": { "type": { "name": "object" }, "default": "{}" }, "slots": { "type": { "name": "object" }, @@ -153,7 +157,7 @@ }, { "name": "endIcon", - "description": "The default icon displayed next to an end item.\nThis is applied to all tree items and can be overridden by the TreeItem `icon` slot prop.", + "description": "The default icon displayed next to an end item.\nThis is applied to all Tree Items and can be overridden by the TreeItem `icon` slot prop.", "class": null } ], @@ -164,6 +168,6 @@ "forwardsRefTo": "HTMLUListElement", "filename": "/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx", "inheritance": null, - "demos": "", + "demos": "", "cssComponent": false } diff --git a/docs/pages/x/api/tree-view/simple-tree-view.json b/docs/pages/x/api/tree-view/simple-tree-view.json index 0e9d838a7327..bcf5153e9cb5 100644 --- a/docs/pages/x/api/tree-view/simple-tree-view.json +++ b/docs/pages/x/api/tree-view/simple-tree-view.json @@ -73,6 +73,10 @@ } }, "selectedItems": { "type": { "name": "any" } }, + "selectionPropagation": { + "type": { "name": "shape", "description": "{ descendants?: bool, parents?: bool }" }, + "default": "{ parents: false, descendants: false }" + }, "slotProps": { "type": { "name": "object" } }, "slots": { "type": { "name": "object" }, "additionalInfo": { "slotsApi": true } }, "sx": { @@ -108,7 +112,7 @@ }, { "name": "endIcon", - "description": "The default icon displayed next to an end item.\nThis is applied to all tree items and can be overridden by the TreeItem `icon` slot prop.", + "description": "The default icon displayed next to an end item.\nThis is applied to all Tree Items and can be overridden by the TreeItem `icon` slot prop.", "class": null } ], diff --git a/docs/pages/x/api/tree-view/tree-item-2.json b/docs/pages/x/api/tree-view/tree-item-2.json deleted file mode 100644 index 547383a665af..000000000000 --- a/docs/pages/x/api/tree-view/tree-item-2.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "props": { - "itemId": { "type": { "name": "string" }, "required": true }, - "children": { "type": { "name": "node" } }, - "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, - "disabled": { "type": { "name": "bool" }, "default": "false" }, - "id": { "type": { "name": "string" } }, - "label": { "type": { "name": "node" } }, - "onBlur": { "type": { "name": "func" } }, - "onFocus": { "type": { "name": "custom", "description": "unsupportedProp" } }, - "onKeyDown": { "type": { "name": "func" } }, - "slotProps": { "type": { "name": "object" }, "default": "{}" }, - "slots": { - "type": { "name": "object" }, - "default": "{}", - "additionalInfo": { "slotsApi": true } - } - }, - "name": "TreeItem2", - "imports": [ - "import { TreeItem2 } from '@mui/x-tree-view/TreeItem2';", - "import { TreeItem2 } from '@mui/x-tree-view';", - "import { TreeItem2 } from '@mui/x-tree-view-pro';" - ], - "slots": [ - { - "name": "root", - "description": "The component that renders the root.", - "default": "TreeItem2Root", - "class": "MuiTreeItem2-root" - }, - { - "name": "content", - "description": "The component that renders the content of the item.\n(e.g.: everything related to this item, not to its children).", - "default": "TreeItem2Content", - "class": "MuiTreeItem2-content" - }, - { - "name": "groupTransition", - "description": "The component that renders the children of the item.", - "default": "TreeItem2GroupTransition", - "class": "MuiTreeItem2-groupTransition" - }, - { - "name": "iconContainer", - "description": "The component that renders the icon.", - "default": "TreeItem2IconContainer", - "class": "MuiTreeItem2-iconContainer" - }, - { - "name": "checkbox", - "description": "The component that renders the item checkbox for selection.", - "default": "TreeItem2Checkbox", - "class": "MuiTreeItem2-checkbox" - }, - { - "name": "label", - "description": "The component that renders the item label.", - "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.", - "default": "TreeItem2DragAndDropOverlay", - "class": "MuiTreeItem2-dragAndDropOverlay" - }, - { "name": "collapseIcon", "description": "The icon used to collapse the item.", "class": null }, - { "name": "expandIcon", "description": "The icon used to expand the item.", "class": null }, - { "name": "endIcon", "description": "The icon displayed next to an end item.", "class": null }, - { - "name": "icon", - "description": "The icon to display next to the tree item's label.", - "class": null - } - ], - "classes": [ - { - "key": "disabled", - "className": "Mui-disabled", - "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", - "description": "State class applied to the content element when expanded.", - "isGlobal": true - }, - { - "key": "focused", - "className": "Mui-focused", - "description": "State class applied to the content element when focused.", - "isGlobal": true - }, - { - "key": "selected", - "className": "Mui-selected", - "description": "State class applied to the content element when selected.", - "isGlobal": true - } - ], - "spread": true, - "themeDefaultProps": true, - "muiName": "MuiTreeItem2", - "forwardsRefTo": "HTMLLIElement", - "filename": "/packages/x-tree-view/src/TreeItem2/TreeItem2.tsx", - "inheritance": null, - "demos": "", - "cssComponent": false -} diff --git a/docs/pages/x/api/tree-view/tree-item-drag-and-drop-overlay.js b/docs/pages/x/api/tree-view/tree-item-drag-and-drop-overlay.js new file mode 100644 index 000000000000..256f275cd77e --- /dev/null +++ b/docs/pages/x/api/tree-view/tree-item-drag-and-drop-overlay.js @@ -0,0 +1,23 @@ +import * as React from 'react'; +import ApiPage from 'docs/src/modules/components/ApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './tree-item-drag-and-drop-overlay.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docsx/translations/api-docs/tree-view/tree-item-drag-and-drop-overlay', + false, + /\.\/tree-item-drag-and-drop-overlay.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/x/api/tree-view/tree-item-drag-and-drop-overlay.json b/docs/pages/x/api/tree-view/tree-item-drag-and-drop-overlay.json new file mode 100644 index 000000000000..8c5d38162293 --- /dev/null +++ b/docs/pages/x/api/tree-view/tree-item-drag-and-drop-overlay.json @@ -0,0 +1,15 @@ +{ + "props": {}, + "name": "TreeItemDragAndDropOverlay", + "imports": [ + "import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view/TreeItemDragAndDropOverlay';", + "import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view';", + "import { TreeItemDragAndDropOverlay } from '@mui/x-tree-view-pro';" + ], + "classes": [], + "muiName": "MuiTreeItemDragAndDropOverlay", + "filename": "/packages/x-tree-view/src/TreeItemDragAndDropOverlay/TreeItemDragAndDropOverlay.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/pages/x/api/tree-view/tree-item-2.js b/docs/pages/x/api/tree-view/tree-item-icon.js similarity index 78% rename from docs/pages/x/api/tree-view/tree-item-2.js rename to docs/pages/x/api/tree-view/tree-item-icon.js index 157c5febd2e1..866a7447a59d 100644 --- a/docs/pages/x/api/tree-view/tree-item-2.js +++ b/docs/pages/x/api/tree-view/tree-item-icon.js @@ -1,7 +1,7 @@ import * as React from 'react'; import ApiPage from 'docs/src/modules/components/ApiPage'; import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; -import jsonPageContent from './tree-item-2.json'; +import jsonPageContent from './tree-item-icon.json'; export default function Page(props) { const { descriptions, pageContent } = props; @@ -10,9 +10,9 @@ export default function Page(props) { Page.getInitialProps = () => { const req = require.context( - 'docsx/translations/api-docs/tree-view/tree-item-2', + 'docsx/translations/api-docs/tree-view/tree-item-icon', false, - /\.\/tree-item-2.*.json$/, + /\.\/tree-item-icon.*.json$/, ); const descriptions = mapApiPageTranslations(req); diff --git a/docs/pages/x/api/tree-view/tree-item-icon.json b/docs/pages/x/api/tree-view/tree-item-icon.json new file mode 100644 index 000000000000..adf8622e98fd --- /dev/null +++ b/docs/pages/x/api/tree-view/tree-item-icon.json @@ -0,0 +1,32 @@ +{ + "props": { + "slotProps": { "type": { "name": "object" }, "default": "{}" }, + "slots": { + "type": { "name": "object" }, + "default": "{}", + "additionalInfo": { "slotsApi": true } + } + }, + "name": "TreeItemIcon", + "imports": [ + "import { TreeItemIcon } from '@mui/x-tree-view/TreeItemIcon';", + "import { TreeItemIcon } from '@mui/x-tree-view';", + "import { TreeItemIcon } from '@mui/x-tree-view-pro';" + ], + "slots": [ + { "name": "collapseIcon", "description": "The icon used to collapse the item.", "class": null }, + { "name": "expandIcon", "description": "The icon used to expand the item.", "class": null }, + { "name": "endIcon", "description": "The icon displayed next to an end item.", "class": null }, + { + "name": "icon", + "description": "The icon to display next to the Tree Item's label.", + "class": null + } + ], + "classes": [], + "muiName": "MuiTreeItemIcon", + "filename": "/packages/x-tree-view/src/TreeItemIcon/TreeItemIcon.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/pages/x/api/tree-view/tree-item.json b/docs/pages/x/api/tree-view/tree-item.json index 33853fb02fb1..be0a273ec4c0 100644 --- a/docs/pages/x/api/tree-view/tree-item.json +++ b/docs/pages/x/api/tree-view/tree-item.json @@ -3,19 +3,10 @@ "itemId": { "type": { "name": "string" }, "required": true }, "children": { "type": { "name": "node" } }, "classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } }, - "ContentComponent": { - "type": { "name": "custom", "description": "element type" }, - "default": "TreeItemContent", - "deprecated": true, - "deprecationInfo": "Consider using the TreeItem2 component or the useTreeItem2 hook instead. For more detail, see https://mui.com/x/react-tree-view/tree-item-customization/." - }, - "ContentProps": { - "type": { "name": "object" }, - "deprecated": true, - "deprecationInfo": "Consider using the TreeItem2 component or the useTreeItem2 hook instead. For more detail, see https://mui.com/x/react-tree-view/tree-item-customization/." - }, "disabled": { "type": { "name": "bool" }, "default": "false" }, + "id": { "type": { "name": "string" } }, "label": { "type": { "name": "node" } }, + "onBlur": { "type": { "name": "func" } }, "onFocus": { "type": { "name": "custom", "description": "unsupportedProp" } }, "onKeyDown": { "type": { "name": "func" } }, "slotProps": { "type": { "name": "object" }, "default": "{}" }, @@ -23,13 +14,6 @@ "type": { "name": "object" }, "default": "{}", "additionalInfo": { "slotsApi": true } - }, - "sx": { - "type": { - "name": "union", - "description": "Array<func
| object
| bool>
| func
| object" - }, - "additionalInfo": { "sx": true } } }, "name": "TreeItem", @@ -39,46 +23,70 @@ "import { TreeItem } from '@mui/x-tree-view-pro';" ], "slots": [ - { "name": "collapseIcon", "description": "The icon used to collapse the item.", "class": null }, - { "name": "expandIcon", "description": "The icon used to expand the item.", "class": null }, - { "name": "endIcon", "description": "The icon displayed next to an end item.", "class": null }, { - "name": "icon", - "description": "The icon to display next to the tree item's label.", - "class": null + "name": "root", + "description": "The component that renders the root.", + "default": "TreeItemRoot", + "class": "MuiTreeItem-root" + }, + { + "name": "content", + "description": "The component that renders the content of the item.\n(e.g.: everything related to this item, not to its children).", + "default": "TreeItemContent", + "class": "MuiTreeItem-content" }, { "name": "groupTransition", - "description": "The component that animates the appearance / disappearance of the item's children.", - "default": "TreeItem2Group", + "description": "The component that renders the children of the item.", + "default": "TreeItemGroupTransition", "class": "MuiTreeItem-groupTransition" - } - ], - "classes": [ + }, { - "key": "checkbox", - "className": "MuiTreeItem-checkbox", - "description": "Styles applied to the checkbox element.", - "isGlobal": false + "name": "iconContainer", + "description": "The component that renders the icon.", + "default": "TreeItemIconContainer", + "class": "MuiTreeItem-iconContainer" }, { - "key": "content", - "className": "MuiTreeItem-content", - "description": "Styles applied to the content element.", - "isGlobal": false + "name": "checkbox", + "description": "The component that renders the item checkbox for selection.", + "default": "TreeItemCheckbox", + "class": "MuiTreeItem-checkbox" }, + { + "name": "label", + "description": "The component that renders the item label.", + "default": "TreeItemLabel", + "class": "MuiTreeItem-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": "TreeItemLabelInput", + "class": "MuiTreeItem-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 `` component.", + "default": "TreeItemDragAndDropOverlay", + "class": "MuiTreeItem-dragAndDropOverlay" + }, + { "name": "collapseIcon", "description": "The icon used to collapse the item.", "class": null }, + { "name": "expandIcon", "description": "The icon used to expand the item.", "class": null }, + { "name": "endIcon", "description": "The icon displayed next to an end item.", "class": null }, + { + "name": "icon", + "description": "The icon to display next to the Tree Item's label.", + "class": null + } + ], + "classes": [ { "key": "disabled", "className": "Mui-disabled", "description": "State class applied to the element when disabled.", "isGlobal": true }, - { - "key": "dragAndDropOverlay", - "className": "MuiTreeItem-dragAndDropOverlay", - "description": "Styles applied to the drag and drop overlay.", - "isGlobal": false - }, { "key": "editable", "className": "MuiTreeItem-editable", @@ -103,30 +111,6 @@ "description": "State class applied to the content element when focused.", "isGlobal": true }, - { - "key": "iconContainer", - "className": "MuiTreeItem-iconContainer", - "description": "Styles applied to the tree item icon.", - "isGlobal": false - }, - { - "key": "label", - "className": "MuiTreeItem-label", - "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", - "description": "Styles applied to the root element.", - "isGlobal": false - }, { "key": "selected", "className": "Mui-selected", @@ -140,6 +124,6 @@ "forwardsRefTo": "HTMLLIElement", "filename": "/packages/x-tree-view/src/TreeItem/TreeItem.tsx", "inheritance": null, - "demos": "", + "demos": "", "cssComponent": false } diff --git a/docs/pages/x/api/tree-view/tree-view.json b/docs/pages/x/api/tree-view/tree-view.json index c03fa0e611a9..c9f0691c2b7b 100644 --- a/docs/pages/x/api/tree-view/tree-view.json +++ b/docs/pages/x/api/tree-view/tree-view.json @@ -73,6 +73,10 @@ } }, "selectedItems": { "type": { "name": "any" } }, + "selectionPropagation": { + "type": { "name": "shape", "description": "{ descendants?: bool, parents?: bool }" }, + "default": "{ parents: false, descendants: false }" + }, "slotProps": { "type": { "name": "object" } }, "slots": { "type": { "name": "object" }, "additionalInfo": { "slotsApi": true } }, "sx": { @@ -108,7 +112,7 @@ }, { "name": "endIcon", - "description": "The default icon displayed next to an end item.\nThis is applied to all tree items and can be overridden by the TreeItem `icon` slot prop.", + "description": "The default icon displayed next to an end item.\nThis is applied to all Tree Items and can be overridden by the TreeItem `icon` slot prop.", "class": null } ], diff --git a/docs/pages/x/migration/migration-charts-v7.js b/docs/pages/x/migration/migration-charts-v7.js new file mode 100644 index 000000000000..09b47f90866e --- /dev/null +++ b/docs/pages/x/migration/migration-charts-v7.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/migration/migration-charts-v7/migration-charts-v7.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/x/migration/migration-data-grid-v7.js b/docs/pages/x/migration/migration-data-grid-v7.js new file mode 100644 index 000000000000..454b6a6e813e --- /dev/null +++ b/docs/pages/x/migration/migration-data-grid-v7.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/migration/migration-data-grid-v7/migration-data-grid-v7.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/x/migration/migration-pickers-v7.js b/docs/pages/x/migration/migration-pickers-v7.js new file mode 100644 index 000000000000..24de8699a05f --- /dev/null +++ b/docs/pages/x/migration/migration-pickers-v7.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/migration/migration-pickers-v7/migration-pickers-v7.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/x/migration/migration-tree-view-v7.js b/docs/pages/x/migration/migration-tree-view-v7.js new file mode 100644 index 000000000000..6dd80f8e2a2d --- /dev/null +++ b/docs/pages/x/migration/migration-tree-view-v7.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/migration/migration-tree-view-v7/migration-tree-view-v7.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/public/static/x/tree-view-illustrations/tree-item-dark.png b/docs/public/static/x/tree-view-illustrations/tree-item-dark.png index 9cd285c3d279..4067c0fb25a5 100644 Binary files a/docs/public/static/x/tree-view-illustrations/tree-item-dark.png and b/docs/public/static/x/tree-view-illustrations/tree-item-dark.png differ diff --git a/docs/public/static/x/tree-view-illustrations/tree-item-light.png b/docs/public/static/x/tree-view-illustrations/tree-item-light.png index 052d8359525c..355f6c881340 100644 Binary files a/docs/public/static/x/tree-view-illustrations/tree-item-light.png and b/docs/public/static/x/tree-view-illustrations/tree-item-light.png differ diff --git a/docs/src/modules/components/ChartFeaturesGrid.js b/docs/src/modules/components/ChartFeaturesGrid.js index 36af272ba23c..7da1be867826 100644 --- a/docs/src/modules/components/ChartFeaturesGrid.js +++ b/docs/src/modules/components/ChartFeaturesGrid.js @@ -1,12 +1,16 @@ import * as React from 'react'; import Grid from '@mui/material/Grid'; import { InfoCard } from '@mui/docs/InfoCard'; -import LineAxisRoundedIcon from '@mui/icons-material/LineAxisRounded'; +import ChatBubbleRoundedIcon from '@mui/icons-material/ChatBubble'; import DashboardCustomizeRoundedIcon from '@mui/icons-material/DashboardCustomizeRounded'; +import ExtensionRoundedIcon from '@mui/icons-material/Extension'; +import HighlightRoundedIcon from '@mui/icons-material/Highlight'; +import LabelImportantRoundedIcon from '@mui/icons-material/LabelImportant'; import LegendToggleRoundedIcon from '@mui/icons-material/LegendToggleRounded'; +import LineAxisRoundedIcon from '@mui/icons-material/LineAxisRounded'; import StackedBarChartRoundedIcon from '@mui/icons-material/StackedBarChartRounded'; import StyleRoundedIcon from '@mui/icons-material/StyleRounded'; -import TipsAndUpdatesRoundedIcon from '@mui/icons-material/TipsAndUpdatesRounded'; +import ZoomInRoundedIcon from '@mui/icons-material/ZoomIn'; const content = [ { @@ -19,6 +23,16 @@ const content = [ link: '/x/react-charts/components/', icon: , }, + { + title: 'Composition', + link: '/x/react-charts/composition/', + icon: , + }, + { + title: 'Label', + link: '/x/react-charts/label/', + icon: , + }, { title: 'Legend', link: '/x/react-charts/legend/', @@ -35,9 +49,19 @@ const content = [ icon: , }, { - title: 'Tooltips and highlights', + title: 'Tooltips', link: '/x/react-charts/tooltip/', - icon: , + icon: , + }, + { + title: 'Highlighting', + link: '/x/react-charts/highlighting/', + icon: , + }, + { + title: 'Zoom and pan', + link: '/x/react-charts/zoom-and-pan/', + icon: , }, ]; @@ -45,7 +69,7 @@ export default function ChartFeaturesGrid() { return ( {content.map(({ icon, title, link }) => ( - + ))} diff --git a/docs/src/modules/components/ChartsInstallationInstructions.js b/docs/src/modules/components/ChartsInstallationInstructions.js index b11fd2a21135..eeb2d1af8627 100644 --- a/docs/src/modules/components/ChartsInstallationInstructions.js +++ b/docs/src/modules/components/ChartsInstallationInstructions.js @@ -4,8 +4,8 @@ import InstallationInstructions from './InstallationInstructions'; // #default-branch-switch const packages = { - Community: '@mui/x-charts', - Pro: '@mui/x-charts-pro', + Community: '@mui/x-charts@next', + Pro: '@mui/x-charts-pro@next', }; export default function DataGridInstallationInstructions() { diff --git a/docs/src/modules/components/DataGridInstallationInstructions.js b/docs/src/modules/components/DataGridInstallationInstructions.js index fd41a7bddc3e..030004edd155 100644 --- a/docs/src/modules/components/DataGridInstallationInstructions.js +++ b/docs/src/modules/components/DataGridInstallationInstructions.js @@ -4,9 +4,9 @@ import InstallationInstructions from './InstallationInstructions'; // #default-branch-switch const packages = { - Community: '@mui/x-data-grid', - Pro: '@mui/x-data-grid-pro', - Premium: '@mui/x-data-grid-premium', + Community: '@mui/x-data-grid@next', + Pro: '@mui/x-data-grid-pro@next', + Premium: '@mui/x-data-grid-premium@next', }; export default function DataGridInstallationInstructions() { diff --git a/docs/src/modules/components/PickersInstallationInstructions.js b/docs/src/modules/components/PickersInstallationInstructions.js index 1282d02797a6..bac6c64d7c20 100644 --- a/docs/src/modules/components/PickersInstallationInstructions.js +++ b/docs/src/modules/components/PickersInstallationInstructions.js @@ -4,8 +4,8 @@ import InstallationInstructions from './InstallationInstructions'; // #default-branch-switch const packages = { - Community: '@mui/x-date-pickers', - Pro: '@mui/x-date-pickers-pro', + Community: '@mui/x-date-pickers@next', + Pro: '@mui/x-date-pickers-pro@next', }; const peerDependency = { diff --git a/docs/src/modules/components/TreeItem2Anatomy.js b/docs/src/modules/components/TreeItem2Anatomy.js deleted file mode 100644 index 2f46bb4dce94..000000000000 --- a/docs/src/modules/components/TreeItem2Anatomy.js +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from 'react'; -import { useTheme } from '@mui/material/styles'; -import Card from '@mui/material/Card'; -import CardMedia from '@mui/material/CardMedia'; - -export default function TreeItem2Anatomy() { - const { palette } = useTheme(); - - const src = - palette.mode === 'light' - ? '/static/x/tree-view-illustrations/tree-item-light.png' - : '/static/x/tree-view-illustrations/tree-item-dark.png'; - return ( - - - - ); -} diff --git a/docs/src/modules/components/TreeViewInstallationInstructions.js b/docs/src/modules/components/TreeViewInstallationInstructions.js index d05d2ebba846..fdec6c899509 100644 --- a/docs/src/modules/components/TreeViewInstallationInstructions.js +++ b/docs/src/modules/components/TreeViewInstallationInstructions.js @@ -4,8 +4,8 @@ import InstallationInstructions from './InstallationInstructions'; // #default-branch-switch const packages = { - Community: '@mui/x-tree-view', - Pro: '@mui/x-tree-view-pro', + Community: '@mui/x-tree-view@next', + Pro: '@mui/x-tree-view-pro@next', }; export default function TreeViewInstallationInstructions() { diff --git a/docs/src/modules/components/overview/Keyboard.tsx b/docs/src/modules/components/overview/Keyboard.tsx index 2d0a141601f0..1f4beb6a6746 100644 --- a/docs/src/modules/components/overview/Keyboard.tsx +++ b/docs/src/modules/components/overview/Keyboard.tsx @@ -487,7 +487,6 @@ export default function Keyboard() { onKeyUp={() => { setSelectedKey(null); }} - enableAccessibleFieldDOMStructure onSelectedSectionsChange={(newSelectedSection) => { selectedSection.current = newSelectedSection; }} diff --git a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx index 0dc82b364e0f..27b6857feed5 100644 --- a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx +++ b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx @@ -12,8 +12,8 @@ import { } from '@mui/x-date-pickers/models'; interface ButtonFieldProps - extends UseDateFieldProps, - BaseSingleInputFieldProps { + extends UseDateFieldProps, + BaseSingleInputFieldProps { setOpen?: React.Dispatch>; } 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 5e463ec7b112..3b9442aa58e3 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 @@ -1230,13 +1230,17 @@ } }, "slotDescriptions": { + "baseBadge": "The custom Badge component used in the grid for both header and cells.", "baseButton": "The custom Button component used in the grid.", "baseCheckbox": "The custom Checkbox component used in the grid for both header and cells.", "baseChip": "The custom Chip component used in the grid.", + "baseDivider": "The custom Divider component used in the grid.", "baseFormControl": "The custom FormControl component used in the grid.", "baseIconButton": "The custom IconButton component used in the grid.", "baseInputAdornment": "The custom InputAdornment component used in the grid.", "baseInputLabel": "The custom InputLabel component used in the grid.", + "baseMenuItem": "The custom MenuItem component used in the grid.", + "baseMenuList": "The custom MenuList component used in the grid.", "basePopper": "The custom Popper component used in the grid.", "baseSelect": "The custom Select component used in the grid.", "baseSelectOption": "The custom SelectOption component used in the grid.", 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 b2b762362095..c1d26982ac46 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 @@ -1168,13 +1168,17 @@ } }, "slotDescriptions": { + "baseBadge": "The custom Badge component used in the grid for both header and cells.", "baseButton": "The custom Button component used in the grid.", "baseCheckbox": "The custom Checkbox component used in the grid for both header and cells.", "baseChip": "The custom Chip component used in the grid.", + "baseDivider": "The custom Divider component used in the grid.", "baseFormControl": "The custom FormControl component used in the grid.", "baseIconButton": "The custom IconButton component used in the grid.", "baseInputAdornment": "The custom InputAdornment component used in the grid.", "baseInputLabel": "The custom InputLabel component used in the grid.", + "baseMenuItem": "The custom MenuItem component used in the grid.", + "baseMenuList": "The custom MenuList component used in the grid.", "basePopper": "The custom Popper component used in the grid.", "baseSelect": "The custom Select component used in the grid.", "baseSelectOption": "The custom SelectOption component used in the grid.", 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 9cf3ea258f2a..d082ef5d495d 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 @@ -1048,13 +1048,17 @@ } }, "slotDescriptions": { + "baseBadge": "The custom Badge component used in the grid for both header and cells.", "baseButton": "The custom Button component used in the grid.", "baseCheckbox": "The custom Checkbox component used in the grid for both header and cells.", "baseChip": "The custom Chip component used in the grid.", + "baseDivider": "The custom Divider component used in the grid.", "baseFormControl": "The custom FormControl component used in the grid.", "baseIconButton": "The custom IconButton component used in the grid.", "baseInputAdornment": "The custom InputAdornment component used in the grid.", "baseInputLabel": "The custom InputLabel component used in the grid.", + "baseMenuItem": "The custom MenuItem component used in the grid.", + "baseMenuList": "The custom MenuList component used in the grid.", "basePopper": "The custom Popper component used in the grid.", "baseSelect": "The custom Select component used in the grid.", "baseSelectOption": "The custom SelectOption component used in the grid.", 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 3a33b96c08bd..b43dd9784328 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 @@ -15,7 +15,7 @@ } }, "checkboxSelection": { - "description": "If true, the tree view renders a checkbox at the left of its label that allows selecting it." + "description": "If true, the Tree View renders a checkbox at the left of its label that allows selecting it." }, "classes": { "description": "Override or extend the styles applied to the component." }, "defaultExpandedItems": { @@ -75,21 +75,21 @@ "description": "If true, ctrl and shift will trigger multiselect." }, "onExpandedItemsChange": { - "description": "Callback fired when tree items are expanded/collapsed.", + "description": "Callback fired when Tree Items are expanded/collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the expanded items." } }, "onItemClick": { - "description": "Callback fired when the content slot of a given tree item is clicked.", + "description": "Callback fired when the content slot of a given Tree Item is clicked.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The id of the focused item." } }, "onItemExpansionToggle": { - "description": "Callback fired when a tree item is expanded or collapsed.", + "description": "Callback fired when a Tree Item is expanded or collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -97,7 +97,7 @@ } }, "onItemFocus": { - "description": "Callback fired when a given tree item is focused.", + "description": "Callback fired when a given Tree Item is focused.", "typeDescriptions": { "event": "The DOM event that triggered the change. Warning: This is a generic event not a focus event.", "itemId": "The id of the focused item." @@ -111,7 +111,7 @@ } }, "onItemPositionChange": { - "description": "Callback fired when a tree item is moved in the tree.", + "description": "Callback fired when a Tree Item is moved in the tree.", "typeDescriptions": { "params": "The params describing the item re-ordering.", "params.itemId": "The id of the item moved.", @@ -120,7 +120,7 @@ } }, "onItemSelectionToggle": { - "description": "Callback fired when a tree item is selected or deselected.", + "description": "Callback fired when a Tree Item is selected or deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -128,7 +128,7 @@ } }, "onSelectedItemsChange": { - "description": "Callback fired when tree items are selected/deselected.", + "description": "Callback fired when Tree Items are selected/deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the selected items. When multiSelect is true, this is an array of strings; when false (default) a string." @@ -137,6 +137,9 @@ "selectedItems": { "description": "Selected item ids. (Controlled) When multiSelect is true this takes an array of strings; when false (default) a string." }, + "selectionPropagation": { + "description": "When selectionPropagation.descendants is set to true.
- Selecting a parent selects all its descendants automatically. - Deselecting a parent deselects all its descendants automatically.
When selectionPropagation.parents is set to true.
- Selecting all the descendants of a parent selects the parent automatically. - Deselecting a descendant of a selected parent deselects the parent automatically.
Only works when multiSelect is true. On the <SimpleTreeView />, only the expanded items are considered (since the collapsed item are not passed to the Tree View component at all)" + }, "slotProps": { "description": "The props used for each component slot." }, "slots": { "description": "Overridable component slots." }, "sx": { @@ -146,7 +149,7 @@ "classDescriptions": {}, "slotDescriptions": { "collapseIcon": "The default icon used to collapse the item.", - "endIcon": "The default icon displayed next to an end item. This is applied to all tree items and can be overridden by the TreeItem icon slot prop.", + "endIcon": "The default icon displayed next to an end item. This is applied to all Tree Items and can be overridden by the TreeItem icon slot prop.", "expandIcon": "The default icon used to expand the item.", "item": "Custom component for the item.", "root": "Element rendered at the root." 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 3ba5993e5187..a2fdbd15c255 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 @@ -5,7 +5,7 @@ "description": "The ref object that allows Tree View manipulation. Can be instantiated with useTreeViewApiRef()." }, "checkboxSelection": { - "description": "If true, the tree view renders a checkbox at the left of its label that allows selecting it." + "description": "If true, the Tree View renders a checkbox at the left of its label that allows selecting it." }, "classes": { "description": "Override or extend the styles applied to the component." }, "defaultExpandedItems": { @@ -55,21 +55,21 @@ "description": "If true, ctrl and shift will trigger multiselect." }, "onExpandedItemsChange": { - "description": "Callback fired when tree items are expanded/collapsed.", + "description": "Callback fired when Tree Items are expanded/collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the expanded items." } }, "onItemClick": { - "description": "Callback fired when the content slot of a given tree item is clicked.", + "description": "Callback fired when the content slot of a given Tree Item is clicked.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The id of the focused item." } }, "onItemExpansionToggle": { - "description": "Callback fired when a tree item is expanded or collapsed.", + "description": "Callback fired when a Tree Item is expanded or collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -77,7 +77,7 @@ } }, "onItemFocus": { - "description": "Callback fired when a given tree item is focused.", + "description": "Callback fired when a given Tree Item is focused.", "typeDescriptions": { "event": "The DOM event that triggered the change. Warning: This is a generic event not a focus event.", "itemId": "The id of the focused item." @@ -91,7 +91,7 @@ } }, "onItemSelectionToggle": { - "description": "Callback fired when a tree item is selected or deselected.", + "description": "Callback fired when a Tree Item is selected or deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -99,7 +99,7 @@ } }, "onSelectedItemsChange": { - "description": "Callback fired when tree items are selected/deselected.", + "description": "Callback fired when Tree Items are selected/deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the selected items. When multiSelect is true, this is an array of strings; when false (default) a string." @@ -108,6 +108,9 @@ "selectedItems": { "description": "Selected item ids. (Controlled) When multiSelect is true this takes an array of strings; when false (default) a string." }, + "selectionPropagation": { + "description": "When selectionPropagation.descendants is set to true.
- Selecting a parent selects all its descendants automatically. - Deselecting a parent deselects all its descendants automatically.
When selectionPropagation.parents is set to true.
- Selecting all the descendants of a parent selects the parent automatically. - Deselecting a descendant of a selected parent deselects the parent automatically.
Only works when multiSelect is true. On the <SimpleTreeView />, only the expanded items are considered (since the collapsed item are not passed to the Tree View component at all)" + }, "slotProps": { "description": "The props used for each component slot." }, "slots": { "description": "Overridable component slots." }, "sx": { @@ -117,7 +120,7 @@ "classDescriptions": {}, "slotDescriptions": { "collapseIcon": "The default icon used to collapse the item.", - "endIcon": "The default icon displayed next to an end item. This is applied to all tree items and can be overridden by the TreeItem icon slot prop.", + "endIcon": "The default icon displayed next to an end item. This is applied to all Tree Items and can be overridden by the TreeItem icon slot prop.", "expandIcon": "The default icon used to expand the item.", "item": "Custom component for the item.", "root": "Element rendered at the root." diff --git a/docs/translations/api-docs/tree-view/simple-tree-view/simple-tree-view.json b/docs/translations/api-docs/tree-view/simple-tree-view/simple-tree-view.json index a56fd8d2d75a..86afc50c65db 100644 --- a/docs/translations/api-docs/tree-view/simple-tree-view/simple-tree-view.json +++ b/docs/translations/api-docs/tree-view/simple-tree-view/simple-tree-view.json @@ -5,7 +5,7 @@ "description": "The ref object that allows Tree View manipulation. Can be instantiated with useTreeViewApiRef()." }, "checkboxSelection": { - "description": "If true, the tree view renders a checkbox at the left of its label that allows selecting it." + "description": "If true, the Tree View renders a checkbox at the left of its label that allows selecting it." }, "children": { "description": "The content of the component." }, "classes": { "description": "Override or extend the styles applied to the component." }, @@ -38,21 +38,21 @@ "description": "If true, ctrl and shift will trigger multiselect." }, "onExpandedItemsChange": { - "description": "Callback fired when tree items are expanded/collapsed.", + "description": "Callback fired when Tree Items are expanded/collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the expanded items." } }, "onItemClick": { - "description": "Callback fired when the content slot of a given tree item is clicked.", + "description": "Callback fired when the content slot of a given Tree Item is clicked.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The id of the focused item." } }, "onItemExpansionToggle": { - "description": "Callback fired when a tree item is expanded or collapsed.", + "description": "Callback fired when a Tree Item is expanded or collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -60,14 +60,14 @@ } }, "onItemFocus": { - "description": "Callback fired when a given tree item is focused.", + "description": "Callback fired when a given Tree Item is focused.", "typeDescriptions": { "event": "The DOM event that triggered the change. Warning: This is a generic event not a focus event.", "itemId": "The id of the focused item." } }, "onItemSelectionToggle": { - "description": "Callback fired when a tree item is selected or deselected.", + "description": "Callback fired when a Tree Item is selected or deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -75,7 +75,7 @@ } }, "onSelectedItemsChange": { - "description": "Callback fired when tree items are selected/deselected.", + "description": "Callback fired when Tree Items are selected/deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the selected items. When multiSelect is true, this is an array of strings; when false (default) a string." @@ -84,6 +84,9 @@ "selectedItems": { "description": "Selected item ids. (Controlled) When multiSelect is true this takes an array of strings; when false (default) a string." }, + "selectionPropagation": { + "description": "When selectionPropagation.descendants is set to true.
- Selecting a parent selects all its descendants automatically. - Deselecting a parent deselects all its descendants automatically.
When selectionPropagation.parents is set to true.
- Selecting all the descendants of a parent selects the parent automatically. - Deselecting a descendant of a selected parent deselects the parent automatically.
Only works when multiSelect is true. On the <SimpleTreeView />, only the expanded items are considered (since the collapsed item are not passed to the Tree View component at all)" + }, "slotProps": { "description": "The props used for each component slot." }, "slots": { "description": "Overridable component slots." }, "sx": { @@ -93,7 +96,7 @@ "classDescriptions": {}, "slotDescriptions": { "collapseIcon": "The default icon used to collapse the item.", - "endIcon": "The default icon displayed next to an end item. This is applied to all tree items and can be overridden by the TreeItem icon slot prop.", + "endIcon": "The default icon displayed next to an end item. This is applied to all Tree Items and can be overridden by the TreeItem icon slot prop.", "expandIcon": "The default icon used to expand the item.", "root": "Element rendered at the root." } 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 deleted file mode 100644 index 1ec2e89e98d8..000000000000 --- a/docs/translations/api-docs/tree-view/tree-item-2/tree-item-2.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "componentDescription": "", - "propDescriptions": { - "children": { "description": "The content of the component." }, - "classes": { "description": "Override or extend the styles applied to the component." }, - "disabled": { "description": "If true, the item is disabled." }, - "id": { "description": "The id attribute of the item. If not provided, it will be generated." }, - "itemId": { "description": "The id of the item. Must be unique." }, - "label": { "description": "The label of the item." }, - "onBlur": { "description": "Callback fired when the item root is blurred." }, - "onFocus": { - "description": "This prop isn't supported. Use the onItemFocus callback on the tree if you need to monitor an item's focus." - }, - "onKeyDown": { - "description": "Callback fired when a key is pressed on the keyboard and the tree is in focus." - }, - "slotProps": { "description": "The props used for each component slot." }, - "slots": { "description": "Overridable component slots." } - }, - "classDescriptions": { - "disabled": { - "description": "State class applied to {{nodeName}} when {{conditions}}.", - "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", - "conditions": "expanded" - }, - "focused": { - "description": "State class applied to {{nodeName}} when {{conditions}}.", - "nodeName": "the content element", - "conditions": "focused" - }, - "selected": { - "description": "State class applied to {{nodeName}} when {{conditions}}.", - "nodeName": "the content element", - "conditions": "selected" - } - }, - "slotDescriptions": { - "checkbox": "The component that renders the item checkbox for selection.", - "collapseIcon": "The icon used to collapse the item.", - "content": "The component that renders the content of the item. (e.g.: everything related to this item, not to its children).", - "dragAndDropOverlay": "The component that renders the overlay when an item reordering is ongoing. Warning: This slot is only useful when using the RichTreeViewPro component.", - "endIcon": "The icon displayed next to an end item.", - "expandIcon": "The icon used to expand the item.", - "groupTransition": "The component that renders the children of the item.", - "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-drag-and-drop-overlay/tree-item-drag-and-drop-overlay.json b/docs/translations/api-docs/tree-view/tree-item-drag-and-drop-overlay/tree-item-drag-and-drop-overlay.json new file mode 100644 index 000000000000..f93d4cbd8c79 --- /dev/null +++ b/docs/translations/api-docs/tree-view/tree-item-drag-and-drop-overlay/tree-item-drag-and-drop-overlay.json @@ -0,0 +1 @@ +{ "componentDescription": "", "propDescriptions": {}, "classDescriptions": {} } diff --git a/docs/translations/api-docs/tree-view/tree-item-icon/tree-item-icon.json b/docs/translations/api-docs/tree-view/tree-item-icon/tree-item-icon.json new file mode 100644 index 000000000000..c39a07ae223f --- /dev/null +++ b/docs/translations/api-docs/tree-view/tree-item-icon/tree-item-icon.json @@ -0,0 +1,14 @@ +{ + "componentDescription": "", + "propDescriptions": { + "slotProps": { "description": "The props used for each component slot." }, + "slots": { "description": "Overridable component slots." } + }, + "classDescriptions": {}, + "slotDescriptions": { + "collapseIcon": "The icon used to collapse the item.", + "endIcon": "The icon displayed next to an end item.", + "expandIcon": "The icon used to expand the item.", + "icon": "The icon to display next to the Tree Item's label." + } +} 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 f0febee14167..5177a55030aa 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 @@ -3,44 +3,26 @@ "propDescriptions": { "children": { "description": "The content of the component." }, "classes": { "description": "Override or extend the styles applied to the component." }, - "ContentComponent": { - "description": "The component used to render the content of the item.", - "requiresRef": true - }, - "ContentProps": { "description": "Props applied to ContentComponent." }, "disabled": { "description": "If true, the item is disabled." }, - "itemId": { "description": "The id of the item." }, - "label": { "description": "The tree item label." }, + "id": { "description": "The id attribute of the item. If not provided, it will be generated." }, + "itemId": { "description": "The id of the item. Must be unique." }, + "label": { "description": "The label of the item." }, + "onBlur": { "description": "Callback fired when the item root is blurred." }, "onFocus": { - "description": "This prop isn't supported. Use the onItemFocus callback on the tree if you need to monitor a item's focus." + "description": "This prop isn't supported. Use the onItemFocus callback on the tree if you need to monitor an item's focus." }, "onKeyDown": { - "description": "Callback fired when a key of the keyboard is pressed on the item." + "description": "Callback fired when a key is pressed on the keyboard and the tree is in focus." }, "slotProps": { "description": "The props used for each component slot." }, - "slots": { "description": "Overridable component slots." }, - "sx": { - "description": "The system prop that allows defining system overrides as well as additional CSS styles." - } + "slots": { "description": "Overridable component slots." } }, "classDescriptions": { - "checkbox": { - "description": "Styles applied to {{nodeName}}.", - "nodeName": "the checkbox element" - }, - "content": { - "description": "Styles applied to {{nodeName}}.", - "nodeName": "the content element" - }, "disabled": { "description": "State class applied to {{nodeName}} when {{conditions}}.", "nodeName": "the element", "conditions": "disabled" }, - "dragAndDropOverlay": { - "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" @@ -60,17 +42,6 @@ "nodeName": "the content element", "conditions": "focused" }, - "iconContainer": { - "description": "Styles applied to {{nodeName}}.", - "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}}.", "nodeName": "the content element", @@ -78,10 +49,17 @@ } }, "slotDescriptions": { + "checkbox": "The component that renders the item checkbox for selection.", "collapseIcon": "The icon used to collapse the item.", + "content": "The component that renders the content of the item. (e.g.: everything related to this item, not to its children).", + "dragAndDropOverlay": "The component that renders the overlay when an item reordering is ongoing. Warning: This slot is only useful when using the <RichTreeViewPro /> component.", "endIcon": "The icon displayed next to an end item.", "expandIcon": "The icon used to expand the item.", - "groupTransition": "The component that animates the appearance / disappearance of the item's children.", - "icon": "The icon to display next to the tree item's label." + "groupTransition": "The component that renders the children of the item.", + "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-view/tree-view.json b/docs/translations/api-docs/tree-view/tree-view/tree-view.json index 765c53c0efd9..7558c542dc41 100644 --- a/docs/translations/api-docs/tree-view/tree-view/tree-view.json +++ b/docs/translations/api-docs/tree-view/tree-view/tree-view.json @@ -1,11 +1,11 @@ { - "componentDescription": "This component has been deprecated in favor of the new `SimpleTreeView` component.\nYou can have a look at how to migrate to the new component in the v7 [migration guide](https://mui.com/x/migration/migration-tree-view-v6/#use-simpletreeview-instead-of-treeview)", + "componentDescription": "This component has been deprecated in favor of the new Simple Tree View component.\nYou can have a look at how to migrate to the new component in the v7 [migration guide](https://mui.com/x/migration/migration-tree-view-v6/#use-simpletreeview-instead-of-treeview)", "propDescriptions": { "apiRef": { "description": "The ref object that allows Tree View manipulation. Can be instantiated with useTreeViewApiRef()." }, "checkboxSelection": { - "description": "If true, the tree view renders a checkbox at the left of its label that allows selecting it." + "description": "If true, the Tree View renders a checkbox at the left of its label that allows selecting it." }, "children": { "description": "The content of the component." }, "classes": { "description": "Override or extend the styles applied to the component." }, @@ -38,21 +38,21 @@ "description": "If true, ctrl and shift will trigger multiselect." }, "onExpandedItemsChange": { - "description": "Callback fired when tree items are expanded/collapsed.", + "description": "Callback fired when Tree Items are expanded/collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the expanded items." } }, "onItemClick": { - "description": "Callback fired when the content slot of a given tree item is clicked.", + "description": "Callback fired when the content slot of a given Tree Item is clicked.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The id of the focused item." } }, "onItemExpansionToggle": { - "description": "Callback fired when a tree item is expanded or collapsed.", + "description": "Callback fired when a Tree Item is expanded or collapsed.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -60,14 +60,14 @@ } }, "onItemFocus": { - "description": "Callback fired when a given tree item is focused.", + "description": "Callback fired when a given Tree Item is focused.", "typeDescriptions": { "event": "The DOM event that triggered the change. Warning: This is a generic event not a focus event.", "itemId": "The id of the focused item." } }, "onItemSelectionToggle": { - "description": "Callback fired when a tree item is selected or deselected.", + "description": "Callback fired when a Tree Item is selected or deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemId": "The itemId of the modified item.", @@ -75,7 +75,7 @@ } }, "onSelectedItemsChange": { - "description": "Callback fired when tree items are selected/deselected.", + "description": "Callback fired when Tree Items are selected/deselected.", "typeDescriptions": { "event": "The DOM event that triggered the change.", "itemIds": "The ids of the selected items. When multiSelect is true, this is an array of strings; when false (default) a string." @@ -84,6 +84,9 @@ "selectedItems": { "description": "Selected item ids. (Controlled) When multiSelect is true this takes an array of strings; when false (default) a string." }, + "selectionPropagation": { + "description": "When selectionPropagation.descendants is set to true.
- Selecting a parent selects all its descendants automatically. - Deselecting a parent deselects all its descendants automatically.
When selectionPropagation.parents is set to true.
- Selecting all the descendants of a parent selects the parent automatically. - Deselecting a descendant of a selected parent deselects the parent automatically.
Only works when multiSelect is true. On the <SimpleTreeView />, only the expanded items are considered (since the collapsed item are not passed to the Tree View component at all)" + }, "slotProps": { "description": "The props used for each component slot." }, "slots": { "description": "Overridable component slots." }, "sx": { @@ -93,7 +96,7 @@ "classDescriptions": {}, "slotDescriptions": { "collapseIcon": "The default icon used to collapse the item.", - "endIcon": "The default icon displayed next to an end item. This is applied to all tree items and can be overridden by the TreeItem icon slot prop.", + "endIcon": "The default icon displayed next to an end item. This is applied to all Tree Items and can be overridden by the TreeItem icon slot prop.", "expandIcon": "The default icon used to expand the item.", "root": "Element rendered at the root." } diff --git a/package.json b/package.json index 741584687456..b73f676852b2 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "7.20.0", + "version": "7.21.0", "private": true, "scripts": { "preinstall": "npx only-allow pnpm", @@ -62,8 +62,8 @@ "release:changelog": "node scripts/releaseChangelog.mjs", "release:version": "lerna version --exact --no-changelog --no-push --no-git-tag-version --no-private", "release:build": "lerna run --parallel --no-private --scope \"@mui/*\" build", - "release:publish": "pnpm publish --recursive --tag latest", - "release:publish:dry-run": "pnpm publish --recursive --tag latest --registry=\"http://localhost:4873/\"", + "release:publish": "pnpm publish --recursive --tag next", + "release:publish:dry-run": "pnpm publish --recursive --tag next --registry=\"http://localhost:4873/\"", "release:tag": "node scripts/releaseTag.mjs", "validate": "concurrently \"pnpm prettier && pnpm eslint\" \"pnpm proptypes\" \"pnpm docs:typescript:formatted\" \"pnpm docs:api\"", "clean:node_modules": "rimraf --glob \"**/node_modules\"" @@ -95,7 +95,7 @@ "@mui/internal-markdown": "^1.0.17", "@mui/internal-test-utils": "^1.0.17", "@mui/material": "^5.16.7", - "@mui/monorepo": "github:mui/material-ui#010de4505361345951824d905d1508d6f258ba67", + "@mui/monorepo": "github:mui/material-ui#ca637077e091b7f1f6e591ded5a453edcc784204", "@mui/utils": "^5.16.6", "@next/eslint-plugin-next": "14.2.15", "@octokit/plugin-retry": "^7.1.2", @@ -107,9 +107,9 @@ "@types/chai-dom": "^1.11.3", "@types/fs-extra": "^11.0.4", "@types/karma": "^6.3.8", - "@types/lodash": "^4.17.10", + "@types/lodash": "^4.17.12", "@types/mocha": "^10.0.9", - "@types/node": "^20.16.11", + "@types/node": "^20.16.14", "@types/react": "^18.3.4", "@types/react-dom": "^18.3.0", "@types/react-test-renderer": "^18.3.0", @@ -119,7 +119,7 @@ "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "autoprefixer": "^10.4.20", - "axe-core": "4.10.0", + "axe-core": "4.10.1", "babel-loader": "^9.2.1", "babel-plugin-istanbul": "^7.0.0", "babel-plugin-module-resolver": "^5.0.2", @@ -144,21 +144,21 @@ "eslint-import-resolver-webpack": "^0.13.9", "eslint-plugin-filenames": "^1.3.2", "eslint-plugin-import": "^2.31.0", - "eslint-plugin-jsdoc": "^50.4.1", - "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-jsdoc": "^50.4.3", + "eslint-plugin-jsx-a11y": "^6.10.1", "eslint-plugin-material-ui": "workspace:^", "eslint-plugin-mocha": "^10.5.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.37.1", "eslint-plugin-react-compiler": "0.0.0-experimental-9ed098e-20240725", "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-testing-library": "^6.3.0", + "eslint-plugin-testing-library": "^6.4.0", "fast-glob": "^3.3.2", "format-util": "^1.0.5", "fs-extra": "^11.2.0", "glob-gitignore": "^1.0.15", "globby": "^14.0.2", - "html-webpack-plugin": "^5.6.0", + "html-webpack-plugin": "^5.6.2", "jsdom": "24.1.3", "jss": "^10.10.0", "jss-plugin-template": "^10.10.0", @@ -183,7 +183,7 @@ "react-dom": "^18.3.1", "remark": "^15.0.1", "rimraf": "^6.0.1", - "serve": "^14.2.3", + "serve": "^14.2.4", "sinon": "^18.0.1", "stream-browserify": "^3.0.0", "string-replace-loader": "^3.1.0", @@ -199,11 +199,11 @@ }, "resolutions": { "react-is": "^18.3.1", - "@types/node": "^20.16.11" + "@types/node": "^20.16.14" }, - "packageManager": "pnpm@9.12.1", + "packageManager": "pnpm@9.12.2", "engines": { - "pnpm": "9.12.1" + "pnpm": "9.12.2" }, "pnpm": { "patchedDependencies": { diff --git a/packages/rsc-builder/package.json b/packages/rsc-builder/package.json index 92d10aa52ebb..d9e6e09e97b9 100644 --- a/packages/rsc-builder/package.json +++ b/packages/rsc-builder/package.json @@ -9,6 +9,6 @@ }, "devDependencies": { "@types/mocha": "^10.0.9", - "@types/node": "^20.16.11" + "@types/node": "^20.16.14" } } diff --git a/packages/x-charts-pro/package.json b/packages/x-charts-pro/package.json index 946258e4fc7c..628ad50ae463 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-beta.4", + "version": "7.0.0-beta.5", "description": "The Pro plan edition of the Charts components (MUI X).", "author": "MUI Team", "main": "src/index.ts", diff --git a/packages/x-charts-vendor/package.json b/packages/x-charts-vendor/package.json index 79dd902e3dd1..cf9975eb676d 100644 --- a/packages/x-charts-vendor/package.json +++ b/packages/x-charts-vendor/package.json @@ -50,7 +50,7 @@ "d3-format": "^3.1.0", "d3-path": "^3.1.0", "d3-time-format": "^4.1.0", - "execa": "^9.4.0", + "execa": "^9.4.1", "internmap": "^2.0.3", "rimraf": "^6.0.1" }, diff --git a/packages/x-charts/package.json b/packages/x-charts/package.json index 181b81b28723..02d47aedb6be 100644 --- a/packages/x-charts/package.json +++ b/packages/x-charts/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-charts", - "version": "7.20.0", + "version": "7.21.0", "description": "The community edition of the Charts components (MUI X).", "author": "MUI Team", "main": "src/index.js", diff --git a/packages/x-charts/src/ChartsLegend/DefaultChartsLegend.tsx b/packages/x-charts/src/ChartsLegend/DefaultChartsLegend.tsx index 7278a105536f..24d575ca0095 100644 --- a/packages/x-charts/src/ChartsLegend/DefaultChartsLegend.tsx +++ b/packages/x-charts/src/ChartsLegend/DefaultChartsLegend.tsx @@ -20,7 +20,7 @@ export interface LegendRendererProps series: FormattedSeries; seriesToDisplay: LegendPerItemProps['itemsToDisplay']; /** - * @deprecated Use the `useDrawingArea` hook instead. + * @deprecated Use the `useDrawingArea()` hook instead. */ drawingArea: Omit; /** @@ -76,7 +76,7 @@ DefaultChartsLegend.propTypes = { */ direction: PropTypes.oneOf(['column', 'row']).isRequired, /** - * @deprecated Use the `useDrawingArea` hook instead. + * @deprecated Use the `useDrawingArea()` hook instead. */ drawingArea: PropTypes.shape({ bottom: PropTypes.number.isRequired, diff --git a/packages/x-charts/src/ChartsOnAxisClickHandler/ChartsOnAxisClickHandler.tsx b/packages/x-charts/src/ChartsOnAxisClickHandler/ChartsOnAxisClickHandler.tsx index 326f12ef6f4b..c4da5f03a666 100644 --- a/packages/x-charts/src/ChartsOnAxisClickHandler/ChartsOnAxisClickHandler.tsx +++ b/packages/x-charts/src/ChartsOnAxisClickHandler/ChartsOnAxisClickHandler.tsx @@ -6,7 +6,7 @@ import { useSeries } from '../hooks/useSeries'; import { useSvgRef } from '../hooks'; import { useCartesianContext } from '../context/CartesianProvider'; -type AxisData = { +export type ChartsAxisData = { dataIndex: number; axisValue?: number | Date | string; seriesValues: Record; @@ -19,7 +19,7 @@ export interface ChartsOnAxisClickHandlerProps { * @param {MouseEvent} event The mouse event recorded on the `` element. * @param {null | AxisData} data The data about the clicked axis and items associated with it. */ - onAxisClick?: (event: MouseEvent, data: null | AxisData) => void; + onAxisClick?: (event: MouseEvent, data: null | ChartsAxisData) => void; } function ChartsOnAxisClickHandler(props: ChartsOnAxisClickHandlerProps) { diff --git a/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx b/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx index 4566eddb96d6..51c770bbb827 100644 --- a/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx +++ b/packages/x-charts/src/ChartsTooltip/ChartsTooltip.tsx @@ -11,11 +11,13 @@ import { InteractionContext, ItemInteractionData, } from '../context/InteractionProvider'; +import { useSvgRef } from '../hooks/useSvgRef'; import { generateVirtualElement, - useMouseTracker, getTooltipHasData, TriggerOptions, + usePointerType, + VirtualElement, } from './utils'; import { ChartSeriesType } from '../models/seriesType/config'; import { ChartsItemContentProps, ChartsItemTooltipContent } from './ChartsItemTooltipContent'; @@ -133,14 +135,22 @@ function ChartsTooltip(inProps: ChartsTooltipProps }); const { trigger = 'axis', itemContent, axisContent, slots, slotProps } = props; - const mousePosition = useMouseTracker(); + const svgRef = useSvgRef(); + const pointerType = usePointerType(); + + const popperRef: PopperProps['popperRef'] = React.useRef(null); + + const virtualElement = React.useRef(null); + if (virtualElement.current === null) { + virtualElement.current = generateVirtualElement(null); + } const { item, axis } = React.useContext(InteractionContext); const displayedData = trigger === 'item' ? item : axis; const tooltipHasData = getTooltipHasData(trigger, displayedData); - const popperOpen = mousePosition !== null && tooltipHasData; + const popperOpen = pointerType !== null && tooltipHasData; const classes = useUtilityClasses({ classes: props.classes }); @@ -150,14 +160,14 @@ function ChartsTooltip(inProps: ChartsTooltipProps externalSlotProps: slotProps?.popper, additionalProps: { open: popperOpen, - placement: - mousePosition?.pointerType === 'mouse' ? ('right-start' as const) : ('top' as const), - anchorEl: generateVirtualElement(mousePosition), + placement: pointerType?.pointerType === 'mouse' ? ('right-start' as const) : ('top' as const), + popperRef, + anchorEl: virtualElement.current, modifiers: [ { name: 'offset', options: { - offset: [0, mousePosition?.pointerType === 'touch' ? 40 - mousePosition.height : 0], + offset: [0, pointerType?.pointerType === 'touch' ? 40 - pointerType.height : 0], }, }, ], @@ -165,6 +175,26 @@ function ChartsTooltip(inProps: ChartsTooltipProps ownerState: {}, }); + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleMove = (event: PointerEvent) => { + virtualElement.current = generateVirtualElement({ + x: event.clientX, + y: event.clientY, + }); + popperRef.current?.update(); + }; + element.addEventListener('pointermove', handleMove); + + return () => { + element.removeEventListener('pointermove', handleMove); + }; + }, [svgRef]); + if (trigger === 'none') { return null; } diff --git a/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx b/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx index 6b4634241e3d..bde48ebc9f35 100644 --- a/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx +++ b/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx @@ -54,6 +54,7 @@ describe('ChartsTooltip', () => { ); const svg = document.querySelector('svg')!; + fireEvent.pointerEnter(svg); // Trigger the tooltip firePointerEvent(svg, 'pointermove', { clientX: 198, clientY: 60, @@ -120,6 +121,7 @@ describe('ChartsTooltip', () => { ); const svg = document.querySelector('svg')!; + fireEvent.pointerEnter(svg); // Trigger the tooltip firePointerEvent(svg, 'pointermove', { clientX: 150, clientY: 60, @@ -191,6 +193,7 @@ describe('ChartsTooltip', () => { fireEvent.pointerEnter(rectangles[0]); + fireEvent.pointerEnter(svg); // Trigger the tooltip firePointerEvent(svg, 'pointermove', { clientX: 150, clientY: 60, @@ -235,6 +238,7 @@ describe('ChartsTooltip', () => { fireEvent.pointerEnter(rectangles[0]); + fireEvent.pointerEnter(svg); // Trigger the tooltip firePointerEvent(svg, 'pointermove', { clientX: 150, clientY: 60, diff --git a/packages/x-charts/src/ChartsTooltip/utils.tsx b/packages/x-charts/src/ChartsTooltip/utils.tsx index 89faff2a7b17..24e2e2220391 100644 --- a/packages/x-charts/src/ChartsTooltip/utils.tsx +++ b/packages/x-charts/src/ChartsTooltip/utils.tsx @@ -10,8 +10,28 @@ type MousePosition = { height: number; }; -export function generateVirtualElement(mousePosition: MousePosition | null) { - if (mousePosition === null) { +export type VirtualElement = { + getBoundingClientRect: () => { + width: number; + height: number; + x: number; + y: number; + top: number; + right: number; + bottom: number; + left: number; + toJSON: () => string; + }; +}; +/** + * Generate a virtual element for the tooltip. + * Default to (0, 0) is the argument is not provided, or null. + * @param mousePosition { x: number, y: number} + */ +export function generateVirtualElement( + mousePosition?: Pick | null, +): VirtualElement { + if (!mousePosition) { return { getBoundingClientRect: () => ({ width: 0, @@ -47,6 +67,9 @@ export function generateVirtualElement(mousePosition: MousePosition | null) { export type UseMouseTrackerReturnValue = null | MousePosition; +/** + * @deprecated We recommend using vanilla JS to let popper track mouse position. + */ export function useMouseTracker(): UseMouseTrackerReturnValue { const svgRef = useSvgRef(); @@ -59,6 +82,8 @@ export function useMouseTracker(): UseMouseTrackerReturnValue { return () => {}; } + const controller = new AbortController(); + const handleOut = (event: PointerEvent) => { if (event.pointerType !== 'mouse') { setMousePosition(null); @@ -74,18 +99,57 @@ export function useMouseTracker(): UseMouseTrackerReturnValue { }); }; - element.addEventListener('pointerdown', handleMove); - element.addEventListener('pointermove', handleMove); + element.addEventListener('pointerdown', handleMove, { signal: controller.signal }); + element.addEventListener('pointermove', handleMove, { signal: controller.signal }); + element.addEventListener('pointerup', handleOut, { signal: controller.signal }); + + return () => { + // Calling `.abort()` removes ALL event listeners + // For more info, see https://kettanaito.com/blog/dont-sleep-on-abort-controller + controller.abort(); + }; + }, [svgRef]); + + return mousePosition; +} + +type PointerType = Pick; + +export function usePointerType(): null | PointerType { + const svgRef = useSvgRef(); + + // Use a ref to avoid rerendering on every mousemove event. + const [pointerType, setPointerType] = React.useState(null); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + + const handleOut = (event: PointerEvent) => { + if (event.pointerType !== 'mouse') { + setPointerType(null); + } + }; + + const handleEnter = (event: PointerEvent) => { + setPointerType({ + height: event.height, + pointerType: event.pointerType as PointerType['pointerType'], + }); + }; + + element.addEventListener('pointerenter', handleEnter); element.addEventListener('pointerup', handleOut); return () => { - element.removeEventListener('pointerdown', handleMove); - element.removeEventListener('pointermove', handleMove); + element.removeEventListener('pointerenter', handleEnter); element.removeEventListener('pointerup', handleOut); }; }, [svgRef]); - return mousePosition; + return pointerType; } export type TriggerOptions = 'item' | 'axis' | 'none'; diff --git a/packages/x-codemod/README.md b/packages/x-codemod/README.md index 453f783b567d..af1290cabb81 100644 --- a/packages/x-codemod/README.md +++ b/packages/x-codemod/README.md @@ -13,7 +13,7 @@ This repository contains a collection of codemod scripts based for use with ```bash -npx @mui/x-codemod@latest +npx @mui/x-codemod@next Applies a `@mui/x-codemod` to the specified paths @@ -29,8 +29,8 @@ Options: --jscodeshift Pass options directly to jscodeshift [array] Examples: - npx @mui/x-codemod@latest v7.0.0/preset-safe src - npx @mui/x-codemod@latest v6.0.0/component-rename-prop src -- + npx @mui/x-codemod@next v7.0.0/preset-safe src + npx @mui/x-codemod@next v6.0.0/component-rename-prop src -- --component=DataGrid --from=prop --to=newProp ``` @@ -40,9 +40,9 @@ To pass more options directly to jscodeshift, use `--jscodeshift=...`. For examp ```bash // single option -npx @mui/x-codemod@latest --jscodeshift=--run-in-band +npx @mui/x-codemod@next --jscodeshift=--run-in-band // multiple options -npx @mui/x-codemod@latest --jscodeshift=--cpus=1 --jscodeshift=--print --jscodeshift=--dry --jscodeshift=--verbose=2 +npx @mui/x-codemod@next --jscodeshift=--cpus=1 --jscodeshift=--print --jscodeshift=--dry --jscodeshift=--verbose=2 ``` See all available options [here](https://github.com/facebook/jscodeshift#usage-cli). @@ -53,7 +53,50 @@ Options to [recast](https://github.com/benjamn/recast)'s printer can be provided through jscodeshift's `printOptions` command line argument ```bash -npx @mui/x-codemod@latest --jscodeshift="--printOptions='{\"quote\":\"double\"}'" +npx @mui/x-codemod@next --jscodeshift="--printOptions='{\"quote\":\"double\"}'" +``` + +## v8.0.0 + +### 🚀 `preset-safe` for v8.0.0 + +A combination of all important transformers for migrating v7 to v8. +⚠️ This codemod should be run only once. +It runs codemods for both Data Grid and Date and Time Pickers packages. +To run codemods for a specific package, refer to the respective section. + +```bash +npx @mui/x-codemod@latest v8.0.0/preset-safe +``` + +The corresponding sub-sections are listed below + +- [`preset-safe-for-tree-view`](#preset-safe-for-tree-view-v800) + +### Tree View codemods + +#### `preset-safe` for tree view v8.0.0 + +The `preset-safe` codemods for tree view. + +```bash +npx @mui/x-codemod@latest v8.0.0/tree-view/preset-safe +``` + +The list includes these transformers + +- [`rename-tree-item-2`](#rename-tree-item-2) + +#### `rename-tree-item-2` + +Renames the `TreeItem2` component to `TreeItem` (same for any subcomponents or utils like `useTreeItem2` or `TreeItem2Icon`). + +```diff +-import { TreeItem2 } from '@mui/x-tree-view'; ++import { TreeItem } from '@mui/x-tree-view'; + +-import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; ++import { TreeItem } from '@mui/x-tree-view/TreeItem'; ``` ## v7.0.0 @@ -227,9 +270,9 @@ npx @mui/x-codemod@latest v7.0.0/data-grid/remove-stabilized-experimentalFeature ### Tree View codemods -#### `preset-safe` for tree view v7.0.0 +#### `preset-safe` for Tree View v7.0.0 -The `preset-safe` codemods for tree view. +The `preset-safe` codemods for Tree View. ```bash npx @mui/x-codemod@latest v7.0.0/tree-view/preset-safe @@ -247,7 +290,7 @@ The list includes these transformers #### `rename-tree-view-simple-tree-view` -Renames the `TreeView` component to `SimpleTreeView` +Renames the Tree View component to Simple Tree View ```diff -import { TreeView } from '@mui/x-tree-view'; diff --git a/packages/x-codemod/package.json b/packages/x-codemod/package.json index 4f9a0b5687a4..c9a7f1934e1d 100644 --- a/packages/x-codemod/package.json +++ b/packages/x-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-codemod", - "version": "7.20.0", + "version": "7.21.0", "bin": "./codemod.js", "private": false, "author": "MUI Team", diff --git a/packages/x-codemod/src/v8.0.0/preset-safe/actual.spec.js b/packages/x-codemod/src/v8.0.0/preset-safe/actual.spec.js new file mode 100644 index 000000000000..b7a570e98bff --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/preset-safe/actual.spec.js @@ -0,0 +1,13 @@ +// @ts-nocheck +import * as React from 'react'; +import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; +import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; + +const className = treeViewClasses.root; + +// prettier-ignore + + + + + diff --git a/packages/x-codemod/src/v8.0.0/preset-safe/expected.spec.js b/packages/x-codemod/src/v8.0.0/preset-safe/expected.spec.js new file mode 100644 index 000000000000..b3252ca7f64b --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/preset-safe/expected.spec.js @@ -0,0 +1,13 @@ +// @ts-nocheck +import * as React from 'react'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; +import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; + +const className = treeViewClasses.root; + +// prettier-ignore + + + + + diff --git a/packages/x-codemod/src/v8.0.0/preset-safe/index.ts b/packages/x-codemod/src/v8.0.0/preset-safe/index.ts new file mode 100644 index 000000000000..306d3446813d --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/preset-safe/index.ts @@ -0,0 +1,8 @@ +import transformTreeView from '../tree-view/preset-safe'; +import { JsCodeShiftAPI, JsCodeShiftFileInfo } from '../../types'; + +export default function transformer(file: JsCodeShiftFileInfo, api: JsCodeShiftAPI, options: any) { + file.source = transformTreeView(file, api, options); + + return file.source; +} diff --git a/packages/x-codemod/src/v8.0.0/preset-safe/preset-safe.test.ts b/packages/x-codemod/src/v8.0.0/preset-safe/preset-safe.test.ts new file mode 100644 index 000000000000..b0ea5e858c7a --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/preset-safe/preset-safe.test.ts @@ -0,0 +1,27 @@ +import path from 'path'; +import { expect } from 'chai'; +import jscodeshift from 'jscodeshift'; +import transform from '.'; +import readFile from '../../util/readFile'; + +function read(fileName) { + return readFile(path.join(__dirname, fileName)); +} + +describe('v8.0.0', () => { + describe('preset-safe', () => { + it('transforms code as needed', () => { + const actual = transform({ source: read('./actual.spec.js') }, { jscodeshift }, {}); + + const expected = read('./expected.spec.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent', () => { + const actual = transform({ source: read('./expected.spec.js') }, { jscodeshift }, {}); + + const expected = read('./expected.spec.js'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + }); +}); diff --git a/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/actual.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/actual.spec.tsx new file mode 100644 index 000000000000..4b5b1a751562 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/actual.spec.tsx @@ -0,0 +1,11 @@ +// @ts-nocheck +import * as React from 'react'; +import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; +import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; + +const className = treeViewClasses.root; + +// prettier-ignore + + +; diff --git a/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/expected.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/expected.spec.tsx new file mode 100644 index 000000000000..4a5aa4e135b9 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/expected.spec.tsx @@ -0,0 +1,11 @@ +// @ts-nocheck +import * as React from 'react'; +import { TreeItem } from '@mui/x-tree-view/TreeItem'; +import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; + +const className = treeViewClasses.root; + +// prettier-ignore + + +; diff --git a/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/index.ts b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/index.ts new file mode 100644 index 000000000000..d94e0b16c030 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/index.ts @@ -0,0 +1,9 @@ +import transformRenameTreeItem2 from '../rename-tree-item-2'; + +import { JsCodeShiftAPI, JsCodeShiftFileInfo } from '../../../types'; + +export default function transformer(file: JsCodeShiftFileInfo, api: JsCodeShiftAPI, options: any) { + file.source = transformRenameTreeItem2(file, api, options); + + return file.source; +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/preset-safe.test.ts b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/preset-safe.test.ts new file mode 100644 index 000000000000..7f61d9c0e721 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/preset-safe/preset-safe.test.ts @@ -0,0 +1,41 @@ +import path from 'path'; +import { expect } from 'chai'; +import jscodeshift from 'jscodeshift'; +import transform from './index'; +import readFile from '../../../util/readFile'; + +function read(fileName) { + return readFile(path.join(__dirname, fileName)); +} + +describe('v8.0.0/tree-view', () => { + describe('preset-safe', () => { + it('transforms code as needed', () => { + const actual = transform( + { + source: read('./actual.spec.tsx'), + path: require.resolve('./actual.spec.tsx'), + }, + { jscodeshift: jscodeshift.withParser('tsx') }, + {}, + ); + + const expected = read('./expected.spec.tsx'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent for expression', () => { + const actual = transform( + { + source: read('./expected.spec.tsx'), + path: require.resolve('./expected.spec.tsx'), + }, + { jscodeshift: jscodeshift.withParser('tsx') }, + {}, + ); + + const expected = read('./expected.spec.tsx'); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + }); +}); diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-community-nested-imports.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-community-nested-imports.spec.tsx new file mode 100644 index 000000000000..48001cc74bf5 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-community-nested-imports.spec.tsx @@ -0,0 +1,94 @@ +// @ts-nocheck +import * as React from 'react'; +import { + TreeItem2, + TreeItem2Root, + TreeItem2Content, + TreeItem2IconContainer, + TreeItem2GroupTransition, + TreeItem2Checkbox, + TreeItem2Label, + TreeItem2Props, + TreeItem2Slots, + TreeItem2SlotProps, +} from '@mui/x-tree-view/TreeItem2'; +import { + useTreeItem2, + unstable_useTreeItem2 as useAliasedTreeItem, + UseTreeItem2Parameters, + UseTreeItem2ReturnValue, + UseTreeItem2Status, + UseTreeItem2RootSlotOwnProps, + UseTreeItem2ContentSlotOwnProps, + UseTreeItem2LabelInputSlotOwnProps, + UseTreeItem2LabelSlotOwnProps, + UseTreeItem2CheckboxSlotOwnProps, + UseTreeItem2IconContainerSlotOwnProps, + UseTreeItem2GroupTransitionSlotOwnProps, + UseTreeItem2DragAndDropOverlaySlotOwnProps, +} from '@mui/x-tree-view/useTreeItem2'; +import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; +import { + TreeItem2Provider, + TreeItem2ProviderProps, + FakeImportToForcePrettierNewLine, +} from '@mui/x-tree-view/TreeItem2Provider'; +import { + TreeItem2Icon, + TreeItem2IconProps, + TreeItem2IconSlots, + TreeItem2IconSlotProps, +} from '@mui/x-tree-view/TreeItem2Icon'; +import { + TreeItem2DragAndDropOverlay, + TreeItem2DragAndDropOverlayProps, +} from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; +import { + TreeItem2LabelInput, + TreeItem2LabelInputProps, + FakeImport2ToForcePrettierNewLine, +} from '@mui/x-tree-view/TreeItem2LabelInput'; + +// prettier-ignore +function App() { + useTreeItem2({}); + useAliasedTreeItem({}); + useTreeItem2Utils(); + + const treeItemProps: TreeItem2Props = {}; + const treeItemSlots: TreeItem2Slots = {}; + const treeItemSlotProps: TreeItem2SlotProps = {}; + + const params: UseTreeItem2Parameters = {}; + const returnValue: UseTreeItem2ReturnValue = {}; + const status: UseTreeItem2Status = {}; + const root: UseTreeItem2RootSlotOwnProps = {}; + const content: UseTreeItem2ContentSlotOwnProps = {}; + const labelInput: UseTreeItem2LabelInputSlotOwnProps = {}; + const label: UseTreeItem2LabelSlotOwnProps = {}; + const checkbox: UseTreeItem2CheckboxSlotOwnProps = {}; + const iconContainer: UseTreeItem2IconContainerSlotOwnProps = {}; + const groupTransition: UseTreeItem2GroupTransitionSlotOwnProps = {}; + const dragAndDropOverlay: UseTreeItem2DragAndDropOverlaySlotOwnProps = {}; + + const treeItemProviderProps: TreeItem2ProviderProps = {}; + const treeItemIconProps: TreeItem2IconProps = {}; + const treeItemDragAndDropOverlayProps: TreeItem2DragAndDropOverlayProps = {}; + const treeItemLabelInputProps: TreeItem2LabelInputProps = {}; + + return ( + + + + + + + + + + + + + + ); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-community-root-imports.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-community-root-imports.spec.tsx new file mode 100644 index 000000000000..9b5484ad5f45 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-community-root-imports.spec.tsx @@ -0,0 +1,83 @@ +/* eslint-disable no-restricted-imports */ +// @ts-nocheck +import * as React from 'react'; +import { + TreeItem2, + TreeItem2Root, + TreeItem2Content, + TreeItem2IconContainer, + TreeItem2GroupTransition, + TreeItem2Checkbox, + TreeItem2Label, + TreeItem2Props, + TreeItem2Slots, + TreeItem2SlotProps, + useTreeItem2, + unstable_useTreeItem2 as useAliasedTreeItem, + UseTreeItem2Parameters, + UseTreeItem2ReturnValue, + UseTreeItem2Status, + UseTreeItem2RootSlotOwnProps, + UseTreeItem2ContentSlotOwnProps, + UseTreeItem2LabelInputSlotOwnProps, + UseTreeItem2LabelSlotOwnProps, + UseTreeItem2CheckboxSlotOwnProps, + UseTreeItem2IconContainerSlotOwnProps, + UseTreeItem2GroupTransitionSlotOwnProps, + UseTreeItem2DragAndDropOverlaySlotOwnProps, + useTreeItem2Utils, + TreeItem2Provider, + TreeItem2ProviderProps, + TreeItem2Icon, + TreeItem2IconProps, + TreeItem2IconSlots, + TreeItem2IconSlotProps, + TreeItem2DragAndDropOverlay, + TreeItem2DragAndDropOverlayProps, + TreeItem2LabelInput, + TreeItem2LabelInputProps, +} from '@mui/x-tree-view'; + +// prettier-ignore +function App() { + useTreeItem2({}); + useAliasedTreeItem({}); + useTreeItem2Utils(); + + const treeItemProps: TreeItem2Props = {}; + const treeItemSlots: TreeItem2Slots = {}; + const treeItemSlotProps: TreeItem2SlotProps = {}; + + const params: UseTreeItem2Parameters = {}; + const returnValue: UseTreeItem2ReturnValue = {}; + const status: UseTreeItem2Status = {}; + const root: UseTreeItem2RootSlotOwnProps = {}; + const content: UseTreeItem2ContentSlotOwnProps = {}; + const labelInput: UseTreeItem2LabelInputSlotOwnProps = {}; + const label: UseTreeItem2LabelSlotOwnProps = {}; + const checkbox: UseTreeItem2CheckboxSlotOwnProps = {}; + const iconContainer: UseTreeItem2IconContainerSlotOwnProps = {}; + const groupTransition: UseTreeItem2GroupTransitionSlotOwnProps = {}; + const dragAndDropOverlay: UseTreeItem2DragAndDropOverlaySlotOwnProps = {}; + + const treeItemProviderProps: TreeItem2ProviderProps = {}; + const treeItemIconProps: TreeItem2IconProps = {}; + const treeItemDragAndDropOverlayProps: TreeItem2DragAndDropOverlayProps = {}; + const treeItemLabelInputProps: TreeItem2LabelInputProps = {}; + + return ( + + + + + + + + + + + + + + ); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-pro-root-imports.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-pro-root-imports.spec.tsx new file mode 100644 index 000000000000..46275e564461 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/actual-pro-root-imports.spec.tsx @@ -0,0 +1,83 @@ +/* eslint-disable no-restricted-imports */ +// @ts-nocheck +import * as React from 'react'; +import { + TreeItem2, + TreeItem2Root, + TreeItem2Content, + TreeItem2IconContainer, + TreeItem2GroupTransition, + TreeItem2Checkbox, + TreeItem2Label, + TreeItem2Props, + TreeItem2Slots, + TreeItem2SlotProps, + useTreeItem2, + unstable_useTreeItem2 as useAliasedTreeItem, + UseTreeItem2Parameters, + UseTreeItem2ReturnValue, + UseTreeItem2Status, + UseTreeItem2RootSlotOwnProps, + UseTreeItem2ContentSlotOwnProps, + UseTreeItem2LabelInputSlotOwnProps, + UseTreeItem2LabelSlotOwnProps, + UseTreeItem2CheckboxSlotOwnProps, + UseTreeItem2IconContainerSlotOwnProps, + UseTreeItem2GroupTransitionSlotOwnProps, + UseTreeItem2DragAndDropOverlaySlotOwnProps, + useTreeItem2Utils, + TreeItem2Provider, + TreeItem2ProviderProps, + TreeItem2Icon, + TreeItem2IconProps, + TreeItem2IconSlots, + TreeItem2IconSlotProps, + TreeItem2DragAndDropOverlay, + TreeItem2DragAndDropOverlayProps, + TreeItem2LabelInput, + TreeItem2LabelInputProps, +} from '@mui/x-tree-view-pro'; + +// prettier-ignore +function App() { + useTreeItem2({}); + useAliasedTreeItem({}); + useTreeItem2Utils(); + + const treeItemProps: TreeItem2Props = {}; + const treeItemSlots: TreeItem2Slots = {}; + const treeItemSlotProps: TreeItem2SlotProps = {}; + + const params: UseTreeItem2Parameters = {}; + const returnValue: UseTreeItem2ReturnValue = {}; + const status: UseTreeItem2Status = {}; + const root: UseTreeItem2RootSlotOwnProps = {}; + const content: UseTreeItem2ContentSlotOwnProps = {}; + const labelInput: UseTreeItem2LabelInputSlotOwnProps = {}; + const label: UseTreeItem2LabelSlotOwnProps = {}; + const checkbox: UseTreeItem2CheckboxSlotOwnProps = {}; + const iconContainer: UseTreeItem2IconContainerSlotOwnProps = {}; + const groupTransition: UseTreeItem2GroupTransitionSlotOwnProps = {}; + const dragAndDropOverlay: UseTreeItem2DragAndDropOverlaySlotOwnProps = {}; + + const treeItemProviderProps: TreeItem2ProviderProps = {}; + const treeItemIconProps: TreeItem2IconProps = {}; + const treeItemDragAndDropOverlayProps: TreeItem2DragAndDropOverlayProps = {}; + const treeItemLabelInputProps: TreeItem2LabelInputProps = {}; + + return ( + + + + + + + + + + + + + + ); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-community-nested-imports.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-community-nested-imports.spec.tsx new file mode 100644 index 000000000000..b3f4a9d62063 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-community-nested-imports.spec.tsx @@ -0,0 +1,94 @@ +// @ts-nocheck +import * as React from 'react'; +import { + TreeItem, + TreeItemRoot, + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemCheckbox, + TreeItemLabel, + TreeItemProps, + TreeItemSlots, + TreeItemSlotProps, +} from '@mui/x-tree-view/TreeItem'; +import { + useTreeItem, + useTreeItem as useAliasedTreeItem, + UseTreeItemParameters, + UseTreeItemReturnValue, + UseTreeItemStatus, + UseTreeItemRootSlotOwnProps, + UseTreeItemContentSlotOwnProps, + UseTreeItemLabelInputSlotOwnProps, + UseTreeItemLabelSlotOwnProps, + UseTreeItemCheckboxSlotOwnProps, + UseTreeItemIconContainerSlotOwnProps, + UseTreeItemGroupTransitionSlotOwnProps, + UseTreeItemDragAndDropOverlaySlotOwnProps, +} from '@mui/x-tree-view/useTreeItem'; +import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; +import { + TreeItemProvider, + TreeItemProviderProps, + FakeImportToForcePrettierNewLine, +} from '@mui/x-tree-view/TreeItemProvider'; +import { + TreeItemIcon, + TreeItemIconProps, + TreeItemIconSlots, + TreeItemIconSlotProps, +} from '@mui/x-tree-view/TreeItemIcon'; +import { + TreeItemDragAndDropOverlay, + TreeItemDragAndDropOverlayProps, +} from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; +import { + TreeItemLabelInput, + TreeItemLabelInputProps, + FakeImport2ToForcePrettierNewLine, +} from '@mui/x-tree-view/TreeItemLabelInput'; + +// prettier-ignore +function App() { + useTreeItem({}); + useAliasedTreeItem({}); + useTreeItemUtils(); + + const treeItemProps: TreeItemProps = {}; + const treeItemSlots: TreeItemSlots = {}; + const treeItemSlotProps: TreeItemSlotProps = {}; + + const params: UseTreeItemParameters = {}; + const returnValue: UseTreeItemReturnValue = {}; + const status: UseTreeItemStatus = {}; + const root: UseTreeItemRootSlotOwnProps = {}; + const content: UseTreeItemContentSlotOwnProps = {}; + const labelInput: UseTreeItemLabelInputSlotOwnProps = {}; + const label: UseTreeItemLabelSlotOwnProps = {}; + const checkbox: UseTreeItemCheckboxSlotOwnProps = {}; + const iconContainer: UseTreeItemIconContainerSlotOwnProps = {}; + const groupTransition: UseTreeItemGroupTransitionSlotOwnProps = {}; + const dragAndDropOverlay: UseTreeItemDragAndDropOverlaySlotOwnProps = {}; + + const treeItemProviderProps: TreeItemProviderProps = {}; + const treeItemIconProps: TreeItemIconProps = {}; + const treeItemDragAndDropOverlayProps: TreeItemDragAndDropOverlayProps = {}; + const treeItemLabelInputProps: TreeItemLabelInputProps = {}; + + return ( + ( + + + + + + + + + + + + ) + ); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-community-root-imports.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-community-root-imports.spec.tsx new file mode 100644 index 000000000000..864d5e1a5c40 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-community-root-imports.spec.tsx @@ -0,0 +1,83 @@ +/* eslint-disable no-restricted-imports */ +// @ts-nocheck +import * as React from 'react'; +import { + TreeItem, + TreeItemRoot, + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemCheckbox, + TreeItemLabel, + TreeItemProps, + TreeItemSlots, + TreeItemSlotProps, + useTreeItem, + useTreeItem as useAliasedTreeItem, + UseTreeItemParameters, + UseTreeItemReturnValue, + UseTreeItemStatus, + UseTreeItemRootSlotOwnProps, + UseTreeItemContentSlotOwnProps, + UseTreeItemLabelInputSlotOwnProps, + UseTreeItemLabelSlotOwnProps, + UseTreeItemCheckboxSlotOwnProps, + UseTreeItemIconContainerSlotOwnProps, + UseTreeItemGroupTransitionSlotOwnProps, + UseTreeItemDragAndDropOverlaySlotOwnProps, + useTreeItemUtils, + TreeItemProvider, + TreeItemProviderProps, + TreeItemIcon, + TreeItemIconProps, + TreeItemIconSlots, + TreeItemIconSlotProps, + TreeItemDragAndDropOverlay, + TreeItemDragAndDropOverlayProps, + TreeItemLabelInput, + TreeItemLabelInputProps, +} from '@mui/x-tree-view'; + +// prettier-ignore +function App() { + useTreeItem({}); + useAliasedTreeItem({}); + useTreeItemUtils(); + + const treeItemProps: TreeItemProps = {}; + const treeItemSlots: TreeItemSlots = {}; + const treeItemSlotProps: TreeItemSlotProps = {}; + + const params: UseTreeItemParameters = {}; + const returnValue: UseTreeItemReturnValue = {}; + const status: UseTreeItemStatus = {}; + const root: UseTreeItemRootSlotOwnProps = {}; + const content: UseTreeItemContentSlotOwnProps = {}; + const labelInput: UseTreeItemLabelInputSlotOwnProps = {}; + const label: UseTreeItemLabelSlotOwnProps = {}; + const checkbox: UseTreeItemCheckboxSlotOwnProps = {}; + const iconContainer: UseTreeItemIconContainerSlotOwnProps = {}; + const groupTransition: UseTreeItemGroupTransitionSlotOwnProps = {}; + const dragAndDropOverlay: UseTreeItemDragAndDropOverlaySlotOwnProps = {}; + + const treeItemProviderProps: TreeItemProviderProps = {}; + const treeItemIconProps: TreeItemIconProps = {}; + const treeItemDragAndDropOverlayProps: TreeItemDragAndDropOverlayProps = {}; + const treeItemLabelInputProps: TreeItemLabelInputProps = {}; + + return ( + ( + + + + + + + + + + + + ) + ); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-pro-root-imports.spec.tsx b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-pro-root-imports.spec.tsx new file mode 100644 index 000000000000..a05a8d97ba11 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/expected-pro-root-imports.spec.tsx @@ -0,0 +1,83 @@ +/* eslint-disable no-restricted-imports */ +// @ts-nocheck +import * as React from 'react'; +import { + TreeItem, + TreeItemRoot, + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemCheckbox, + TreeItemLabel, + TreeItemProps, + TreeItemSlots, + TreeItemSlotProps, + useTreeItem, + useTreeItem as useAliasedTreeItem, + UseTreeItemParameters, + UseTreeItemReturnValue, + UseTreeItemStatus, + UseTreeItemRootSlotOwnProps, + UseTreeItemContentSlotOwnProps, + UseTreeItemLabelInputSlotOwnProps, + UseTreeItemLabelSlotOwnProps, + UseTreeItemCheckboxSlotOwnProps, + UseTreeItemIconContainerSlotOwnProps, + UseTreeItemGroupTransitionSlotOwnProps, + UseTreeItemDragAndDropOverlaySlotOwnProps, + useTreeItemUtils, + TreeItemProvider, + TreeItemProviderProps, + TreeItemIcon, + TreeItemIconProps, + TreeItemIconSlots, + TreeItemIconSlotProps, + TreeItemDragAndDropOverlay, + TreeItemDragAndDropOverlayProps, + TreeItemLabelInput, + TreeItemLabelInputProps, +} from '@mui/x-tree-view-pro'; + +// prettier-ignore +function App() { + useTreeItem({}); + useAliasedTreeItem({}); + useTreeItemUtils(); + + const treeItemProps: TreeItemProps = {}; + const treeItemSlots: TreeItemSlots = {}; + const treeItemSlotProps: TreeItemSlotProps = {}; + + const params: UseTreeItemParameters = {}; + const returnValue: UseTreeItemReturnValue = {}; + const status: UseTreeItemStatus = {}; + const root: UseTreeItemRootSlotOwnProps = {}; + const content: UseTreeItemContentSlotOwnProps = {}; + const labelInput: UseTreeItemLabelInputSlotOwnProps = {}; + const label: UseTreeItemLabelSlotOwnProps = {}; + const checkbox: UseTreeItemCheckboxSlotOwnProps = {}; + const iconContainer: UseTreeItemIconContainerSlotOwnProps = {}; + const groupTransition: UseTreeItemGroupTransitionSlotOwnProps = {}; + const dragAndDropOverlay: UseTreeItemDragAndDropOverlaySlotOwnProps = {}; + + const treeItemProviderProps: TreeItemProviderProps = {}; + const treeItemIconProps: TreeItemIconProps = {}; + const treeItemDragAndDropOverlayProps: TreeItemDragAndDropOverlayProps = {}; + const treeItemLabelInputProps: TreeItemLabelInputProps = {}; + + return ( + ( + + + + + + + + + + + + ) + ); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/index.ts b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/index.ts new file mode 100644 index 000000000000..fcc1d0b10b12 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/index.ts @@ -0,0 +1,98 @@ +import type { JsCodeShiftAPI, JsCodeShiftFileInfo } from '../../../types'; +import { renameImports } from '../../../util/renameImports'; + +export default function transformer(file: JsCodeShiftFileInfo, api: JsCodeShiftAPI, options: any) { + const j = api.jscodeshift; + const root = j(file.source); + + const printOptions = options.printOptions || { + quote: 'single', + trailingComma: true, + wrapColumn: 40, + }; + + renameImports({ + j, + root, + packageNames: ['@mui/x-tree-view', '@mui/x-tree-view-pro'], + imports: [ + { + oldEndpoint: 'TreeItem2', + newEndpoint: 'TreeItem', + importsMapping: { + TreeItem2: 'TreeItem', + TreeItem2Root: 'TreeItemRoot', + TreeItem2Content: 'TreeItemContent', + TreeItem2IconContainer: 'TreeItemIconContainer', + TreeItem2GroupTransition: 'TreeItemGroupTransition', + TreeItem2Checkbox: 'TreeItemCheckbox', + TreeItem2Label: 'TreeItemLabel', + TreeItem2Props: 'TreeItemProps', + TreeItem2Slots: 'TreeItemSlots', + TreeItem2SlotProps: 'TreeItemSlotProps', + }, + }, + { + oldEndpoint: 'useTreeItem2', + newEndpoint: 'useTreeItem', + importsMapping: { + useTreeItem2: 'useTreeItem', + unstable_useTreeItem2: 'useTreeItem', + UseTreeItem2Parameters: 'UseTreeItemParameters', + UseTreeItem2ReturnValue: 'UseTreeItemReturnValue', + UseTreeItem2Status: 'UseTreeItemStatus', + UseTreeItem2RootSlotOwnProps: 'UseTreeItemRootSlotOwnProps', + UseTreeItem2ContentSlotOwnProps: 'UseTreeItemContentSlotOwnProps', + UseTreeItem2LabelInputSlotOwnProps: 'UseTreeItemLabelInputSlotOwnProps', + UseTreeItem2LabelSlotOwnProps: 'UseTreeItemLabelSlotOwnProps', + UseTreeItem2CheckboxSlotOwnProps: 'UseTreeItemCheckboxSlotOwnProps', + UseTreeItem2IconContainerSlotOwnProps: 'UseTreeItemIconContainerSlotOwnProps', + UseTreeItem2GroupTransitionSlotOwnProps: 'UseTreeItemGroupTransitionSlotOwnProps', + UseTreeItem2DragAndDropOverlaySlotOwnProps: 'UseTreeItemDragAndDropOverlaySlotOwnProps', + }, + }, + { + oldEndpoint: 'TreeItem2Provider', + newEndpoint: 'TreeItemProvider', + importsMapping: { + TreeItem2Provider: 'TreeItemProvider', + TreeItem2ProviderProps: 'TreeItemProviderProps', + }, + }, + { + oldEndpoint: 'TreeItem2Icon', + newEndpoint: 'TreeItemIcon', + importsMapping: { + TreeItem2Icon: 'TreeItemIcon', + TreeItem2IconProps: 'TreeItemIconProps', + TreeItem2IconSlots: 'TreeItemIconSlots', + TreeItem2IconSlotProps: 'TreeItemIconSlotProps', + }, + }, + { + oldEndpoint: 'TreeItem2DragAndDropOverlay', + newEndpoint: 'TreeItemDragAndDropOverlay', + importsMapping: { + TreeItem2DragAndDropOverlay: 'TreeItemDragAndDropOverlay', + TreeItem2DragAndDropOverlayProps: 'TreeItemDragAndDropOverlayProps', + }, + }, + { + oldEndpoint: 'TreeItem2LabelInput', + newEndpoint: 'TreeItemLabelInput', + importsMapping: { + TreeItem2LabelInput: 'TreeItemLabelInput', + TreeItem2LabelInputProps: 'TreeItemLabelInputProps', + }, + }, + { + oldEndpoint: 'hooks', + importsMapping: { + useTreeItem2Utils: 'useTreeItemUtils', + }, + }, + ], + }); + + return root.toSource(printOptions); +} diff --git a/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/rename-tree-item-2.test.ts b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/rename-tree-item-2.test.ts new file mode 100644 index 000000000000..d91b9e1456b5 --- /dev/null +++ b/packages/x-codemod/src/v8.0.0/tree-view/rename-tree-item-2/rename-tree-item-2.test.ts @@ -0,0 +1,44 @@ +import path from 'path'; +import { expect } from 'chai'; +import jscodeshift from 'jscodeshift'; +import transform from '.'; +import readFile from '../../../util/readFile'; + +function read(fileName) { + return readFile(path.join(__dirname, fileName)); +} + +const TEST_FILES = ['community-nested-imports', 'community-root-imports', 'pro-root-imports']; + +describe('v7.0.0/tree-view', () => { + describe('rename-tree-item-2', () => { + TEST_FILES.forEach((testFile) => { + const actualPath = `./actual-${testFile}.spec.tsx`; + const expectedPath = `./expected-${testFile}.spec.tsx`; + + describe(`${testFile.replace(/-/g, ' ')}`, () => { + it('transforms imports as needed', () => { + const actual = transform( + { source: read(actualPath) }, + { jscodeshift: jscodeshift.withParser('tsx') }, + {}, + ); + + const expected = read(expectedPath); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + + it('should be idempotent', () => { + const actual = transform( + { source: read(expectedPath) }, + { jscodeshift: jscodeshift.withParser('tsx') }, + {}, + ); + + const expected = read(expectedPath); + expect(actual).to.equal(expected, 'The transformed version should be correct'); + }); + }); + }); + }); +}); diff --git a/packages/x-data-grid-generator/package.json b/packages/x-data-grid-generator/package.json index cf21b769479f..a9b08defc290 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.20.0", + "version": "7.21.0", "description": "Generate fake data for demo purposes only.", "author": "MUI Team", "main": "src/index.ts", diff --git a/packages/x-data-grid-generator/src/hooks/index.ts b/packages/x-data-grid-generator/src/hooks/index.ts index 84dd7368aea4..223d6170c94b 100644 --- a/packages/x-data-grid-generator/src/hooks/index.ts +++ b/packages/x-data-grid-generator/src/hooks/index.ts @@ -1,6 +1,7 @@ export * from './useDemoData'; export * from './useBasicDemoData'; -export * from './useMovieData'; +export { useMovieData } from './useMovieData'; +export type { Movie } from './useMovieData'; export * from './useQuery'; export * from './useMockServer'; export { loadServerRows } from './serverUtils'; diff --git a/packages/x-data-grid-generator/src/hooks/serverUtils.ts b/packages/x-data-grid-generator/src/hooks/serverUtils.ts index 095893126643..80651b133af5 100644 --- a/packages/x-data-grid-generator/src/hooks/serverUtils.ts +++ b/packages/x-data-grid-generator/src/hooks/serverUtils.ts @@ -10,7 +10,6 @@ import { GridValidRowModel, } from '@mui/x-data-grid-pro'; import { GridStateColDef } from '@mui/x-data-grid-pro/internals'; -import { UseDemoDataOptions } from './useDemoData'; import { randomInt } from '../services/random-generator'; export interface FakeServerResponse { @@ -53,14 +52,9 @@ export interface ServerSideQueryOptions { sortModel?: GridSortModel; firstRowToRender?: number; lastRowToRender?: number; + groupFields?: string[]; } -export const DEFAULT_DATASET_OPTIONS: UseDemoDataOptions = { - dataSet: 'Commodity', - rowLength: 100, - maxColumns: 6, -}; - declare const DISABLE_CHANCE_RANDOM: any; export const disableDelay = typeof DISABLE_CHANCE_RANDOM !== 'undefined' && DISABLE_CHANCE_RANDOM; @@ -323,7 +317,7 @@ export const loadServerRows = ( }); }; -interface ProcessTreeDataRowsResponse { +interface NestedDataRowsResponse { rows: GridRowModel[]; rootRowCount: number; } @@ -333,6 +327,7 @@ const findTreeDataRowChildren = ( parentPath: string[], pathKey: string = 'path', depth: number = 1, // the depth of the children to find relative to parentDepth, `-1` to find all + rowQualifier?: (row: GridRowModel) => boolean, ) => { const parentDepth = parentPath.length; const children = []; @@ -346,7 +341,9 @@ const findTreeDataRowChildren = ( ((depth < 0 && rowPath.length > parentDepth) || rowPath.length === parentDepth + depth) && parentPath.every((value, index) => value === rowPath[index]) ) { - children.push(row); + if (!rowQualifier || rowQualifier(row)) { + children.push(row); + } } } return children; @@ -427,14 +424,14 @@ const getTreeDataFilteredRows: GetTreeDataFilteredRows = ( }; /** - * Simulates server data loading + * Simulates server data for tree-data feature */ export const processTreeDataRows = ( rows: GridRowModel[], queryOptions: ServerSideQueryOptions, serverOptions: ServerOptions, columnsWithDefaultColDef: GridColDef[], -): Promise => { +): Promise => { const { minDelay = 100, maxDelay = 300 } = serverOptions; const pathKey = 'path'; // TODO: Support filtering and cursor based pagination @@ -490,3 +487,124 @@ export const processTreeDataRows = ( }, delay); // simulate network latency }); }; + +/** + * Simulates server data for row grouping feature + */ +export const processRowGroupingRows = ( + rows: GridValidRowModel[], + queryOptions: ServerSideQueryOptions, + serverOptions: ServerOptions, + columnsWithDefaultColDef: GridColDef[], +): Promise => { + const { minDelay = 100, maxDelay = 300 } = serverOptions; + const pathKey = 'path'; + + if (maxDelay < minDelay) { + throw new Error('serverOptions.minDelay is larger than serverOptions.maxDelay '); + } + + if (queryOptions.groupKeys == null) { + throw new Error('serverOptions.groupKeys must be defined to compute row grouping data'); + } + + if (queryOptions.groupFields == null) { + throw new Error('serverOptions.groupFields must be defined to compute row grouping data'); + } + + const delay = randomInt(minDelay, maxDelay); + + const pathsToAutogenerate = new Set(); + let rowsWithPaths = rows; + const rowsWithMissingGroups: GridValidRowModel[] = []; + + // add paths and generate parent rows based on `groupFields` + const groupFields = queryOptions.groupFields; + if (groupFields.length > 0) { + rowsWithPaths = rows.reduce((acc, row) => { + const partialPath = groupFields.map((field) => String(row[field])); + for (let index = 0; index < partialPath.length; index += 1) { + const value = partialPath[index]; + if (value === undefined) { + if (index === 0) { + rowsWithMissingGroups.push({ ...row, group: false }); + } + return acc; + } + const parentPath = partialPath.slice(0, index + 1); + const strigifiedPath = parentPath.join(','); + if (!pathsToAutogenerate.has(strigifiedPath)) { + pathsToAutogenerate.add(strigifiedPath); + } + } + acc.push({ ...row, path: [...partialPath, ''] }); + return acc; + }, []); + } else { + rowsWithPaths = rows.map((row) => ({ ...row, path: [''] })); + } + + const autogeneratedRows = Array.from(pathsToAutogenerate).map((path) => { + const pathArray = path.split(','); + return { + id: `auto-generated-parent-${pathArray.join('-')}`, + path: pathArray.slice(0, pathArray.length), + group: pathArray.slice(-1)[0], + }; + }); + + // apply plain filtering + const filteredRows = getTreeDataFilteredRows( + [...autogeneratedRows, ...rowsWithPaths, ...rowsWithMissingGroups], + queryOptions.filterModel, + columnsWithDefaultColDef, + ) as GridValidRowModel[]; + + // get root row count + const rootRows = findTreeDataRowChildren(filteredRows, []); + const rootRowCount = rootRows.length; + + let filteredRowsWithMissingGroups: GridValidRowModel[] = []; + let childRows = rootRows; + if (queryOptions.groupKeys.length === 0) { + filteredRowsWithMissingGroups = filteredRows.filter(({ group }) => group === false); + } else { + childRows = findTreeDataRowChildren(filteredRows, queryOptions.groupKeys); + } + + let childRowsWithDescendantCounts = childRows.map((row) => { + const descendants = findTreeDataRowChildren( + filteredRows, + row[pathKey], + pathKey, + -1, + ({ id }) => typeof id !== 'string' || !id.startsWith('auto-generated-parent-'), + ); + const descendantCount = descendants.length; + return { ...row, descendantCount } as GridRowModel; + }); + + if (queryOptions.sortModel) { + const rowComparator = getRowComparator(queryOptions.sortModel, columnsWithDefaultColDef); + const sortedMissingGroups = [...filteredRowsWithMissingGroups].sort(rowComparator); + const sortedChildRows = [...childRowsWithDescendantCounts].sort(rowComparator); + childRowsWithDescendantCounts = [...sortedMissingGroups, ...sortedChildRows]; + } + + if (queryOptions.paginationModel && queryOptions.groupKeys.length === 0) { + // Only paginate root rows, grid should refetch root rows when `paginationModel` updates + const { pageSize, page } = queryOptions.paginationModel; + if (pageSize < childRowsWithDescendantCounts.length) { + childRowsWithDescendantCounts = childRowsWithDescendantCounts.slice( + page * pageSize, + (page + 1) * pageSize, + ); + } + } + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ rows: childRowsWithDescendantCounts, rootRowCount }); + }, delay); // simulate network latency + }); +}; diff --git a/packages/x-data-grid-generator/src/hooks/useMockServer.ts b/packages/x-data-grid-generator/src/hooks/useMockServer.ts index 83c43fd30c05..e9c168f0ca08 100644 --- a/packages/x-data-grid-generator/src/hooks/useMockServer.ts +++ b/packages/x-data-grid-generator/src/hooks/useMockServer.ts @@ -9,23 +9,24 @@ import { GridInitialState, GridColumnVisibilityModel, } from '@mui/x-data-grid-pro'; -import { - UseDemoDataOptions, - getColumnsFromOptions, - extrapolateSeed, - deepFreeze, -} from './useDemoData'; +import { extrapolateSeed, deepFreeze } from './useDemoData'; +import { getCommodityColumns } from '../columns/commodities.columns'; +import { getEmployeeColumns } from '../columns/employees.columns'; import { GridColDefGenerator } from '../services/gridColDefGenerator'; import { getRealGridData, GridDemoData } from '../services/real-data-service'; -import { addTreeDataOptionsToDemoData } from '../services/tree-data-generator'; +import { + addTreeDataOptionsToDemoData, + AddPathToDemoDataOptions, +} from '../services/tree-data-generator'; import { loadServerRows, processTreeDataRows, - DEFAULT_DATASET_OPTIONS, + processRowGroupingRows, DEFAULT_SERVER_OPTIONS, } from './serverUtils'; import type { ServerOptions } from './serverUtils'; import { randomInt } from '../services'; +import { getMovieRows, getMovieColumns } from './useMovieData'; const dataCache = new LRUCache({ max: 10, @@ -43,6 +44,66 @@ type UseMockServerResponse = { loadNewData: () => void; }; +type DataSet = 'Commodity' | 'Employee' | 'Movies'; + +interface UseMockServerOptions { + dataSet: DataSet; + /** + * Has no effect when DataSet='Movies' + */ + rowLength: number; + maxColumns?: number; + visibleFields?: string[]; + editable?: boolean; + treeData?: AddPathToDemoDataOptions; + rowGrouping?: boolean; +} + +interface GridMockServerData { + rows: GridRowModel[]; + columns: GridColDefGenerator[] | GridColDef[]; + initialState?: GridInitialState; +} + +interface ColumnsOptions + extends Pick {} + +const GET_DEFAULT_DATASET_OPTIONS: (isRowGrouping: boolean) => UseMockServerOptions = ( + isRowGrouping, +) => ({ + dataSet: isRowGrouping ? 'Movies' : 'Commodity', + rowLength: isRowGrouping ? getMovieRows().length : 100, + maxColumns: 6, +}); + +const getColumnsFromOptions = (options: ColumnsOptions): GridColDefGenerator[] | GridColDef[] => { + let columns; + + switch (options.dataSet) { + case 'Commodity': + columns = getCommodityColumns(options.editable); + break; + case 'Employee': + columns = getEmployeeColumns(); + break; + case 'Movies': + columns = getMovieColumns(); + break; + default: + throw new Error('Unknown dataset'); + } + + if (options.visibleFields) { + columns = columns.map((col) => + options.visibleFields?.includes(col.field) ? col : { ...col, hide: true }, + ); + } + if (options.maxColumns) { + columns = columns.slice(0, options.maxColumns); + } + return columns; +}; + function decodeParams(url: string): GridGetRowsParams { const params = new URL(url).searchParams; const decodedParams = {} as any; @@ -76,12 +137,18 @@ const getInitialState = (columns: GridColDefGenerator[], groupingField?: string) const defaultColDef = getGridDefaultColumnTypes(); +function sendEmptyResponse() { + return new Promise((resolve) => { + resolve({ rows: [], rowCount: 0 }); + }); +} + export const useMockServer = ( - dataSetOptions?: Partial, + dataSetOptions?: Partial, serverOptions?: ServerOptions & { verbose?: boolean }, shouldRequestsFail?: boolean, ): UseMockServerResponse => { - const [data, setData] = React.useState(); + const [data, setData] = React.useState(); const [index, setIndex] = React.useState(0); const shouldRequestsFailRef = React.useRef(shouldRequestsFail ?? false); @@ -91,7 +158,11 @@ export const useMockServer = ( } }, [shouldRequestsFail]); - const options = { ...DEFAULT_DATASET_OPTIONS, ...dataSetOptions }; + const isRowGrouping = dataSetOptions?.rowGrouping ?? false; + + const options = { ...GET_DEFAULT_DATASET_OPTIONS(isRowGrouping), ...dataSetOptions }; + + const isTreeData = options.treeData?.groupingField != null; const columns = React.useMemo(() => { return getColumnsFromOptions({ @@ -116,8 +187,6 @@ export const useMockServer = ( [columns], ); - const isTreeData = options.treeData?.groupingField != null; - const getGroupKey = React.useMemo(() => { if (isTreeData) { return (row: GridRowModel): string => row[options.treeData!.groupingField!]; @@ -144,6 +213,13 @@ export const useMockServer = ( return undefined; } + if (options.dataSet === 'Movies') { + const rowsData = { rows: getMovieRows(), columns }; + setData(rowsData); + dataCache.set(cacheKey, rowsData); + return undefined; + } + let active = true; (async () => { @@ -193,10 +269,8 @@ export const useMockServer = ( const fetchRows = React.useCallback( async (requestUrl: string): Promise => { - if (!data || !requestUrl) { - return new Promise((resolve) => { - resolve({ rows: [], rowCount: 0 }); - }); + if (!requestUrl || !data?.rows) { + return sendEmptyResponse(); } const params = decodeParams(requestUrl); const verbose = serverOptions?.verbose ?? true; @@ -224,9 +298,21 @@ export const useMockServer = ( }); } - if (isTreeData /* || TODO: `isRowGrouping` */) { + if (isTreeData) { const { rows, rootRowCount } = await processTreeDataRows( - data.rows, + data?.rows ?? [], + params, + serverOptionsWithDefault, + columnsWithDefaultColDef, + ); + + getRowsResponse = { + rows: rows.slice().map((row) => ({ ...row, path: undefined })), + rowCount: rootRowCount, + }; + } else if (isRowGrouping) { + const { rows, rootRowCount } = await processRowGroupingRows( + data?.rows ?? [], params, serverOptionsWithDefault, columnsWithDefaultColDef, @@ -237,9 +323,8 @@ export const useMockServer = ( rowCount: rootRowCount, }; } else { - // plain data const { returnedRows, nextCursor, totalRowCount } = await loadServerRows( - data.rows, + data?.rows ?? [], { ...params, ...params.paginationModel }, serverOptionsWithDefault, columnsWithDefaultColDef, @@ -262,12 +347,13 @@ export const useMockServer = ( serverOptions?.useCursorPagination, isTreeData, columnsWithDefaultColDef, + isRowGrouping, ], ); return { columns: columnsWithDefaultColDef, - initialState, + initialState: options.dataSet === 'Movies' ? {} : initialState, getGroupKey, getChildrenCount, fetchRows, diff --git a/packages/x-data-grid-generator/src/hooks/useMovieData.ts b/packages/x-data-grid-generator/src/hooks/useMovieData.ts index 7820a7471fde..b9b196716839 100644 --- a/packages/x-data-grid-generator/src/hooks/useMovieData.ts +++ b/packages/x-data-grid-generator/src/hooks/useMovieData.ts @@ -546,6 +546,9 @@ const ROWS: GridRowModel[] = [ }, ]; +export const getMovieColumns = (): GridColDef[] => COLUMNS; +export const getMovieRows = (): GridRowModel[] => ROWS; + export const useMovieData = () => { return { rows: ROWS, diff --git a/packages/x-data-grid-generator/src/hooks/useQuery.ts b/packages/x-data-grid-generator/src/hooks/useQuery.ts index 62ae140bcdcd..5387a7e1f485 100644 --- a/packages/x-data-grid-generator/src/hooks/useQuery.ts +++ b/packages/x-data-grid-generator/src/hooks/useQuery.ts @@ -7,9 +7,15 @@ import { getColumnsFromOptions, getInitialState, } from './useDemoData'; -import { DEFAULT_DATASET_OPTIONS, DEFAULT_SERVER_OPTIONS, loadServerRows } from './serverUtils'; +import { DEFAULT_SERVER_OPTIONS, loadServerRows } from './serverUtils'; import type { ServerOptions, QueryOptions, PageInfo } from './serverUtils'; +const DEFAULT_DATASET_OPTIONS: UseDemoDataOptions = { + dataSet: 'Commodity', + rowLength: 100, + maxColumns: 6, +}; + export const createFakeServer = ( dataSetOptions?: Partial, serverOptions?: ServerOptions, diff --git a/packages/x-data-grid-premium/package.json b/packages/x-data-grid-premium/package.json index 0099d542a43d..228a3581e84a 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.20.0", + "version": "7.21.0", "description": "The Premium plan edition of the Data Grid Components (MUI X).", "author": "MUI Team", "main": "src/index.ts", diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index 8106dec99163..46c76914dda7 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -86,6 +86,7 @@ import { rowGroupingStateInitializer, } from '../hooks/features/rowGrouping/useGridRowGrouping'; import { useGridRowGroupingPreProcessors } from '../hooks/features/rowGrouping/useGridRowGroupingPreProcessors'; +import { useGridDataSourceRowGroupingPreProcessors } from '../hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors'; import { useGridExcelExport } from '../hooks/features/export/useGridExcelExport'; import { cellSelectionStateInitializer, @@ -105,6 +106,7 @@ export const useDataGridPremiumComponent = ( useGridRowSelectionPreProcessors(apiRef, props); useGridRowReorderPreProcessors(apiRef, props); useGridRowGroupingPreProcessors(apiRef, props); + useGridDataSourceRowGroupingPreProcessors(apiRef, props); useGridTreeDataPreProcessors(apiRef, props); useGridDataSourceTreeDataPreProcessors(apiRef, props); useGridLazyLoaderPreProcessors(apiRef, props); diff --git a/packages/x-data-grid-premium/src/components/GridColumnMenuAggregationItem.tsx b/packages/x-data-grid-premium/src/components/GridColumnMenuAggregationItem.tsx index f2976b011913..27fd029bb344 100644 --- a/packages/x-data-grid-premium/src/components/GridColumnMenuAggregationItem.tsx +++ b/packages/x-data-grid-premium/src/components/GridColumnMenuAggregationItem.tsx @@ -1,13 +1,11 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { GridColumnMenuItemProps, useGridSelector } from '@mui/x-data-grid-pro'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import FormControl from '@mui/material/FormControl'; import InputLabel from '@mui/material/InputLabel'; import { unstable_useId as useId } from '@mui/utils'; -import Select, { SelectChangeEvent } from '@mui/material/Select'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { @@ -53,8 +51,8 @@ function GridColumnMenuAggregationItem(props: GridColumnMenuItemProps) { return ''; }, [rootProps.aggregationFunctions, aggregationModel, colDef]); - const handleAggregationItemChange = (event: SelectChangeEvent) => { - const newAggregationItem = event.target?.value || undefined; + const handleAggregationItemChange = (event: Event) => { + const newAggregationItem = (event.target as HTMLSelectElement | null)?.value || undefined; const currentModel = gridAggregationModelSelector(apiRef); const { [colDef.field]: columnItem, ...otherColumnItems } = currentModel; const newModel: GridAggregationModel = @@ -69,26 +67,26 @@ function GridColumnMenuAggregationItem(props: GridColumnMenuItemProps) { const label = apiRef.current.getLocaleText('aggregationMenuItemHeader'); return ( - + {label} - + - + ); } diff --git a/packages/x-data-grid-premium/src/components/GridColumnMenuRowGroupItem.tsx b/packages/x-data-grid-premium/src/components/GridColumnMenuRowGroupItem.tsx index 0cc989571312..7f661095f957 100644 --- a/packages/x-data-grid-premium/src/components/GridColumnMenuRowGroupItem.tsx +++ b/packages/x-data-grid-premium/src/components/GridColumnMenuRowGroupItem.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { @@ -32,12 +31,16 @@ export function GridColumnMenuRowGroupItem(props: GridColumnMenuItemProps) { const groupedColumn = columnsLookup[field]; const name = groupedColumn.headerName ?? field; return ( - + {apiRef.current.getLocaleText('unGroupColumn')(name)} - + ); }; diff --git a/packages/x-data-grid-premium/src/components/GridColumnMenuRowUngroupItem.tsx b/packages/x-data-grid-premium/src/components/GridColumnMenuRowUngroupItem.tsx index 48a8e21f85ff..9f100470d0b6 100644 --- a/packages/x-data-grid-premium/src/components/GridColumnMenuRowUngroupItem.tsx +++ b/packages/x-data-grid-premium/src/components/GridColumnMenuRowUngroupItem.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { @@ -36,21 +35,21 @@ export function GridColumnMenuRowUngroupItem(props: GridColumnMenuItemProps) { if (rowGroupingModel.includes(colDef.field)) { return ( - + {apiRef.current.getLocaleText('unGroupColumn')(name)} - + ); } return ( - + {apiRef.current.getLocaleText('groupColumn')(name)} - + ); } diff --git a/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx b/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx new file mode 100644 index 000000000000..0f7f159ee84c --- /dev/null +++ b/packages/x-data-grid-premium/src/components/GridDataSourceGroupingCriteriaCell.tsx @@ -0,0 +1,147 @@ +import * as React from 'react'; +import { unstable_composeClasses as composeClasses } from '@mui/utils'; +import Box from '@mui/material/Box'; +import CircularProgress from '@mui/material/CircularProgress'; +import { useGridPrivateApiContext } from '@mui/x-data-grid-pro/internals'; +import { + useGridSelector, + getDataGridUtilityClass, + GridRenderCellParams, + GridGroupNode, +} from '@mui/x-data-grid-pro'; +import { useGridApiContext } from '../hooks/utils/useGridApiContext'; +import { useGridRootProps } from '../hooks/utils/useGridRootProps'; +import { DataGridPremiumProcessedProps } from '../models/dataGridPremiumProps'; +import { GridPrivateApiPremium } from '../models/gridApiPremium'; +import { GridStatePremium } from '../models/gridStatePremium'; + +type OwnerState = DataGridPremiumProcessedProps; + +const useUtilityClasses = (ownerState: OwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['groupingCriteriaCell'], + toggle: ['groupingCriteriaCellToggle'], + loadingContainer: ['groupingCriteriaCellLoadingContainer'], + }; + + return composeClasses(slots, getDataGridUtilityClass, classes); +}; + +interface GridGroupingCriteriaCellProps extends GridRenderCellParams { + hideDescendantCount?: boolean; +} + +interface GridGroupingCriteriaCellIconProps + extends Pick { + descendantCount: number; +} + +function GridGroupingCriteriaCellIcon(props: GridGroupingCriteriaCellIconProps) { + const apiRef = useGridPrivateApiContext() as React.MutableRefObject; + const rootProps = useGridRootProps(); + const classes = useUtilityClasses(rootProps); + const { rowNode, id, field, descendantCount } = props; + + const loadingSelector = (state: GridStatePremium) => state.dataSource.loading[id] ?? false; + const errorSelector = (state: GridStatePremium) => state.dataSource.errors[id]; + const isDataLoading = useGridSelector(apiRef, loadingSelector); + const error = useGridSelector(apiRef, errorSelector); + + const handleClick = (event: React.MouseEvent) => { + if (!rowNode.childrenExpanded) { + // always fetch/get from cache the children when the node is expanded + apiRef.current.unstable_dataSource.fetchRows(id); + } else { + apiRef.current.setRowChildrenExpansion(id, !rowNode.childrenExpanded); + } + apiRef.current.setCellFocus(id, field); + event.stopPropagation(); + }; + + const Icon = rowNode.childrenExpanded + ? rootProps.slots.groupingCriteriaCollapseIcon + : rootProps.slots.groupingCriteriaExpandIcon; + + if (isDataLoading) { + return ( +
+ +
+ ); + } + + return descendantCount > 0 ? ( + + + + + + + + ) : null; +} + +export function GridDataSourceGroupingCriteriaCell(props: GridGroupingCriteriaCellProps) { + const { id, field, rowNode, hideDescendantCount, formattedValue } = props; + + const rootProps = useGridRootProps(); + const apiRef = useGridApiContext(); + const rowSelector = (state: GridStatePremium) => state.rows.dataRowIdToModelLookup[id]; + const row = useGridSelector(apiRef, rowSelector); + const classes = useUtilityClasses(rootProps); + + let descendantCount = 0; + if (row) { + descendantCount = Math.max(rootProps.unstable_dataSource?.getChildrenCount?.(row) ?? 0, 0); + } + + let cellContent: React.ReactNode; + + const colDef = apiRef.current.getColumn(rowNode.groupingField!); + if (typeof colDef?.renderCell === 'function') { + cellContent = colDef.renderCell(props); + } else if (typeof formattedValue !== 'undefined') { + cellContent = {formattedValue}; + } else { + cellContent = {rowNode.groupingKey}; + } + + return ( + + `calc(var(--DataGrid-cellOffsetMultiplier) * ${theme.spacing(rowNode.depth)})`, + }} + > +
+ +
+ {cellContent} + {!hideDescendantCount && descendantCount > 0 ? ( + ({descendantCount}) + ) : null} +
+ ); +} diff --git a/packages/x-data-grid-premium/src/components/GridExcelExportMenuItem.tsx b/packages/x-data-grid-premium/src/components/GridExcelExportMenuItem.tsx index 37e265b9233b..a73c4619b3bf 100644 --- a/packages/x-data-grid-premium/src/components/GridExcelExportMenuItem.tsx +++ b/packages/x-data-grid-premium/src/components/GridExcelExportMenuItem.tsx @@ -1,18 +1,19 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import MenuItem from '@mui/material/MenuItem'; import { GridExportMenuItemProps } from '@mui/x-data-grid-pro'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; +import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { GridExcelExportOptions } from '../hooks/features/export'; export type GridExcelExportMenuItemProps = GridExportMenuItemProps; function GridExcelExportMenuItem(props: GridExcelExportMenuItemProps) { const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); const { hideMenu, options, ...other } = props; return ( - { apiRef.current.exportDataAsExcel(options); hideMenu?.(); @@ -20,7 +21,7 @@ function GridExcelExportMenuItem(props: GridExcelExportMenuItemProps) { {...other} > {apiRef.current.getLocaleText('toolbarExportExcel')} - + ); } 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 634fbdad61b9..928b1cd11bfb 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 @@ -1,5 +1,6 @@ import * as React from 'react'; -import { ownerDocument, useEventCallback } from '@mui/material/utils'; +import ownerDocument from '@mui/utils/ownerDocument'; +import useEventCallback from '@mui/utils/useEventCallback'; import { GridPipeProcessor, GridStateInitializer, diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx index caa1a42af8f5..194c62efa521 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/createGroupingColDef.tsx @@ -12,10 +12,12 @@ import { GridColumnRawLookup, isSingleSelectColDef } from '@mui/x-data-grid-pro/ import { GridApiPremium } from '../../../models/gridApiPremium'; import { GridGroupingColumnFooterCell } from '../../../components/GridGroupingColumnFooterCell'; import { GridGroupingCriteriaCell } from '../../../components/GridGroupingCriteriaCell'; +import { GridDataSourceGroupingCriteriaCell } from '../../../components/GridDataSourceGroupingCriteriaCell'; import { GridGroupingColumnLeafCell } from '../../../components/GridGroupingColumnLeafCell'; import { getRowGroupingFieldFromGroupingCriteria, GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, + RowGroupingStrategy, } from './gridRowGroupingUtils'; import { gridRowGroupingSanitizedModelSelector } from './gridRowGroupingSelector'; @@ -25,11 +27,24 @@ const GROUPING_COL_DEF_DEFAULT_PROPERTIES: Omit = { disableReorder: true, }; -const GROUPING_COL_DEF_FORCED_PROPERTIES: Pick = { +const GROUPING_COL_DEF_FORCED_PROPERTIES_DEFAULT: Pick< + GridColDef, + 'type' | 'editable' | 'groupable' +> = { editable: false, groupable: false, }; +const GROUPING_COL_DEF_FORCED_PROPERTIES_DATA_SOURCE: Pick< + GridColDef, + 'type' | 'editable' | 'groupable' | 'filterable' | 'sortable' | 'aggregable' +> = { + ...GROUPING_COL_DEF_FORCED_PROPERTIES_DEFAULT, + // TODO: Support these features on the grouping column(s) + filterable: false, + sortable: false, +}; + /** * When sorting two cells with different grouping criteria, we consider that the cell with the grouping criteria coming first in the model should be displayed below. * This can occur when some rows don't have all the fields. In which case we want the rows with the missing field to be displayed above. @@ -122,6 +137,7 @@ interface CreateGroupingColDefMonoCriteriaParams { * This value comes `prop.groupingColDef`. */ colDefOverride: GridGroupingColDefOverride | null | undefined; + strategy?: RowGroupingStrategy; } /** @@ -132,11 +148,17 @@ export const createGroupingColDefForOneGroupingCriteria = ({ groupedByColDef, groupingCriteria, colDefOverride, + strategy = RowGroupingStrategy.Default, }: CreateGroupingColDefMonoCriteriaParams): GridColDef => { const { leafField, mainGroupingCriteria, hideDescendantCount, ...colDefOverrideProperties } = colDefOverride ?? {}; const leafColDef = leafField ? columnsLookup[leafField] : null; + const CriteriaCell = + strategy === RowGroupingStrategy.Default + ? GridGroupingCriteriaCell + : GridDataSourceGroupingCriteriaCell; + // The properties that do not depend on the presence of a `leafColDef` and that can be overridden by `colDefOverride` const commonProperties: Partial = { width: Math.max( @@ -170,7 +192,7 @@ export const createGroupingColDefForOneGroupingCriteria = ({ // Render current grouping criteria groups if (params.rowNode.groupingField === groupingCriteria) { return ( - )} hideDescendantCount={hideDescendantCount} /> @@ -222,7 +244,7 @@ export const createGroupingColDefForOneGroupingCriteria = ({ // The properties that can't be overridden with `colDefOverride` const forcedProperties: Pick = { field: getRowGroupingFieldFromGroupingCriteria(groupingCriteria), - ...GROUPING_COL_DEF_FORCED_PROPERTIES, + ...GROUPING_COL_DEF_FORCED_PROPERTIES_DEFAULT, }; return { @@ -246,6 +268,7 @@ interface CreateGroupingColDefSeveralCriteriaParams { * This value comes `prop.groupingColDef`. */ colDefOverride: GridGroupingColDefOverride | null | undefined; + strategy?: RowGroupingStrategy; } /** @@ -256,11 +279,17 @@ export const createGroupingColDefForAllGroupingCriteria = ({ columnsLookup, rowGroupingModel, colDefOverride, + strategy = RowGroupingStrategy.Default, }: CreateGroupingColDefSeveralCriteriaParams): GridColDef => { const { leafField, mainGroupingCriteria, hideDescendantCount, ...colDefOverrideProperties } = colDefOverride ?? {}; const leafColDef = leafField ? columnsLookup[leafField] : null; + const CriteriaCell = + strategy === RowGroupingStrategy.Default + ? GridGroupingCriteriaCell + : GridDataSourceGroupingCriteriaCell; + // The properties that do not depend on the presence of a `leafColDef` and that can be overridden by `colDefOverride` const commonProperties: Partial = { headerName: apiRef.current.getLocaleText('groupingColumnHeaderName'), @@ -296,7 +325,7 @@ export const createGroupingColDefForAllGroupingCriteria = ({ // Render the groups return ( - )} hideDescendantCount={hideDescendantCount} /> @@ -344,7 +373,9 @@ export const createGroupingColDefForAllGroupingCriteria = ({ // The properties that can't be overridden with `colDefOverride` const forcedProperties: Pick = { field: GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, - ...GROUPING_COL_DEF_FORCED_PROPERTIES, + ...(strategy === RowGroupingStrategy.Default + ? GROUPING_COL_DEF_FORCED_PROPERTIES_DEFAULT + : GROUPING_COL_DEF_FORCED_PROPERTIES_DATA_SOURCE), }; return { 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 1cdec6a7c663..0310fb7b338d 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 @@ -9,12 +9,16 @@ import { GridRowModel, GridColDef, GridKeyValue, + GridDataSource, } from '@mui/x-data-grid-pro'; import { passFilterLogic, GridAggregatedFilterItemApplier, GridAggregatedFilterItemApplierResult, GridColumnRawLookup, + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, + getRowGroupingCriteriaFromGroupingField, + isGroupingColumn, } from '@mui/x-data-grid-pro/internals'; import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; import { @@ -26,9 +30,16 @@ import { GridStatePremium } from '../../../models/gridStatePremium'; import { gridRowGroupingSanitizedModelSelector } from './gridRowGroupingSelector'; import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; -export const GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD = '__row_group_by_columns_group__'; +export { + GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, + getRowGroupingCriteriaFromGroupingField, + isGroupingColumn, +}; -export const ROW_GROUPING_STRATEGY = 'grouping-columns'; +export enum RowGroupingStrategy { + Default = 'grouping-columns', + DataSource = 'grouping-columns-data-source', +} export const getRowGroupingFieldFromGroupingCriteria = (groupingCriteria: string | null) => { if (groupingCriteria === null) { @@ -38,20 +49,6 @@ export const getRowGroupingFieldFromGroupingCriteria = (groupingCriteria: string return `__row_group_by_columns_group_${groupingCriteria}__`; }; -export const getRowGroupingCriteriaFromGroupingField = (groupingColDefField: string) => { - const match = groupingColDefField.match(/^__row_group_by_columns_group_(.*)__$/); - - if (!match) { - return null; - } - - return match[1]; -}; - -export const isGroupingColumn = (field: string) => - field === GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD || - getRowGroupingCriteriaFromGroupingField(field) !== null; - interface FilterRowTreeFromTreeDataParams { rowTree: GridRowTreeConfig; isRowMatchingFilters: GridAggregatedFilterItemApplier | null; @@ -178,10 +175,11 @@ export const filterRowTreeFromGroupingColumns = ( export const getColDefOverrides = ( groupingColDefProp: DataGridPremiumProcessedProps['groupingColDef'], fields: string[], + strategy?: RowGroupingStrategy, ) => { if (typeof groupingColDefProp === 'function') { return groupingColDefProp({ - groupingName: ROW_GROUPING_STRATEGY, + groupingName: strategy ?? RowGroupingStrategy.Default, fields, }); } @@ -199,6 +197,7 @@ export const mergeStateWithRowGroupingModel = export const setStrategyAvailability = ( privateApiRef: React.MutableRefObject, disableRowGrouping: boolean, + dataSource?: GridDataSource, ) => { let isAvailable: () => boolean; if (disableRowGrouping) { @@ -210,7 +209,9 @@ export const setStrategyAvailability = ( }; } - privateApiRef.current.setStrategyAvailability('rowTree', ROW_GROUPING_STRATEGY, isAvailable); + const strategy = dataSource ? RowGroupingStrategy.DataSource : RowGroupingStrategy.Default; + + privateApiRef.current.setStrategyAvailability('rowTree', strategy, isAvailable); }; export const getCellGroupingCriteria = ({ diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts new file mode 100644 index 000000000000..07ad4690d059 --- /dev/null +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridDataSourceRowGroupingPreProcessors.ts @@ -0,0 +1,134 @@ +import * as React from 'react'; +import { GridRowId, gridRowTreeSelector, gridColumnLookupSelector } from '@mui/x-data-grid-pro'; +import { + GridStrategyProcessor, + useGridRegisterStrategyProcessor, + createRowTree, + updateRowTree, + getVisibleRowsLookup, + skipSorting, + skipFiltering, + GridRowsPartialUpdates, +} from '@mui/x-data-grid-pro/internals'; +import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; +import { getGroupingRules, RowGroupingStrategy } from './gridRowGroupingUtils'; +import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; +import { gridRowGroupingSanitizedModelSelector } from './gridRowGroupingSelector'; + +export const useGridDataSourceRowGroupingPreProcessors = ( + apiRef: React.MutableRefObject, + props: Pick< + DataGridPremiumProcessedProps, + | 'disableRowGrouping' + | 'groupingColDef' + | 'rowGroupingColumnMode' + | 'defaultGroupingExpansionDepth' + | 'isGroupExpandedByDefault' + | 'unstable_dataSource' + >, +) => { + const createRowTreeForRowGrouping = React.useCallback>( + (params) => { + const getGroupKey = props.unstable_dataSource?.getGroupKey; + if (!getGroupKey) { + throw new Error('MUI X: No `getGroupKey` method provided with the dataSource.'); + } + + const getChildrenCount = props.unstable_dataSource?.getChildrenCount; + if (!getChildrenCount) { + throw new Error('MUI X: No `getChildrenCount` method provided with the dataSource.'); + } + + const sanitizedRowGroupingModel = gridRowGroupingSanitizedModelSelector(apiRef); + const columnsLookup = gridColumnLookupSelector(apiRef); + const groupingRules = getGroupingRules({ + sanitizedRowGroupingModel, + columnsLookup, + }); + apiRef.current.caches.rowGrouping.rulesOnLastRowTreeCreation = groupingRules; + + const getRowTreeBuilderNode = (rowId: GridRowId) => { + const parentPath = (params.updates as GridRowsPartialUpdates).groupKeys ?? []; + const row = params.dataRowIdToModelLookup[rowId]; + const groupingRule = groupingRules[parentPath.length]; + const groupingValueGetter = groupingRule?.groupingValueGetter; + const leafKey = + groupingValueGetter?.( + row[groupingRule.field] as never, + row, + columnsLookup[groupingRule.field], + apiRef, + ) ?? getGroupKey(params.dataRowIdToModelLookup[rowId]); + return { + id: rowId, + path: [...parentPath, leafKey ?? rowId.toString()].map((key, i) => ({ + key, + field: groupingRules[i]?.field ?? null, + })), + serverChildrenCount: getChildrenCount(params.dataRowIdToModelLookup[rowId]) ?? 0, + }; + }; + + if (params.updates.type === 'full') { + return createRowTree({ + previousTree: params.previousTree, + nodes: params.updates.rows.map(getRowTreeBuilderNode), + defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, + isGroupExpandedByDefault: props.isGroupExpandedByDefault, + groupingName: RowGroupingStrategy.DataSource, + }); + } + + return updateRowTree({ + nodes: { + inserted: (params.updates as GridRowsPartialUpdates).actions.insert.map( + getRowTreeBuilderNode, + ), + modified: (params.updates as GridRowsPartialUpdates).actions.modify.map( + getRowTreeBuilderNode, + ), + removed: (params.updates as GridRowsPartialUpdates).actions.remove, + }, + previousTree: params.previousTree!, + previousGroupsToFetch: params.previousGroupsToFetch, + previousTreeDepth: params.previousTreeDepths!, + defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, + isGroupExpandedByDefault: props.isGroupExpandedByDefault, + groupingName: RowGroupingStrategy.DataSource, + }); + }, + [ + apiRef, + props.unstable_dataSource, + props.defaultGroupingExpansionDepth, + props.isGroupExpandedByDefault, + ], + ); + + const filterRows = React.useCallback>(() => { + const rowTree = gridRowTreeSelector(apiRef); + + return skipFiltering(rowTree); + }, [apiRef]); + + const sortRows = React.useCallback>(() => { + const rowTree = gridRowTreeSelector(apiRef); + + return skipSorting(rowTree); + }, [apiRef]); + + useGridRegisterStrategyProcessor( + apiRef, + RowGroupingStrategy.DataSource, + 'rowTreeCreation', + createRowTreeForRowGrouping, + ); + useGridRegisterStrategyProcessor(apiRef, RowGroupingStrategy.DataSource, 'filtering', filterRows); + useGridRegisterStrategyProcessor(apiRef, RowGroupingStrategy.DataSource, 'sorting', sortRows); + useGridRegisterStrategyProcessor( + apiRef, + RowGroupingStrategy.DataSource, + 'visibleRowsLookupCreation', + getVisibleRowsLookup, + ); +}; diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx index b779e23a2064..fe562ab900c3 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGrouping.tsx @@ -19,7 +19,7 @@ import { import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; import { getRowGroupingFieldFromGroupingCriteria, - ROW_GROUPING_STRATEGY, + RowGroupingStrategy, isGroupingColumn, mergeStateWithRowGroupingModel, setStrategyAvailability, @@ -63,6 +63,7 @@ export const useGridRowGrouping = ( | 'disableRowGrouping' | 'slotProps' | 'slots' + | 'unstable_dataSource' >, ) => { apiRef.current.registerControlState({ @@ -73,7 +74,7 @@ export const useGridRowGrouping = ( changeEvent: 'rowGroupingModelChange', }); - /** + /* * API METHODS */ const setRowGroupingModel = React.useCallback( @@ -165,6 +166,16 @@ export const useGridRowGrouping = ( [props.disableRowGrouping], ); + const addGetRowsParams = React.useCallback>( + (params) => { + return { + ...params, + groupFields: gridRowGroupingModelSelector(apiRef), + }; + }, + [apiRef], + ); + const stateExportPreProcessing = React.useCallback>( (prevState, context) => { const rowGroupingModelToExport = gridRowGroupingModelSelector(apiRef); @@ -209,10 +220,11 @@ export const useGridRowGrouping = ( ); useGridRegisterPipeProcessor(apiRef, 'columnMenu', addColumnMenuButtons); + useGridRegisterPipeProcessor(apiRef, 'getRowsParams', addGetRowsParams); useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing); useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing); - /** + /* * EVENTS */ const handleCellKeyDown = React.useCallback>( @@ -233,10 +245,15 @@ export const useGridRowGrouping = ( return; } + if (props.unstable_dataSource && !params.rowNode.childrenExpanded) { + apiRef.current.unstable_dataSource.fetchRows(params.id); + return; + } + apiRef.current.setRowChildrenExpansion(params.id, !params.rowNode.childrenExpanded); } }, - [apiRef, props.rowGroupingColumnMode], + [apiRef, props.rowGroupingColumnMode, props.unstable_dataSource], ); const checkGroupingColumnsModelDiff = React.useCallback< @@ -258,7 +275,7 @@ export const useGridRowGrouping = ( // Refresh the row tree creation strategy processing // TODO: Add a clean way to re-run a strategy processing without publishing a private event - if (apiRef.current.getActiveStrategy('rowTree') === ROW_GROUPING_STRATEGY) { + if (apiRef.current.getActiveStrategy('rowTree') === RowGroupingStrategy.Default) { apiRef.current.publishEvent('activeStrategyProcessorChange', 'rowTreeCreation'); } } @@ -267,8 +284,11 @@ export const useGridRowGrouping = ( useGridApiEventHandler(apiRef, 'cellKeyDown', handleCellKeyDown); useGridApiEventHandler(apiRef, 'columnsChange', checkGroupingColumnsModelDiff); useGridApiEventHandler(apiRef, 'rowGroupingModelChange', checkGroupingColumnsModelDiff); + useGridApiEventHandler(apiRef, 'rowGroupingModelChange', () => + apiRef.current.unstable_dataSource.fetchRows(), + ); - /** + /* * EFFECTS */ React.useEffect(() => { diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts index 2fa403ec72d5..ac43f038695a 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/useGridRowGroupingPreProcessors.ts @@ -31,7 +31,7 @@ import { import { filterRowTreeFromGroupingColumns, getColDefOverrides, - ROW_GROUPING_STRATEGY, + RowGroupingStrategy, isGroupingColumn, setStrategyAvailability, getCellGroupingCriteria, @@ -48,6 +48,7 @@ export const useGridRowGroupingPreProcessors = ( | 'rowGroupingColumnMode' | 'defaultGroupingExpansionDepth' | 'isGroupExpandedByDefault' + | 'unstable_dataSource' >, ) => { const getGroupingColDefs = React.useCallback( @@ -56,6 +57,10 @@ export const useGridRowGroupingPreProcessors = ( return []; } + const strategy = props.unstable_dataSource + ? RowGroupingStrategy.DataSource + : RowGroupingStrategy.Default; + const groupingColDefProp = props.groupingColDef; // We can't use `gridGroupingRowsSanitizedModelSelector` here because the new columns are not in the state yet @@ -73,8 +78,9 @@ export const useGridRowGroupingPreProcessors = ( createGroupingColDefForAllGroupingCriteria({ apiRef, rowGroupingModel, - colDefOverride: getColDefOverrides(groupingColDefProp, rowGroupingModel), + colDefOverride: getColDefOverrides(groupingColDefProp, rowGroupingModel, strategy), columnsLookup: columnsState.lookup, + strategy, }), ]; } @@ -86,6 +92,7 @@ export const useGridRowGroupingPreProcessors = ( colDefOverride: getColDefOverrides(groupingColDefProp, [groupingCriteria]), groupedByColDef: columnsState.lookup[groupingCriteria], columnsLookup: columnsState.lookup, + strategy, }), ); } @@ -95,7 +102,13 @@ export const useGridRowGroupingPreProcessors = ( } } }, - [apiRef, props.groupingColDef, props.rowGroupingColumnMode, props.disableRowGrouping], + [ + apiRef, + props.groupingColDef, + props.rowGroupingColumnMode, + props.disableRowGrouping, + props.unstable_dataSource, + ], ); const updateGroupingColumn = React.useCallback>( @@ -177,7 +190,7 @@ export const useGridRowGroupingPreProcessors = ( nodes: params.updates.rows.map(getRowTreeBuilderNode), defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: ROW_GROUPING_STRATEGY, + groupingName: RowGroupingStrategy.Default, }); } @@ -191,7 +204,7 @@ export const useGridRowGroupingPreProcessors = ( previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: ROW_GROUPING_STRATEGY, + groupingName: RowGroupingStrategy.Default, }); }, [apiRef, props.defaultGroupingExpansionDepth, props.isGroupExpandedByDefault], @@ -228,35 +241,29 @@ export const useGridRowGroupingPreProcessors = ( useGridRegisterPipeProcessor(apiRef, 'hydrateColumns', updateGroupingColumn); useGridRegisterStrategyProcessor( apiRef, - ROW_GROUPING_STRATEGY, + RowGroupingStrategy.Default, 'rowTreeCreation', createRowTreeForRowGrouping, ); - useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY, 'filtering', filterRows); - useGridRegisterStrategyProcessor(apiRef, ROW_GROUPING_STRATEGY, 'sorting', sortRows); + useGridRegisterStrategyProcessor(apiRef, RowGroupingStrategy.Default, 'filtering', filterRows); + useGridRegisterStrategyProcessor(apiRef, RowGroupingStrategy.Default, 'sorting', sortRows); useGridRegisterStrategyProcessor( apiRef, - ROW_GROUPING_STRATEGY, + RowGroupingStrategy.Default, 'visibleRowsLookupCreation', getVisibleRowsLookup, ); - /** - * 1ST RENDER - */ useFirstRender(() => { - setStrategyAvailability(apiRef, props.disableRowGrouping); + setStrategyAvailability(apiRef, props.disableRowGrouping, props.unstable_dataSource); }); - /** - * EFFECTS - */ const isFirstRender = React.useRef(true); React.useEffect(() => { if (!isFirstRender.current) { - setStrategyAvailability(apiRef, props.disableRowGrouping); + setStrategyAvailability(apiRef, props.disableRowGrouping, props.unstable_dataSource); } else { isFirstRender.current = false; } - }, [apiRef, props.disableRowGrouping]); + }, [apiRef, props.disableRowGrouping, props.unstable_dataSource]); }; diff --git a/packages/x-data-grid-pro/package.json b/packages/x-data-grid-pro/package.json index fb2a91f52a6c..8810d0971ce6 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.20.0", + "version": "7.21.0", "description": "The Pro plan edition of the Data Grid components (MUI X).", "author": "MUI Team", "main": "src/index.ts", diff --git a/packages/x-data-grid-pro/src/components/GridColumnMenuPinningItem.tsx b/packages/x-data-grid-pro/src/components/GridColumnMenuPinningItem.tsx index 0a07624dc306..89168d11fa8d 100644 --- a/packages/x-data-grid-pro/src/components/GridColumnMenuPinningItem.tsx +++ b/packages/x-data-grid-pro/src/components/GridColumnMenuPinningItem.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { useRtl } from '@mui/system/RtlProvider'; import PropTypes from 'prop-types'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { GridPinnedColumnPosition, GridColumnMenuItemProps } from '@mui/x-data-grid'; @@ -27,21 +26,21 @@ function GridColumnMenuPinningItem(props: GridColumnMenuItemProps) { onClick(event); }; const pinToLeftMenuItem = ( - + {apiRef.current.getLocaleText('pinToLeft')} - + ); const pinToRightMenuItem = ( - + {apiRef.current.getLocaleText('pinToRight')} - + ); if (!colDef) { @@ -62,16 +61,16 @@ function GridColumnMenuPinningItem(props: GridColumnMenuItemProps) { : rootProps.slots.columnMenuPinRightIcon; return ( - + {apiRef.current.getLocaleText(label)} - - + + {apiRef.current.getLocaleText('unpin')} - + ); } diff --git a/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx index 79e002211adf..d5ee7aa79ab5 100644 --- a/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridDataSourceTreeDataGroupingCell.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import composeClasses from '@mui/utils/composeClasses'; import Box from '@mui/material/Box'; -import Badge from '@mui/material/Badge'; import { getDataGridUtilityClass, GridRenderCellParams, @@ -93,9 +92,9 @@ function GridTreeDataGroupingCellIcon(props: GridTreeDataGroupingCellIconProps) {...rootProps?.slotProps?.baseIconButton} > - + - + ) : null; diff --git a/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterMenu.tsx b/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterMenu.tsx index 9c049130ee1a..c3fe31d4aea7 100644 --- a/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterMenu.tsx +++ b/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterMenu.tsx @@ -1,9 +1,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import MenuList from '@mui/material/MenuList'; -import MenuItem from '@mui/material/MenuItem'; import { unstable_capitalize as capitalize, HTMLElementType } from '@mui/utils'; import { + useGridRootProps, useGridApiContext, GridMenu, GridFilterOperator, @@ -33,6 +32,7 @@ function GridHeaderFilterMenu({ labelledBy, }: GridHeaderFilterMenuProps) { const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); const hideMenu = React.useCallback(() => { apiRef.current.hideHeaderFilterMenu(); @@ -56,7 +56,11 @@ function GridHeaderFilterMenu({ return ( - + {operators.map((op, i) => { const label = op?.headerLabel ?? @@ -65,7 +69,7 @@ function GridHeaderFilterMenu({ ); return ( - { applyFilterChanges({ ...item, operator: op.value }); hideMenu(); @@ -75,10 +79,10 @@ function GridHeaderFilterMenu({ key={`${field}-${op.value}`} > {label} - + ); })} - + ); } diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts index dde8cad3d39f..5645235abf01 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts @@ -15,6 +15,7 @@ function getKey(params: GridGetRowsParams) { params.filterModel, params.sortModel, params.groupKeys, + params.groupFields, ]); } 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 6b6aa5a9404d..09e2d34c9099 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 @@ -21,8 +21,6 @@ export const gridGetRowsParamsSelector = createSelector( (filterModel, sortModel, paginationModel) => { return { groupKeys: [], - // TODO: Implement with `rowGrouping` - groupFields: [], paginationModel, sortModel, filterModel, diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts index 4e365d10d7ee..31f3d209a695 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSource.ts @@ -85,7 +85,10 @@ export const useGridDataSource = ( apiRef.current.resetDataSourceState(); } - const fetchParams = gridGetRowsParamsSelector(apiRef); + const fetchParams = { + ...gridGetRowsParamsSelector(apiRef), + ...apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}), + }; const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); @@ -122,7 +125,8 @@ export const useGridDataSource = ( const fetchRowChildren = React.useCallback( async (id) => { - if (!props.treeData) { + const pipedParams = apiRef.current.unstable_applyPipeProcessors('getRowsParams', {}); + if (!props.treeData && (pipedParams.groupFields?.length ?? 0) === 0) { nestedDataManager.clearPendingRequest(id); return; } @@ -138,7 +142,11 @@ export const useGridDataSource = ( return; } - const fetchParams = { ...gridGetRowsParamsSelector(apiRef), groupKeys: rowNode.path }; + const fetchParams = { + ...gridGetRowsParamsSelector(apiRef), + ...pipedParams, + groupKeys: rowNode.path, + }; const cachedData = apiRef.current.unstable_dataSource.cache.get(fetchParams); diff --git a/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelToggleColDef.tsx b/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelToggleColDef.tsx index 76e80c840299..16998e55615f 100644 --- a/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelToggleColDef.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelToggleColDef.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; import { GRID_STRING_COL_DEF, GridColDef } from '@mui/x-data-grid'; +import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '@mui/x-data-grid/internals'; import { GridApiPro } from '../../../models/gridApiPro'; import { GridDetailPanelToggleCell } from '../../../components/GridDetailPanelToggleCell'; import { gridDetailPanelExpandedRowIdsSelector } from './gridDetailPanelSelector'; -export const GRID_DETAIL_PANEL_TOGGLE_FIELD = '__detail_panel_toggle__'; +export { GRID_DETAIL_PANEL_TOGGLE_FIELD }; export const GRID_DETAIL_PANEL_TOGGLE_COL_DEF: GridColDef = { ...GRID_STRING_COL_DEF, 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 c09bc45b8ded..0c6b841f939d 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 @@ -34,8 +34,7 @@ import { } from '../../../utils/tree/models'; import { updateRowTree } from '../../../utils/tree/updateRowTree'; import { getVisibleRowsLookup } from '../../../utils/tree/utils'; - -const DATA_SOURCE_TREE_DATA_STRATEGY = 'dataSourceTreeData'; +import { TreeDataStrategy } from '../treeData/gridTreeDataUtils'; export const useGridDataSourceTreeDataPreProcessors = ( privateApiRef: React.MutableRefObject, @@ -53,7 +52,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( const setStrategyAvailability = React.useCallback(() => { privateApiRef.current.setStrategyAvailability( 'rowTree', - DATA_SOURCE_TREE_DATA_STRATEGY, + TreeDataStrategy.DataSource, props.treeData && props.unstable_dataSource ? () => true : () => false, ); }, [privateApiRef, props.treeData, props.unstable_dataSource]); @@ -64,7 +63,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( let colDefOverride: GridGroupingColDefOverride | null | undefined; if (typeof groupingColDefProp === 'function') { const params: GridGroupingColDefOverrideParams = { - groupingName: DATA_SOURCE_TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.DataSource, fields: [], }; @@ -171,7 +170,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( nodes: params.updates.rows.map(getRowTreeBuilderNode), defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: DATA_SOURCE_TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.DataSource, onDuplicatePath, }); } @@ -187,7 +186,7 @@ export const useGridDataSourceTreeDataPreProcessors = ( previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: DATA_SOURCE_TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.DataSource, }); }, [ @@ -212,25 +211,20 @@ export const useGridDataSourceTreeDataPreProcessors = ( useGridRegisterPipeProcessor(privateApiRef, 'hydrateColumns', updateGroupingColumn); useGridRegisterStrategyProcessor( privateApiRef, - DATA_SOURCE_TREE_DATA_STRATEGY, + TreeDataStrategy.DataSource, 'rowTreeCreation', createRowTreeForTreeData, ); useGridRegisterStrategyProcessor( privateApiRef, - DATA_SOURCE_TREE_DATA_STRATEGY, + TreeDataStrategy.DataSource, 'filtering', filterRows, ); + useGridRegisterStrategyProcessor(privateApiRef, TreeDataStrategy.DataSource, 'sorting', sortRows); useGridRegisterStrategyProcessor( privateApiRef, - DATA_SOURCE_TREE_DATA_STRATEGY, - 'sorting', - sortRows, - ); - useGridRegisterStrategyProcessor( - privateApiRef, - DATA_SOURCE_TREE_DATA_STRATEGY, + TreeDataStrategy.DataSource, 'visibleRowsLookupCreation', getVisibleRowsLookup, ); diff --git a/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataGroupColDef.ts b/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataGroupColDef.ts index 4fe1addd677f..f9f27fdd5d74 100644 --- a/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataGroupColDef.ts +++ b/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataGroupColDef.ts @@ -1,4 +1,5 @@ import { GRID_STRING_COL_DEF, GridColDef } from '@mui/x-data-grid'; +import { GRID_TREE_DATA_GROUPING_FIELD } from '@mui/x-data-grid/internals'; /** * TODO: Add sorting and filtering on the value and the filteredDescendantCount @@ -19,7 +20,7 @@ export const GRID_TREE_DATA_GROUPING_COL_DEF: Omit; } -export const TREE_DATA_STRATEGY = 'tree-data'; +export enum TreeDataStrategy { + Default = 'tree-data', + DataSource = 'tree-data-source', +} /** * A node is visible if one of the following criteria is met: diff --git a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx index 74c0fcfcda38..6a5753c9eb60 100644 --- a/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/treeData/useGridTreeDataPreProcessors.tsx @@ -19,7 +19,7 @@ import { GRID_TREE_DATA_GROUPING_COL_DEF_FORCED_PROPERTIES, } from './gridTreeDataGroupColDef'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { filterRowTreeFromTreeData, TREE_DATA_STRATEGY } from './gridTreeDataUtils'; +import { filterRowTreeFromTreeData, TreeDataStrategy } from './gridTreeDataUtils'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { GridGroupingColDefOverride, @@ -52,7 +52,7 @@ export const useGridTreeDataPreProcessors = ( const setStrategyAvailability = React.useCallback(() => { privateApiRef.current.setStrategyAvailability( 'rowTree', - TREE_DATA_STRATEGY, + TreeDataStrategy.Default, props.treeData && !props.unstable_dataSource ? () => true : () => false, ); }, [privateApiRef, props.treeData, props.unstable_dataSource]); @@ -63,7 +63,7 @@ export const useGridTreeDataPreProcessors = ( let colDefOverride: GridGroupingColDefOverride | null | undefined; if (typeof groupingColDefProp === 'function') { const params: GridGroupingColDefOverrideParams = { - groupingName: TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.Default, fields: [], }; @@ -158,7 +158,7 @@ export const useGridTreeDataPreProcessors = ( nodes: params.updates.rows.map(getRowTreeBuilderNode), defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.Default, onDuplicatePath, }); } @@ -173,7 +173,7 @@ export const useGridTreeDataPreProcessors = ( previousTreeDepth: params.previousTreeDepths!, defaultGroupingExpansionDepth: props.defaultGroupingExpansionDepth, isGroupExpandedByDefault: props.isGroupExpandedByDefault, - groupingName: TREE_DATA_STRATEGY, + groupingName: TreeDataStrategy.Default, }); }, [props.getTreeDataPath, props.defaultGroupingExpansionDepth, props.isGroupExpandedByDefault], @@ -211,15 +211,20 @@ export const useGridTreeDataPreProcessors = ( useGridRegisterPipeProcessor(privateApiRef, 'hydrateColumns', updateGroupingColumn); useGridRegisterStrategyProcessor( privateApiRef, - TREE_DATA_STRATEGY, + TreeDataStrategy.Default, 'rowTreeCreation', createRowTreeForTreeData, ); - useGridRegisterStrategyProcessor(privateApiRef, TREE_DATA_STRATEGY, 'filtering', filterRows); - useGridRegisterStrategyProcessor(privateApiRef, TREE_DATA_STRATEGY, 'sorting', sortRows); useGridRegisterStrategyProcessor( privateApiRef, - TREE_DATA_STRATEGY, + TreeDataStrategy.Default, + 'filtering', + filterRows, + ); + useGridRegisterStrategyProcessor(privateApiRef, TreeDataStrategy.Default, 'sorting', sortRows); + useGridRegisterStrategyProcessor( + privateApiRef, + TreeDataStrategy.Default, 'visibleRowsLookupCreation', getVisibleRowsLookup, ); diff --git a/packages/x-data-grid-pro/src/internals/index.ts b/packages/x-data-grid-pro/src/internals/index.ts index c0de9e27064b..ed619bb0cc9e 100644 --- a/packages/x-data-grid-pro/src/internals/index.ts +++ b/packages/x-data-grid-pro/src/internals/index.ts @@ -33,7 +33,6 @@ export { useGridRowReorder } from '../hooks/features/rowReorder/useGridRowReorde export { useGridRowReorderPreProcessors } from '../hooks/features/rowReorder/useGridRowReorderPreProcessors'; export { useGridTreeData } from '../hooks/features/treeData/useGridTreeData'; export { useGridTreeDataPreProcessors } from '../hooks/features/treeData/useGridTreeDataPreProcessors'; -export { TREE_DATA_STRATEGY } from '../hooks/features/treeData/gridTreeDataUtils'; export { useGridRowPinning, rowPinningStateInitializer, @@ -61,4 +60,6 @@ export { sortRowTree } from '../utils/tree/sortRowTree'; export { insertNodeInTree, removeNodeFromTree, getVisibleRowsLookup } from '../utils/tree/utils'; export type { RowTreeBuilderGroupingCriterion } from '../utils/tree/models'; +export { skipSorting, skipFiltering } from '../hooks/features/serverSideTreeData/utils'; + export * from './propValidation'; 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 1f71824e507f..b8e38ff9df4a 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 @@ -86,10 +86,10 @@ describe(' - Detail panel', () => { await microtasks(); const virtualScrollerContent = $('.MuiDataGrid-virtualScrollerContent')!; - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + detailPanelHeight}px`, }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); const detailPanels = $$('.MuiDataGrid-detailPanel'); expect(detailPanels[0]).toHaveComputedStyle({ @@ -128,11 +128,9 @@ describe(' - Detail panel', () => { }); await waitFor(() => { - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', - height: `${rowHeight + 100}px`, - }); + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + 100}px` }); }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); const detailPanels = $$('.MuiDataGrid-detailPanel'); expect(detailPanels[0]).toHaveComputedStyle({ @@ -142,11 +140,9 @@ describe(' - Detail panel', () => { fireEvent.click(screen.getByRole('button', { name: 'Increase' })); await waitFor(() => { - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', - height: `${rowHeight + 200}px`, - }); + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + 200}px` }); }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); expect(detailPanels[0]).toHaveComputedStyle({ height: `200px`, diff --git a/packages/x-data-grid/package.json b/packages/x-data-grid/package.json index 753e36dc322b..1076a0532ca3 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.20.0", + "version": "7.21.0", "description": "The Community plan edition of the Data Grid components (MUI X).", "author": "MUI Team", "main": "src/index.ts", diff --git a/packages/x-data-grid/src/components/GridRow.tsx b/packages/x-data-grid/src/components/GridRow.tsx index 8358424a82a7..c3447c435a7a 100644 --- a/packages/x-data-grid/src/components/GridRow.tsx +++ b/packages/x-data-grid/src/components/GridRow.tsx @@ -19,7 +19,7 @@ import { useGridVisibleRows } from '../hooks/utils/useGridVisibleRows'; import { findParentElementFromClassName, isEventTargetInPortal } from '../utils/domUtils'; import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../colDef/gridCheckboxSelectionColDef'; import { GRID_ACTIONS_COLUMN_TYPE } from '../colDef/gridActionsColDef'; -import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../constants/gridDetailPanelToggleField'; +import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../internals/constants'; import type { GridDimensions } from '../hooks/features/dimensions'; import { gridSortModelSelector } from '../hooks/features/sorting/gridSortingSelector'; import { gridRowMaximumTreeDepthSelector } from '../hooks/features/rows/gridRowsSelector'; diff --git a/packages/x-data-grid/src/components/cell/GridActionsCell.tsx b/packages/x-data-grid/src/components/cell/GridActionsCell.tsx index 39c20b5c639e..5707b3939a5c 100644 --- a/packages/x-data-grid/src/components/cell/GridActionsCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridActionsCell.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import MenuList from '@mui/material/MenuList'; import { useRtl } from '@mui/system/RtlProvider'; import { unstable_useId as useId } from '@mui/utils'; import { GridRenderCellParams } from '../../models/params/gridCellParams'; @@ -224,7 +223,7 @@ function GridActionsCell(props: GridActionsCellProps) { {menuButtons.length > 0 && ( - React.cloneElement(button, { key: index, closeMenu: hideMenu }), )} - + )} diff --git a/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx b/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx index 3d39805c11f0..2c0edaec52bb 100644 --- a/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx +++ b/packages/x-data-grid/src/components/cell/GridActionsCellItem.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { IconButtonProps } from '@mui/material/IconButton'; -import MenuItem, { MenuItemProps } from '@mui/material/MenuItem'; +import { MenuItemProps } from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; @@ -70,10 +70,10 @@ const GridActionsCellItem = React.forwardRef + {icon && {icon}} {label} - + ); }, ); diff --git a/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx b/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx index 543e199229c2..7bdd414d0ff8 100644 --- a/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridBooleanCell.tsx @@ -2,13 +2,16 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { SvgIconProps } from '@mui/material/SvgIcon'; import composeClasses from '@mui/utils/composeClasses'; +import { useGridSelector } from '../../hooks/utils/useGridSelector'; +import { gridRowMaximumTreeDepthSelector } from '../../hooks/features/rows/gridRowsSelector'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; -import { GridRenderCellParams } from '../../models/params/gridCellParams'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; -import { DataGridProcessedProps } from '../../models/props/DataGridProps'; -import { GridColDef } from '../../models/colDef/gridColDef'; import { isAutogeneratedRowNode } from '../../hooks/features/rows/gridRowsUtils'; +import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; +import type { GridColDef } from '../../models/colDef/gridColDef'; +import type { GridRenderCellParams } from '../../models/params/gridCellParams'; +import { GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD } from '../../internals/constants'; type OwnerState = { classes: DataGridProcessedProps['classes'] }; @@ -49,11 +52,20 @@ function GridBooleanCellRaw(props: GridBooleanCellProps) { const ownerState = { classes: rootProps.classes }; const classes = useUtilityClasses(ownerState); + const maxDepth = useGridSelector(apiRef, gridRowMaximumTreeDepthSelector); + const isServerSideRowGroupingRow = + // @ts-expect-error - Access tree data prop + maxDepth > 0 && rowNode.type === 'group' && rootProps.treeData === false; + const Icon = React.useMemo( () => (value ? rootProps.slots.booleanCellTrueIcon : rootProps.slots.booleanCellFalseIcon), [rootProps.slots.booleanCellFalseIcon, rootProps.slots.booleanCellTrueIcon, value], ); + if (isServerSideRowGroupingRow && value === undefined) { + return null; + } + return ( { - if (params.field !== '__row_group_by_columns_group__' && isAutogeneratedRowNode(params.rowNode)) { + if ( + params.field !== GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD && + isAutogeneratedRowNode(params.rowNode) + ) { return ''; } diff --git a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx index d48bc0ccdfe2..92641c45e46e 100644 --- a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderFilterIconButton.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { unstable_composeClasses as composeClasses, unstable_useId as useId } from '@mui/utils'; -import Badge from '@mui/material/Badge'; import { useGridSelector } from '../../hooks'; import { gridPreferencePanelStateSelector } from '../../hooks/features/preferencesPanel/gridPreferencePanelSelector'; import { GridPreferencePanelsValue } from '../../hooks/features/preferencesPanel/gridPreferencePanelsValue'; @@ -97,9 +96,9 @@ function GridColumnHeaderFilterIconButton(props: ColumnHeaderFilterIconButtonPro > {counter > 1 && ( - + {iconButton} - + )} {counter === 1 && iconButton} diff --git a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderSortIcon.tsx b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderSortIcon.tsx index 367931b7e3c9..778579bcc0e3 100644 --- a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderSortIcon.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderSortIcon.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import composeClasses from '@mui/utils/composeClasses'; -import Badge from '@mui/material/Badge'; import { GridSlotsComponent } from '../../models/gridSlotsComponent'; import { GridSortDirection } from '../../models/gridSortModel'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; @@ -80,9 +79,9 @@ function GridColumnHeaderSortIconRaw(props: GridColumnHeaderSortIconProps) { return ( {index != null && ( - + {iconButton} - + )} {index == null && iconButton} diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts index 86b93de0962d..84cb0ec975eb 100644 --- a/packages/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/x-data-grid/src/components/containers/GridRootStyles.ts @@ -130,6 +130,9 @@ export const GridRootStyles = styled('div', { { [`& .${c.treeDataGroupingCellLoadingContainer}`]: styles.treeDataGroupingCellLoadingContainer, }, + { + [`& .${c.groupingCriteriaCellLoadingContainer}`]: styles.groupingCriteriaCellLoadingContainer, + }, { [`& .${c.detailPanelToggleCell}`]: styles.detailPanelToggleCell }, { [`& .${c['detailPanelToggleCell--expanded']}`]: styles['detailPanelToggleCell--expanded'], @@ -708,7 +711,7 @@ export const GridRootStyles = styled('div', { alignSelf: 'stretch', marginRight: t.spacing(2), }, - [`& .${c.treeDataGroupingCellLoadingContainer}`]: { + [`& .${c.treeDataGroupingCellLoadingContainer}, .${c.groupingCriteriaCellLoadingContainer}`]: { display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -743,7 +746,7 @@ export const GridRootStyles = styled('div', { }, [`& .${c.filler}`]: { - flex: 1, + flex: '1 0 auto', }, [`& .${c['filler--borderBottom']}`]: { borderBottom: '1px solid var(--DataGrid-rowBorderColor)', diff --git a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuFilterItem.tsx b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuFilterItem.tsx index d86f61e78098..f7db300a230c 100644 --- a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuFilterItem.tsx +++ b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuFilterItem.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { useGridApiContext } from '../../../../hooks/utils/useGridApiContext'; @@ -25,12 +24,12 @@ function GridColumnMenuFilterItem(props: GridColumnMenuItemProps) { } return ( - + {apiRef.current.getLocaleText('columnMenuFilter')} - + ); } diff --git a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuHideItem.tsx b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuHideItem.tsx index ce7660929312..f6594765ba05 100644 --- a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuHideItem.tsx +++ b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuHideItem.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { GridColumnMenuItemProps } from '../GridColumnMenuItemProps'; @@ -43,12 +42,12 @@ function GridColumnMenuHideItem(props: GridColumnMenuItemProps) { } return ( - + {apiRef.current.getLocaleText('columnMenuHideColumn')} - + ); } diff --git a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuManageItem.tsx b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuManageItem.tsx index 860751420dda..df95549cbf30 100644 --- a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuManageItem.tsx +++ b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuManageItem.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { GridPreferencePanelsValue } from '../../../../hooks/features/preferencesPanel/gridPreferencePanelsValue'; @@ -26,12 +25,12 @@ function GridColumnMenuManageItem(props: GridColumnMenuItemProps) { } return ( - + {apiRef.current.getLocaleText('columnMenuManageColumns')} - + ); } diff --git a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuSortItem.tsx b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuSortItem.tsx index de67a637774e..7ac4c77444da 100644 --- a/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuSortItem.tsx +++ b/packages/x-data-grid/src/components/menu/columnMenu/menuItems/GridColumnMenuSortItem.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { useGridSelector } from '../../../../hooks/utils/useGridSelector'; @@ -55,26 +54,26 @@ function GridColumnMenuSortItem(props: GridColumnMenuItemProps) { return ( {sortingOrder.includes('asc') && sortDirection !== 'asc' ? ( - + {getLabel('columnMenuSortAsc')} - + ) : null} {sortingOrder.includes('desc') && sortDirection !== 'desc' ? ( - + {getLabel('columnMenuSortDesc')} - + ) : null} {sortingOrder.includes(null) && sortDirection != null ? ( - + {apiRef.current.getLocaleText('columnMenuUnsort')} - + ) : null} ); diff --git a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputBoolean.tsx b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputBoolean.tsx index 89d0a2114e33..faf133512296 100644 --- a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputBoolean.tsx +++ b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputBoolean.tsx @@ -54,7 +54,7 @@ function GridFilterInputBoolean(props: GridFilterInputBooleanProps) { (event: React.ChangeEvent) => { const value = event.target.value; setFilterValueState(value); - applyValue({ ...item, value }); + applyValue({ ...item, value: Boolean(value) }); }, [applyValue, item], ); diff --git a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleValue.tsx b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleValue.tsx index 9fc87274ac2b..50da622479b8 100644 --- a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleValue.tsx +++ b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputMultipleValue.tsx @@ -47,9 +47,17 @@ function GridFilterInputMultipleValue(props: GridFilterInputMultipleValueProps) >( (event, value) => { setFilterValueState(value.map(String)); - applyValue({ ...item, value: [...value] }); + + applyValue({ + ...item, + value: [ + ...value.map((filterItemValue) => + type === 'number' ? Number(filterItemValue) : filterItemValue, + ), + ], + }); }, - [applyValue, item], + [applyValue, item, type], ); return ( diff --git a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx index a16cc1a850dd..4391eed4166d 100644 --- a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx +++ b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputValue.tsx @@ -48,12 +48,16 @@ function GridFilterInputValue(props: GridTypeFilterInputValueProps) { setIsApplying(true); filterTimeout.start(rootProps.filterDebounceMs, () => { - const newItem = { ...item, value, fromInput: id! }; + const newItem = { + ...item, + value: type === 'number' ? Number(value) : value, + fromInput: id!, + }; applyValue(newItem); setIsApplying(false); }); }, - [id, applyValue, item, rootProps.filterDebounceMs, filterTimeout], + [filterTimeout, rootProps.filterDebounceMs, item, type, id, applyValue], ); React.useEffect(() => { diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbar.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbar.tsx index 216335a99ba3..260f22437210 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbar.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbar.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import Box from '@mui/material/Box'; import { GridToolbarContainer, GridToolbarContainerProps, @@ -61,7 +60,7 @@ const GridToolbar = React.forwardRef( // TODO: remove the reference to excelOptions in community package excelOptions={excelOptions} /> - +
{showQuickFilter && } ); diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx index 67a176845fac..58feb8f10f10 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarColumnsButton.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import useId from '@mui/utils/useId'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; -import { unstable_useId as useId } from '@mui/material/utils'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { gridPreferencePanelStateSelector } from '../../hooks/features/preferencesPanel/gridPreferencePanelSelector'; import { GridPreferencePanelsValue } from '../../hooks/features/preferencesPanel/gridPreferencePanelsValue'; diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx index 5e185987fa16..d6c952c6f543 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarDensitySelector.tsx @@ -1,10 +1,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { unstable_useId as useId, unstable_useForkRef as useForkRef } from '@mui/utils'; -import MenuList from '@mui/material/MenuList'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; -import MenuItem from '@mui/material/MenuItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import { gridDensitySelector } from '../../hooks/features/density/densitySelector'; import { GridDensity } from '../../models/gridDensity'; @@ -97,14 +95,14 @@ const GridToolbarDensitySelector = React.forwardRef< } const densityElements = densityOptions.map((option, index) => ( - handleDensityUpdate(option.value)} selected={option.value === density} > {option.icon} {option.label} - + )); return ( @@ -137,7 +135,7 @@ const GridToolbarDensitySelector = React.forwardRef< onClose={handleDensitySelectorClose} position="bottom-start" > - {densityElements} - + ); diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarExport.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarExport.tsx index 672e5e32dbe1..fada5a986636 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarExport.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarExport.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; -import MenuItem from '@mui/material/MenuItem'; +import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { GridCsvExportOptions, GridPrintExportOptions } from '../../models/gridExport'; import { GridToolbarExportContainer } from './GridToolbarExportContainer'; @@ -37,10 +37,11 @@ export interface GridToolbarExportProps { function GridCsvExportMenuItem(props: GridCsvExportMenuItemProps) { const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); const { hideMenu, options, ...other } = props; return ( - { apiRef.current.exportDataAsCsv(options); hideMenu?.(); @@ -48,7 +49,7 @@ function GridCsvExportMenuItem(props: GridCsvExportMenuItemProps) { {...other} > {apiRef.current.getLocaleText('toolbarExportCSV')} - + ); } @@ -75,10 +76,11 @@ GridCsvExportMenuItem.propTypes = { function GridPrintExportMenuItem(props: GridPrintExportMenuItemProps) { const apiRef = useGridApiContext(); + const rootProps = useGridRootProps(); const { hideMenu, options, ...other } = props; return ( - { apiRef.current.exportDataAsPrint(options); hideMenu?.(); @@ -86,7 +88,7 @@ function GridPrintExportMenuItem(props: GridPrintExportMenuItemProps) { {...other} > {apiRef.current.getLocaleText('toolbarExportPrint')} - + ); } @@ -107,22 +109,7 @@ GridPrintExportMenuItem.propTypes = { hideFooter: PropTypes.bool, hideToolbar: PropTypes.bool, includeCheckboxes: PropTypes.bool, - pageStyle: PropTypes.oneOfType([ - PropTypes.shape({ - '__@hasInstance@646': PropTypes.func.isRequired, - '__@metadata@648': PropTypes.any, - apply: PropTypes.func.isRequired, - arguments: PropTypes.any.isRequired, - bind: PropTypes.func.isRequired, - call: PropTypes.func.isRequired, - caller: PropTypes.object.isRequired, - length: PropTypes.number.isRequired, - name: PropTypes.string.isRequired, - prototype: PropTypes.any.isRequired, - toString: PropTypes.func.isRequired, - }), - PropTypes.string, - ]), + pageStyle: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), }), } as any; diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarExportContainer.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarExportContainer.tsx index 2c00e4ba0b8b..df889bd02870 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarExportContainer.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarExportContainer.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { unstable_useId as useId, unstable_useForkRef as useForkRef } from '@mui/utils'; -import MenuList from '@mui/material/MenuList'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; import { isHideMenuKey } from '../../utils/keyboardUtils'; @@ -85,7 +84,7 @@ const GridToolbarExportContainer = React.forwardRef< onClose={handleMenuClose} position="bottom-start" > - (child, { hideMenu: handleMenuClose }); })} - + ); diff --git a/packages/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx b/packages/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx index 2e286a44519d..5f7bbb6f473f 100644 --- a/packages/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx +++ b/packages/x-data-grid/src/components/toolbar/GridToolbarFilterButton.tsx @@ -6,7 +6,6 @@ import { unstable_capitalize as capitalize, unstable_useId as useId, } from '@mui/utils'; -import Badge from '@mui/material/Badge'; import { ButtonProps } from '@mui/material/Button'; import { TooltipProps } from '@mui/material/Tooltip'; import { gridColumnLookupSelector } from '../../hooks/features/columns/gridColumnsSelector'; @@ -144,9 +143,9 @@ const GridToolbarFilterButton = React.forwardRef + - + } {...buttonProps} onClick={toggleFilter} diff --git a/packages/x-data-grid/src/components/virtualization/GridBottomContainer.tsx b/packages/x-data-grid/src/components/virtualization/GridBottomContainer.tsx index ca0c0d5179eb..cc2a9c8c1789 100644 --- a/packages/x-data-grid/src/components/virtualization/GridBottomContainer.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridBottomContainer.tsx @@ -3,9 +3,6 @@ import clsx from 'clsx'; import { styled } from '@mui/system'; import composeClasses from '@mui/utils/composeClasses'; import { gridClasses, getDataGridUtilityClass } from '../../constants/gridClasses'; -import { gridDimensionsSelector } from '../../hooks/features/dimensions/gridDimensionsSelectors'; -import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; -import { useGridSelector } from '../../hooks/utils/useGridSelector'; const useUtilityClasses = () => { const slots = { @@ -23,25 +20,10 @@ const Element = styled('div')({ export function GridBottomContainer(props: React.PropsWithChildren) { const classes = useUtilityClasses(); - const apiRef = useGridApiContext(); - const { viewportOuterSize, minimumSize, hasScrollX, scrollbarSize } = useGridSelector( - apiRef, - gridDimensionsSelector, - ); - const scrollHeight = hasScrollX ? scrollbarSize : 0; - const offset = Math.max( - viewportOuterSize.height - - minimumSize.height - - // Subtract scroll height twice to account for GridVirtualScrollerFiller and horizontal scrollbar - 2 * scrollHeight, - 0, - ); - return ( ); diff --git a/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx b/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx index c8b4ca5e33be..70d727b3afd5 100644 --- a/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx @@ -8,6 +8,7 @@ const GridPanelAnchor = styled('div')({ position: 'absolute', top: `var(--DataGrid-headersTotalHeight)`, left: 0, + width: 'calc(100% - (var(--DataGrid-hasScrollY) * var(--DataGrid-scrollbarSize)))', }); type OwnerState = DataGridProcessedProps; diff --git a/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx b/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx index 16dd85280208..38859f1f8918 100644 --- a/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx @@ -51,6 +51,8 @@ const Scroller = styled('div', { flexGrow: 1, overflow: 'scroll', scrollbarWidth: 'none' /* Firefox */, + display: 'flex', + flexDirection: 'column', '&::-webkit-scrollbar': { display: 'none' /* Safari and Chrome */, }, diff --git a/packages/x-data-grid/src/constants/gridClasses.ts b/packages/x-data-grid/src/constants/gridClasses.ts index ca4ed8ad672f..06ce8915846a 100644 --- a/packages/x-data-grid/src/constants/gridClasses.ts +++ b/packages/x-data-grid/src/constants/gridClasses.ts @@ -631,6 +631,11 @@ export interface GridClasses { * Styles applied to the toggle of the grouping criteria cell */ groupingCriteriaCellToggle: string; + /** + * Styles applied to the loading container of the grouping cell of the tree data. + * @ignore - do not document. + */ + groupingCriteriaCellLoadingContainer: string; /** * Styles applied to the pinned rows container. */ @@ -810,6 +815,7 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [ 'treeDataGroupingCellLoadingContainer', 'groupingCriteriaCell', 'groupingCriteriaCellToggle', + 'groupingCriteriaCellLoadingContainer', 'pinnedRows', 'pinnedRows--top', 'pinnedRows--bottom', diff --git a/packages/x-data-grid/src/constants/gridDetailPanelToggleField.ts b/packages/x-data-grid/src/constants/gridDetailPanelToggleField.ts deleted file mode 100644 index 7ad670d344b7..000000000000 --- a/packages/x-data-grid/src/constants/gridDetailPanelToggleField.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Can't import from pro package - hence duplication -export const GRID_DETAIL_PANEL_TOGGLE_FIELD = '__detail_panel_toggle__'; diff --git a/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts b/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts index 7cd516a68c06..d47a8bbe98b0 100644 --- a/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts +++ b/packages/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts @@ -20,6 +20,7 @@ import { import { GridRowEntry, GridRowId } from '../../../models/gridRows'; import { GridHydrateRowsValue } from '../../features/rows/gridRowsInterfaces'; import { GridPreferencePanelsValue } from '../../features/preferencesPanel'; +import { GridGetRowsParams } from '../../../models/gridDataSource'; import { HeightEntry } from '../../features/rows/gridRowsMetaInterfaces'; export type GridPipeProcessorGroup = keyof GridPipeProcessingLookup; @@ -30,6 +31,7 @@ export interface GridPipeProcessingLookup { context: GridColDef; }; exportState: { value: GridInitialStateCommunity; context: GridExportStateParams }; + getRowsParams: { value: Partial }; hydrateColumns: { value: GridHydrateColumnsValue; }; diff --git a/packages/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx b/packages/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx index 3a89a3254b60..ee5325a7ad5b 100644 --- a/packages/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx +++ b/packages/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx @@ -368,7 +368,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { return null; } - const { renderedColumns, firstColumnToRender, lastColumnToRender } = columnsToRender; + const { firstColumnToRender, lastColumnToRender } = columnsToRender; const rowStructure = columnGroupsHeaderStructure[depth]; @@ -464,7 +464,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { pinnedPosition={pinnedPosition} style={style} indexInSection={indexInSection} - sectionLength={renderedColumns.length} + sectionLength={rowStructure.length} gridHasFiller={gridHasFiller} /> ); diff --git a/packages/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenuSlots.ts b/packages/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenuSlots.ts index ee8b2fecd978..fbb6c996fd6f 100644 --- a/packages/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenuSlots.ts +++ b/packages/x-data-grid/src/hooks/features/columnMenu/useGridColumnMenuSlots.ts @@ -1,7 +1,7 @@ import * as React from 'react'; -import Divider from '@mui/material/Divider'; import { GridColumnMenuRootProps } from './columnMenuInterfaces'; import { GridColDef } from '../../../models/colDef/gridColDef'; +import { useGridRootProps } from '../../utils/useGridRootProps'; import { useGridPrivateApiContext } from '../../utils/useGridPrivateApiContext'; interface UseGridColumnMenuSlotsProps extends GridColumnMenuRootProps { @@ -16,6 +16,7 @@ type UseGridColumnMenuSlotsResponse = Array< const useGridColumnMenuSlots = (props: UseGridColumnMenuSlotsProps) => { const apiRef = useGridPrivateApiContext(); + const rootProps = useGridRootProps(); const { defaultSlots, defaultSlotProps, @@ -71,7 +72,7 @@ const useGridColumnMenuSlots = (props: UseGridColumnMenuSlotsProps) => { itemProps = { ...itemProps, ...customProps }; } return addDividers && index !== sorted.length - 1 - ? [...acc, [processedComponents[key]!, itemProps], [Divider, {}]] + ? [...acc, [processedComponents[key]!, itemProps], [rootProps.slots.baseDivider, {}]] : [...acc, [processedComponents[key]!, itemProps]]; }, []); }, [ @@ -82,6 +83,7 @@ const useGridColumnMenuSlots = (props: UseGridColumnMenuSlotsProps) => { processedComponents, processedSlotProps, userItems, + rootProps.slots.baseDivider, ]); }; diff --git a/packages/x-data-grid/src/hooks/features/focus/useGridFocus.ts b/packages/x-data-grid/src/hooks/features/focus/useGridFocus.ts index 2c8e83b00875..3990d6ca8c6c 100644 --- a/packages/x-data-grid/src/hooks/features/focus/useGridFocus.ts +++ b/packages/x-data-grid/src/hooks/features/focus/useGridFocus.ts @@ -22,7 +22,7 @@ import { gridVisibleColumnDefinitionsSelector } from '../columns/gridColumnsSele import { getVisibleRows } from '../../utils/useGridVisibleRows'; import { clamp } from '../../../utils/utils'; import { GridCellCoordinates } from '../../../models/gridCell'; -import { GridRowEntry } from '../../../models/gridRows'; +import type { GridRowEntry, GridRowId } from '../../../models/gridRows'; import { gridPinnedRowsSelector } from '../rows/gridRowsSelector'; export const focusStateInitializer: GridStateInitializer = (state) => ({ @@ -423,19 +423,36 @@ export const useGridFocus = ( const handleRowSet = React.useCallback>(() => { const cell = gridFocusCellSelector(apiRef); - // If the focused cell is in a row which does not exist anymore, then remove the focus + // If the focused cell is in a row which does not exist anymore, + // focus previous row or remove the focus if (cell && !apiRef.current.getRow(cell.id)) { + const lastFocusedRowId = gridFocusCellSelector(apiRef)?.id; + + let nextRowId: GridRowId | null = null; + if (typeof lastFocusedRowId !== 'undefined') { + const lastFocusedRowIndex = + apiRef.current.getRowIndexRelativeToVisibleRows(lastFocusedRowId); + const currentPage = getVisibleRows(apiRef, { + pagination: props.pagination, + paginationMode: props.paginationMode, + }); + + const nextRow = + currentPage.rows[clamp(lastFocusedRowIndex, 0, currentPage.rows.length - 1)]; + nextRowId = nextRow?.id ?? null; + } + apiRef.current.setState((state) => ({ ...state, focus: { - cell: null, + cell: nextRowId === null ? null : { id: nextRowId, field: cell.field }, columnHeader: null, columnHeaderFilter: null, columnGroupHeader: null, }, })); } - }, [apiRef]); + }, [apiRef, props.pagination, props.paginationMode]); const handlePaginationModelChange = useEventcallback(() => { const currentFocusedCell = gridFocusCellSelector(apiRef); diff --git a/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts b/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts index bed67114ffcf..8622525c32dd 100644 --- a/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts +++ b/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts @@ -1,5 +1,10 @@ import * as React from 'react'; import { useRtl } from '@mui/system/RtlProvider'; +import { + GRID_TREE_DATA_GROUPING_FIELD, + GRID_DETAIL_PANEL_TOGGLE_FIELD, +} from '../../../internals/constants'; +import { isGroupingColumn } from '../../../internals/utils/gridRowGroupingUtils'; import { GridEventListener } from '../../../models/events'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; import { GridCellParams } from '../../../models/params/gridCellParams'; @@ -16,7 +21,6 @@ import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../../../colDef/gridCheckboxSel import { gridClasses } from '../../../constants/gridClasses'; import { GridCellModes } from '../../../models/gridEditRowModel'; import { isNavigationKey } from '../../../utils/keyboardUtils'; -import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../../../constants/gridDetailPanelToggleField'; import { GridRowId } from '../../../models'; import { gridFocusColumnGroupHeaderSelector } from '../focus'; import { gridColumnGroupsHeaderMaxDepthSelector } from '../columnGrouping/gridColumnGroupsSelector'; @@ -587,8 +591,7 @@ export const useGridKeyboardNavigation = ( const colDef = (params as GridCellParams).colDef; if ( colDef && - // `GRID_TREE_DATA_GROUPING_FIELD` from the Pro package - colDef.field === '__tree_data_group__' + (colDef.field === GRID_TREE_DATA_GROUPING_FIELD || isGroupingColumn(colDef.field)) ) { break; } diff --git a/packages/x-data-grid/src/hooks/features/pagination/gridPaginationSelector.ts b/packages/x-data-grid/src/hooks/features/pagination/gridPaginationSelector.ts index 786ba8881b45..f9c8698e5929 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/gridPaginationSelector.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/gridPaginationSelector.ts @@ -8,6 +8,8 @@ import { import { gridRowMaximumTreeDepthSelector, gridRowTreeSelector } from '../rows/gridRowsSelector'; import { getPageCount } from './gridPaginationUtils'; +const ALL_RESULTS_PAGE_VALUE = -1; + /** * @category Pagination * @ignore - do not document. @@ -92,10 +94,13 @@ export const gridPaginationRowRangeSelector = createSelectorMemoized( paginationModel.pageSize * paginationModel.page, visibleTopLevelRowCount - 1, ); - const topLevelLastRowIndex = Math.min( - topLevelFirstRowIndex + paginationModel.pageSize - 1, - visibleTopLevelRowCount - 1, - ); + const topLevelLastRowIndex = + paginationModel.pageSize === ALL_RESULTS_PAGE_VALUE + ? visibleTopLevelRowCount - 1 + : Math.min( + topLevelFirstRowIndex + paginationModel.pageSize - 1, + visibleTopLevelRowCount - 1, + ); // The range contains no element if (topLevelFirstRowIndex === -1 || topLevelLastRowIndex === -1) { diff --git a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts index 7fd6366dca12..2c8515776a13 100644 --- a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts +++ b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts @@ -29,7 +29,7 @@ import { isKeyboardEvent, isNavigationKey } from '../../../utils/keyboardUtils'; import { useGridVisibleRows } from '../../utils/useGridVisibleRows'; import { GridStateInitializer } from '../../utils/useGridInitializeState'; import { GridRowSelectionModel } from '../../../models'; -import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../../../constants/gridDetailPanelToggleField'; +import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../../../internals/constants'; import { gridClasses } from '../../../constants/gridClasses'; import { isEventTargetInPortal } from '../../../utils/domUtils'; import { isMultipleRowSelectionEnabled, findRowsToSelect, findRowsToDeselect } from './utils'; diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts index 12c5e75859b5..ef9dafab2321 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -1,5 +1,6 @@ import * as React from 'react'; import useLazyRef from '@mui/utils/useLazyRef'; +import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../../../internals/constants'; import { gridVisibleColumnDefinitionsSelector } from '../columns/gridColumnsSelector'; import { useGridVisibleRows } from '../../utils/useGridVisibleRows'; import { gridRenderContextSelector } from '../virtualization/gridVirtualizationSelectors'; @@ -15,6 +16,7 @@ import { isRowContextInitialized, getCellValue, } from './gridRowSpanningUtils'; +import { GRID_CHECKBOX_SELECTION_FIELD } from '../../../colDef/gridCheckboxSelectionColDef'; export interface GridRowSpanningState { spannedCells: Record>; @@ -31,7 +33,11 @@ export type RowRange = { firstRowIndex: number; lastRowIndex: number }; const EMPTY_STATE = { spannedCells: {}, hiddenCells: {}, hiddenCellOriginMap: {} }; const EMPTY_RANGE: RowRange = { firstRowIndex: 0, lastRowIndex: 0 }; -const skippedFields = new Set(['__check__', '__reorder__', '__detail_panel_toggle__']); +const skippedFields = new Set([ + GRID_CHECKBOX_SELECTION_FIELD, + '__reorder__', + GRID_DETAIL_PANEL_TOGGLE_FIELD, +]); /** * Default number of rows to process during state initialization to avoid flickering. * Number `20` is arbitrarily chosen to be large enough to cover most of the cases without diff --git a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx index 7ba0db4ac5da..59c8e1418af4 100644 --- a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx +++ b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx @@ -534,7 +534,8 @@ export const useGridVirtualScroller = () => { const contentSize = React.useMemo(() => { const size: React.CSSProperties = { width: needsHorizontalScrollbar ? columnsTotalWidth : 'auto', - height: contentHeight, + flexBasis: contentHeight, + flexShrink: 0, }; if (rootProps.autoHeight && currentPage.rows.length === 0) { diff --git a/packages/x-data-grid/src/internals/constants.ts b/packages/x-data-grid/src/internals/constants.ts new file mode 100644 index 000000000000..2db12bb9c9d2 --- /dev/null +++ b/packages/x-data-grid/src/internals/constants.ts @@ -0,0 +1,3 @@ +export const GRID_TREE_DATA_GROUPING_FIELD = '__tree_data_group__'; +export const GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD = '__row_group_by_columns_group__'; +export const GRID_DETAIL_PANEL_TOGGLE_FIELD = '__detail_panel_toggle__'; diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts index d07c6dd7d371..41f12e166367 100644 --- a/packages/x-data-grid/src/internals/index.ts +++ b/packages/x-data-grid/src/internals/index.ts @@ -177,4 +177,5 @@ export type { GridApiCaches } from '../models/gridApiCaches'; export { serializeCellValue } from '../hooks/features/export/serializers/csvSerializer'; export * from './utils'; +export * from './constants'; export type { Localization } from '../utils/getGridLocalization'; diff --git a/packages/x-data-grid/src/internals/utils/gridRowGroupingUtils.ts b/packages/x-data-grid/src/internals/utils/gridRowGroupingUtils.ts new file mode 100644 index 000000000000..5026a99260bf --- /dev/null +++ b/packages/x-data-grid/src/internals/utils/gridRowGroupingUtils.ts @@ -0,0 +1,15 @@ +import { GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD } from '../constants'; + +export const getRowGroupingCriteriaFromGroupingField = (groupingColDefField: string) => { + const match = groupingColDefField.match(/^__row_group_by_columns_group_(.*)__$/); + + if (!match) { + return null; + } + + return match[1]; +}; + +export const isGroupingColumn = (field: string) => + field === GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD || + getRowGroupingCriteriaFromGroupingField(field) !== null; diff --git a/packages/x-data-grid/src/internals/utils/index.ts b/packages/x-data-grid/src/internals/utils/index.ts index 5bdfb7c8c66f..4dd26999637b 100644 --- a/packages/x-data-grid/src/internals/utils/index.ts +++ b/packages/x-data-grid/src/internals/utils/index.ts @@ -1,3 +1,4 @@ export * from './computeSlots'; export * from './useProps'; export * from './propValidation'; +export * from './gridRowGroupingUtils'; diff --git a/packages/x-data-grid/src/joy/joySlots.tsx b/packages/x-data-grid/src/joy/joySlots.tsx index 246799f568a6..a5645d892066 100644 --- a/packages/x-data-grid/src/joy/joySlots.tsx +++ b/packages/x-data-grid/src/joy/joySlots.tsx @@ -1,7 +1,9 @@ import * as React from 'react'; import { SxProps } from '@mui/system'; import { ColorPaletteProp, Theme, VariantProp } from '@mui/joy/styles'; +import JoyBadge from '@mui/joy/Badge'; import JoyCheckbox from '@mui/joy/Checkbox'; +import JoyDivider from '@mui/joy/Divider'; import JoyInput from '@mui/joy/Input'; import JoyFormControl from '@mui/joy/FormControl'; import JoyFormLabel from '@mui/joy/FormLabel'; @@ -69,6 +71,20 @@ function convertVariant>( + ({ slotProps, variant, color, sx, ...props }, ref) => { + return ( + } + ref={ref as any} + /> + ); + }, +); + const Checkbox = React.forwardRef< HTMLElement, NonNullable @@ -374,7 +390,9 @@ const LoadingOverlay = React.forwardRef< const joySlots: Partial = { ...joyIconSlots, + baseBadge: Badge, baseCheckbox: Checkbox, + baseDivider: JoyDivider, baseTextField: TextField, baseButton: Button, baseIconButton: IconButton, diff --git a/packages/x-data-grid/src/locales/daDK.ts b/packages/x-data-grid/src/locales/daDK.ts index 9e31979bb081..dc305634c30e 100644 --- a/packages/x-data-grid/src/locales/daDK.ts +++ b/packages/x-data-grid/src/locales/daDK.ts @@ -42,7 +42,7 @@ const daDKGrid: Partial = { columnsManagementSearchTitle: 'Søg', columnsManagementNoColumns: 'Ingen søjler', columnsManagementShowHideAllText: 'Vis/Skjul Alle', - // columnsManagementReset: 'Reset', + columnsManagementReset: 'Nulstil', // Filter panel text filterPanelAddFilter: 'Tilføj filter', @@ -58,9 +58,9 @@ const daDKGrid: Partial = { // Filter operators text filterOperatorContains: 'indeholder', - // filterOperatorDoesNotContain: 'does not contain', + filterOperatorDoesNotContain: 'indeholder ikke', filterOperatorEquals: 'lig med', - // filterOperatorDoesNotEqual: 'does not equal', + filterOperatorDoesNotEqual: 'ikke lig med', filterOperatorStartsWith: 'begynder med', filterOperatorEndsWith: 'ender med', filterOperatorIs: 'er lig med', @@ -81,9 +81,9 @@ const daDKGrid: Partial = { // Header filter operators text headerFilterOperatorContains: 'Indeholder', - // headerFilterOperatorDoesNotContain: 'Does not contain', + headerFilterOperatorDoesNotContain: 'Indeholder ikke', headerFilterOperatorEquals: 'Lig med', - // headerFilterOperatorDoesNotEqual: 'Does not equal', + headerFilterOperatorDoesNotEqual: 'Ikke lig med', headerFilterOperatorStartsWith: 'Begynder med', headerFilterOperatorEndsWith: 'Ender med', headerFilterOperatorIs: 'Er lig med', diff --git a/packages/x-data-grid/src/locales/itIT.ts b/packages/x-data-grid/src/locales/itIT.ts index 31b09da652c5..2058096d6165 100644 --- a/packages/x-data-grid/src/locales/itIT.ts +++ b/packages/x-data-grid/src/locales/itIT.ts @@ -39,10 +39,10 @@ const itITGrid: Partial = { toolbarExportExcel: 'Scarica come Excel', // Columns management text - // columnsManagementSearchTitle: 'Search', - // columnsManagementNoColumns: 'No columns', - // columnsManagementShowHideAllText: 'Show/Hide All', - // columnsManagementReset: 'Reset', + columnsManagementSearchTitle: 'Cerca', + columnsManagementNoColumns: 'Nessuna colonna', + columnsManagementShowHideAllText: 'Mostra/Nascondi Tutto', + columnsManagementReset: 'Resetta', // Filter panel text filterPanelAddFilter: 'Aggiungi un filtro', @@ -58,13 +58,13 @@ const itITGrid: Partial = { // Filter operators text filterOperatorContains: 'contiene', - // filterOperatorDoesNotContain: 'does not contain', + filterOperatorDoesNotContain: 'non contiene', filterOperatorEquals: 'uguale a', - // filterOperatorDoesNotEqual: 'does not equal', + filterOperatorDoesNotEqual: 'diverso da', filterOperatorStartsWith: 'comincia per', filterOperatorEndsWith: 'termina per', filterOperatorIs: 'uguale a', - filterOperatorNot: 'diversa da', + filterOperatorNot: 'diverso da', filterOperatorAfter: 'dopo il', filterOperatorOnOrAfter: 'a partire dal', filterOperatorBefore: 'prima del', @@ -81,26 +81,26 @@ const itITGrid: Partial = { // Header filter operators text headerFilterOperatorContains: 'Contiene', - // headerFilterOperatorDoesNotContain: 'Does not contain', - headerFilterOperatorEquals: 'uguale a', - // headerFilterOperatorDoesNotEqual: 'Does not equal', - headerFilterOperatorStartsWith: 'comincia per', - headerFilterOperatorEndsWith: 'termina per', - headerFilterOperatorIs: 'uguale a', - headerFilterOperatorNot: 'diversa da', - headerFilterOperatorAfter: 'dopo il', - headerFilterOperatorOnOrAfter: 'a partire dal', - headerFilterOperatorBefore: 'prima del', - headerFilterOperatorOnOrBefore: 'fino al', - headerFilterOperatorIsEmpty: 'è vuoto', - headerFilterOperatorIsNotEmpty: 'non è vuoto', - headerFilterOperatorIsAnyOf: 'è uno tra', - 'headerFilterOperator=': 'uguale a', - 'headerFilterOperator!=': 'diverso da', - 'headerFilterOperator>': 'maggiore di', - 'headerFilterOperator>=': 'maggiore o uguale a', - 'headerFilterOperator<': 'minore di', - 'headerFilterOperator<=': 'minore o uguale a', + headerFilterOperatorDoesNotContain: 'Non contiene', + headerFilterOperatorEquals: 'Uguale a', + headerFilterOperatorDoesNotEqual: 'Diverso da', + headerFilterOperatorStartsWith: 'Comincia per', + headerFilterOperatorEndsWith: 'Termina per', + headerFilterOperatorIs: 'Uguale a', + headerFilterOperatorNot: 'Diverso da', + headerFilterOperatorAfter: 'Dopo il', + headerFilterOperatorOnOrAfter: 'A partire dal', + headerFilterOperatorBefore: 'Prima del', + headerFilterOperatorOnOrBefore: 'Fino al', + headerFilterOperatorIsEmpty: 'È vuoto', + headerFilterOperatorIsNotEmpty: 'Non è vuoto', + headerFilterOperatorIsAnyOf: 'È uno tra', + 'headerFilterOperator=': 'Uguale a', + 'headerFilterOperator!=': 'Diverso da', + 'headerFilterOperator>': 'Maggiore di', + 'headerFilterOperator>=': 'Maggiore o uguale a', + 'headerFilterOperator<': 'Minore di', + 'headerFilterOperator<=': 'Minore o uguale a', // Filter values text filterValueAny: 'qualunque', diff --git a/packages/x-data-grid/src/locales/ptBR.ts b/packages/x-data-grid/src/locales/ptBR.ts index bd9a3153b868..4dbd7b806024 100644 --- a/packages/x-data-grid/src/locales/ptBR.ts +++ b/packages/x-data-grid/src/locales/ptBR.ts @@ -58,9 +58,9 @@ const ptBRGrid: Partial = { // Filter operators text filterOperatorContains: 'contém', - // filterOperatorDoesNotContain: 'does not contain', + filterOperatorDoesNotContain: 'não contém', filterOperatorEquals: 'é igual a', - // filterOperatorDoesNotEqual: 'does not equal', + filterOperatorDoesNotEqual: 'não é igual a', filterOperatorStartsWith: 'começa com', filterOperatorEndsWith: 'termina com', filterOperatorIs: 'é', @@ -81,9 +81,9 @@ const ptBRGrid: Partial = { // Header filter operators text headerFilterOperatorContains: 'Contém', - // headerFilterOperatorDoesNotContain: 'Does not contain', + headerFilterOperatorDoesNotContain: 'Não contém', headerFilterOperatorEquals: 'Igual', - // headerFilterOperatorDoesNotEqual: 'Does not equal', + headerFilterOperatorDoesNotEqual: 'Não é igual a', headerFilterOperatorStartsWith: 'Começa com', headerFilterOperatorEndsWith: 'Termina com', headerFilterOperatorIs: 'É', diff --git a/packages/x-data-grid/src/material/index.ts b/packages/x-data-grid/src/material/index.ts index 6011f2c4606e..db2748de022c 100644 --- a/packages/x-data-grid/src/material/index.ts +++ b/packages/x-data-grid/src/material/index.ts @@ -1,4 +1,8 @@ +import MUIBadge from '@mui/material/Badge'; import MUICheckbox from '@mui/material/Checkbox'; +import MUIDivider from '@mui/material/Divider'; +import MUIMenuList from '@mui/material/MenuList'; +import MUIMenuItem from '@mui/material/MenuItem'; import MUITextField from '@mui/material/TextField'; import MUIFormControl from '@mui/material/FormControl'; import MUISelect from '@mui/material/Select'; @@ -81,7 +85,11 @@ const iconSlots: GridIconSlotsComponent = { const materialSlots: GridBaseSlots & GridIconSlotsComponent = { ...iconSlots, + baseBadge: MUIBadge, baseCheckbox: MUICheckbox, + baseDivider: MUIDivider, + baseMenuList: MUIMenuList, + baseMenuItem: MUIMenuItem, baseTextField: MUITextField, baseFormControl: MUIFormControl, baseSelect: MUISelect, diff --git a/packages/x-data-grid/src/models/gridExport.ts b/packages/x-data-grid/src/models/gridExport.ts index 98220015a855..a0a628b4b38c 100644 --- a/packages/x-data-grid/src/models/gridExport.ts +++ b/packages/x-data-grid/src/models/gridExport.ts @@ -149,7 +149,7 @@ export interface GridPrintExportOptions extends GridExportOptions { /** * Provide Print specific styles to the print window. */ - pageStyle?: string | Function; + pageStyle?: string | (() => string); /** * Function that returns the list of row ids to export in the order they should be exported. * @param {GridPrintGetRowsToExportParams} params With all properties from [[GridPrintGetRowsToExportParams]]. diff --git a/packages/x-data-grid/src/models/gridSlotsComponent.ts b/packages/x-data-grid/src/models/gridSlotsComponent.ts index 4fe7ea508c74..c9c38a0fe356 100644 --- a/packages/x-data-grid/src/models/gridSlotsComponent.ts +++ b/packages/x-data-grid/src/models/gridSlotsComponent.ts @@ -5,6 +5,11 @@ import type { GridIconSlotsComponent } from './gridIconSlotsComponent'; export type { GridSlotProps } from './gridSlotsComponentsProps'; export interface GridBaseSlots { + /** + * The custom Badge component used in the grid for both header and cells. + * @default Badge + */ + baseBadge: React.JSXElementConstructor; /** * The custom Checkbox component used in the grid for both header and cells. * @default Checkbox @@ -15,6 +20,21 @@ export interface GridBaseSlots { * @default Chip */ baseChip: React.JSXElementConstructor; + /** + * The custom Divider component used in the grid. + * @default Divider + */ + baseDivider: React.JSXElementConstructor; + /** + * The custom MenuList component used in the grid. + * @default MenuList + */ + baseMenuList: React.JSXElementConstructor; + /** + * The custom MenuItem component used in the grid. + * @default MenuItem + */ + baseMenuItem: React.JSXElementConstructor; /** * The custom InputAdornment component used in the grid. * @default InputAdornment diff --git a/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts b/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts index 6ea9c9f27613..8b709b41a55b 100644 --- a/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts +++ b/packages/x-data-grid/src/models/gridSlotsComponentsProps.ts @@ -1,5 +1,8 @@ import * as React from 'react'; +import type { BadgeProps } from '@mui/material/Badge'; import type { CheckboxProps } from '@mui/material/Checkbox'; +import type { MenuListProps } from '@mui/material/MenuList'; +import type { MenuItemProps } from '@mui/material/MenuItem'; import type { TextFieldProps } from '@mui/material/TextField'; import type { FormControlProps } from '@mui/material/FormControl'; import type { SelectProps } from '@mui/material/Select'; @@ -31,8 +34,14 @@ import type { GridLoadingOverlayProps } from '../components/GridLoadingOverlay'; import type { GridRowCountProps } from '../components/GridRowCount'; import type { GridColumnHeaderSortIconProps } from '../components/columnHeaders/GridColumnHeaderSortIcon'; +type DividerProps = {}; + // Overrides for module augmentation +export interface BaseBadgePropsOverrides {} export interface BaseCheckboxPropsOverrides {} +export interface BaseDividerPropsOverrides {} +export interface BaseMenuListPropsOverrides {} +export interface BaseMenuItemPropsOverrides {} export interface BaseTextFieldPropsOverrides {} export interface BaseFormControlPropsOverrides {} export interface BaseSelectPropsOverrides {} @@ -66,7 +75,11 @@ export interface SkeletonCellPropsOverrides {} export interface RowPropsOverrides {} export interface GridSlotProps { + baseBadge: BadgeProps & BaseBadgePropsOverrides; baseCheckbox: CheckboxProps & BaseCheckboxPropsOverrides; + baseDivider: DividerProps & BaseDividerPropsOverrides; + baseMenuList: MenuListProps & BaseMenuListPropsOverrides; + baseMenuItem: MenuItemProps & BaseMenuItemPropsOverrides; baseTextField: TextFieldProps & BaseTextFieldPropsOverrides; baseFormControl: FormControlProps & BaseFormControlPropsOverrides; baseSelect: SelectProps & BaseSelectPropsOverrides; 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 8a19bf501a4c..4ad235c405e8 100644 --- a/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx @@ -228,16 +228,15 @@ 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'); 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', () => { 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 2efc26c7c9c3..095292926198 100644 --- a/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rows.DataGrid.test.tsx @@ -645,11 +645,9 @@ describe(' - Rows', () => { ); const expectedHeight = baselineProps.rows.length * (contentHeight + border); await waitFor(() => { - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', - height: `${expectedHeight}px`, - }); + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${expectedHeight}px` }); }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); }); it('should use the default row height to calculate the content size when the row has not been measured yet', async () => { @@ -674,11 +672,9 @@ describe(' - Rows', () => { border + // Measured rows also include the border (baselineProps.rows.length - 1) * defaultRowHeight; await waitFor(() => { - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', - height: `${expectedHeight}px`, - }); + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${expectedHeight}px` }); }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); }); it('should use the value from getEstimatedRowHeight to estimate the content size', async () => { @@ -703,11 +699,9 @@ describe(' - Rows', () => { const expectedHeight = firstRowHeight + (baselineProps.rows.length - 1) * estimatedRowHeight; await waitFor(() => { - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', - height: `${expectedHeight}px`, - }); + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${expectedHeight}px` }); }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); }); it('should recalculate the content size when the rows prop changes', async () => { @@ -723,18 +717,14 @@ describe(' - Rows', () => { '.MuiDataGrid-virtualScrollerContent', ); await waitFor(() => { - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', - height: '101px', - }); + expect(virtualScrollerContent).toHaveComputedStyle({ height: '101px' }); }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); setProps({ rows: [{ clientId: 'c1', expanded: true }] }); await waitFor(() => { - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', - height: '201px', - }); + expect(virtualScrollerContent).toHaveComputedStyle({ height: '201px' }); }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); }); it('should set minHeight to "auto" in all rows with dynamic row height', () => { @@ -813,11 +803,11 @@ describe(' - Rows', () => { '.MuiDataGrid-virtualScrollerContent', )!; await waitFor(() => { - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${Math.floor(expectedHeight)}px`, }); }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); }); it('should position correctly the render zone when the 2nd page has less rows than the 1st page', async function test() { @@ -968,7 +958,11 @@ describe(' - Rows', () => { }); }); - it('should consider the spacing when computing the content size', () => { + it('should consider the spacing when computing the content size', function test() { + if (isJSDOM) { + // Need layouting + this.skip(); + } const spacingTop = 5; const spacingBottom = 10; const rowHeight = 50; @@ -981,13 +975,15 @@ describe(' - Rows', () => { ); const virtualScrollerContent = document.querySelector('.MuiDataGrid-virtualScrollerContent'); const expectedHeight = rows.length * (rowHeight + spacingTop + spacingBottom); - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', - height: `${expectedHeight}px`, - }); + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${expectedHeight}px` }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); }); - it('should update the content size when getRowSpacing is removed', () => { + it('should update the content size when getRowSpacing is removed', function test() { + if (isJSDOM) { + // Need layouting + this.skip(); + } const spacingTop = 5; const spacingBottom = 10; const rowHeight = 50; @@ -1000,15 +996,13 @@ describe(' - Rows', () => { ); const virtualScrollerContent = document.querySelector('.MuiDataGrid-virtualScrollerContent'); const expectedHeight = rows.length * (rowHeight + spacingTop + spacingBottom); - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', - height: `${expectedHeight}px`, - }); + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${expectedHeight}px` }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); setProps({ getRowSpacing: null }); - expect(virtualScrollerContent).toHaveInlineStyle({ - width: 'auto', + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rows.length * rowHeight}px`, }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); }); it('should set the row margin to the value returned by getRowSpacing if rowSpacingType is not defined', () => { diff --git a/packages/x-date-pickers-pro/package.json b/packages/x-date-pickers-pro/package.json index 7c823343bcd9..6d7d8201a0e3 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.20.0", + "version": "7.21.0", "description": "The Pro plan edition of the Date and Time Picker components (MUI X).", "author": "MUI Team", "main": "src/index.ts", diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx new file mode 100644 index 000000000000..2d345ab1cf08 --- /dev/null +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { screen, fireEvent } from '@mui/internal-test-utils'; +import { describeAdapters } from 'test/utils/pickers'; +import { DateRangeCalendar } from './DateRangeCalendar'; + +describe(' - Timezone', () => { + describeAdapters('Timezone prop', DateRangeCalendar, ({ adapter, render }) => { + if (!adapter.isTimezoneCompatible) { + return; + } + + it('should correctly render month days when timezone changes', () => { + function DateCalendarWithControlledTimezone() { + const [timezone, setTimezone] = React.useState('Europe/Paris'); + return ( + + + + + ); + } + render(); + + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + + fireEvent.click(screen.getByRole('button', { name: 'Switch timezone' })); + + // the amount of rendered days should remain the same after changing timezone + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + }); + }); +}); 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 2d6094f4db18..7aa94997646b 100644 --- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.test.tsx @@ -22,7 +22,7 @@ describe('', () => { }); it('should not open mobile picker dialog when clicked on input', () => { - // Test with v7 input + // Test with accessible DOM structure const { unmount } = renderWithProps({ enableAccessibleFieldDOMStructure: true }); fireEvent.click(getFieldInputRoot()); clock.runToLast(); @@ -32,7 +32,7 @@ describe('', () => { unmount(); - // Test with v6 input + // Test with non-accessible DOM structure renderWithProps({ enableAccessibleFieldDOMStructure: false }); fireEvent.click(screen.getAllByRole('textbox')[0]); clock.runToLast(); @@ -45,7 +45,7 @@ describe('', () => { const originalMatchMedia = window.matchMedia; window.matchMedia = stubMatchMedia(false); - render(); + render(); fireEvent.click(getFieldInputRoot()); clock.runToLast(); diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.tsx b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.tsx index c6a5c6e350c8..cc96fb4d9bbe 100644 --- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.tsx @@ -11,7 +11,7 @@ import { DateRangePickerProps } from './DateRangePicker.types'; type DatePickerComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: DateRangePickerProps & React.RefAttributes, @@ -29,7 +29,7 @@ type DatePickerComponent = (< */ const DateRangePicker = React.forwardRef(function DateRangePicker< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: DateRangePickerProps, ref: React.Ref, @@ -140,7 +140,7 @@ DateRangePicker.propTypes = { */ displayWeekNumber: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.any, /** diff --git a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.types.ts b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.types.ts index cc56765af366..95cd8b8a6b63 100644 --- a/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.types.ts +++ b/packages/x-date-pickers-pro/src/DateRangePicker/DateRangePicker.types.ts @@ -22,7 +22,7 @@ export interface DateRangePickerSlotProps< export interface DateRangePickerProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > extends DesktopDateRangePickerProps, MobileDateRangePickerProps { /** diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.tsx index 64a1316f3b78..53837bb78ad5 100644 --- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.tsx @@ -11,7 +11,7 @@ import { MobileDateTimeRangePicker } from '../MobileDateTimeRangePicker'; type DateTimeRangePickerComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: DateTimeRangePickerProps & React.RefAttributes, @@ -29,7 +29,7 @@ type DateTimeRangePickerComponent = (< */ const DateTimeRangePicker = React.forwardRef(function DateTimeRangePicker< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: DateTimeRangePickerProps, ref: React.Ref, @@ -150,7 +150,7 @@ DateTimeRangePicker.propTypes = { */ displayWeekNumber: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.any, /** diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.types.ts b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.types.ts index 109b4715c963..aaa9942d7bf1 100644 --- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.types.ts +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePicker.types.ts @@ -22,7 +22,7 @@ export interface DateTimeRangePickerSlotProps< export interface DateTimeRangePickerProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > extends DesktopDateTimeRangePickerProps, MobileDateTimeRangePickerProps { /** diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.tsx b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.tsx index 984548ad2a1b..efae31fdc727 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.tsx @@ -17,7 +17,7 @@ import { DateRange } from '../models'; type DesktopDateRangePickerComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: DesktopDateRangePickerProps & React.RefAttributes, @@ -35,7 +35,7 @@ type DesktopDateRangePickerComponent = (< */ const DesktopDateRangePicker = React.forwardRef(function DesktopDateRangePicker< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: DesktopDateRangePickerProps, ref: React.Ref, @@ -176,7 +176,7 @@ DesktopDateRangePicker.propTypes = { */ displayWeekNumber: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.any, /** diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.types.ts b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.types.ts index 8c96020fd9fa..b08f4d5e5382 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.types.ts +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/DesktopDateRangePicker.types.ts @@ -26,7 +26,7 @@ export interface DesktopDateRangePickerSlotProps< export interface DesktopDateRangePickerProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > extends BaseDateRangePickerProps, DesktopRangeOnlyPickerProps { /** 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 8ec961707343..7febfb5df6bb 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 @@ -30,7 +30,6 @@ describe('', () => { it('should scroll current month to the active selection when focusing appropriate field', () => { render( , @@ -49,10 +48,7 @@ describe('', () => { it(`should not crash when opening picker with invalid date value`, () => { render( - , + , ); openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); @@ -74,7 +70,6 @@ describe('', () => { ', () => { describe('Field slot: SingleInputDateRangeField', () => { it('should add focused class to the field when it is focused', () => { - // test v7 behavior + // Test with accessible DOM structure const { unmount } = render( - , + , ); const sectionsContainer = getFieldSectionsContainer(); @@ -107,8 +99,13 @@ describe('', () => { unmount(); - // test v6 behavior - render(); + // Test with non-accessible DOM structure + render( + , + ); const input = getTextbox(); act(() => input.focus()); @@ -117,20 +114,22 @@ describe('', () => { }); it('should render the input with a given `name` when `SingleInputDateRangeField` is used', () => { - // Test with v7 input + // Test with accessible DOM structure const { unmount } = render( - , + , ); expect(screen.getByRole('textbox', { hidden: true }).name).to.equal('test'); unmount(); - // Test with v6 input - render(); + // Test with non-accessible DOM structure + render( + , + ); expect(screen.getByRole('textbox').name).to.equal('test'); }); }); @@ -141,7 +140,6 @@ describe('', () => { const handleTouchStart = spy(); render( ', () => { it('should open when clicking the start input', () => { const onOpen = spy(); - render(); + render(); openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); @@ -178,7 +176,7 @@ describe('', () => { it('should open when clicking the end input', () => { const onOpen = spy(); - render(); + render(); openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'end' }); @@ -190,7 +188,7 @@ describe('', () => { it(`should open when pressing "${key}" in the start input`, () => { const onOpen = spy(); - render(); + render(); const startInput = getFieldSectionsContainer(); act(() => startInput.focus()); @@ -206,7 +204,7 @@ describe('', () => { it(`should open when pressing "${key}" in the end input`, () => { const onOpen = spy(); - render(); + render(); const endInput = getFieldSectionsContainer(1); act(() => endInput.focus()); @@ -229,7 +227,6 @@ describe('', () => { render( ', () => { render( ', () => { render( ', () => { render( ', () => { render(
- +
, ); @@ -404,7 +393,6 @@ describe('', () => { render(
', () => { const onAccept = spy(); const onClose = spy(); - render( - , - ); + render(); // Dismiss the picker fireEvent.click(document.body); @@ -471,12 +452,7 @@ describe('', () => { render( - + + + ); + } + render(); + + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + + fireEvent.click(screen.getByRole('button', { name: 'Switch timezone' })); + + // the amount of rendered days should remain the same after changing timezone + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + }); + TIMEZONE_TO_TEST.forEach((timezone) => { describe(`Timezone: ${timezone}`, () => { it('should use timezone prop for onChange when no value is provided', () => { diff --git a/packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx b/packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx index 29fff983fe3d..5e772c5d742d 100644 --- a/packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx +++ b/packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx @@ -43,6 +43,7 @@ export const createCalendarStateReducer = action: | ReducerAction<'finishMonthSwitchingAnimation'> | ReducerAction<'changeMonth', ChangeMonthPayload> + | ReducerAction<'changeMonthTimezone', { newTimezone: string }> | ReducerAction<'changeFocusedDay', ChangeFocusedDayPayload>, ): CalendarState => { switch (action.type) { @@ -54,6 +55,21 @@ export const createCalendarStateReducer = isMonthSwitchingAnimating: !reduceAnimations, }; + case 'changeMonthTimezone': { + const newTimezone = action.newTimezone; + if (utils.getTimezone(state.currentMonth) === newTimezone) { + return state; + } + let newCurrentMonth = utils.setTimezone(state.currentMonth, newTimezone); + if (utils.getMonth(newCurrentMonth) !== utils.getMonth(state.currentMonth)) { + newCurrentMonth = utils.setMonth(newCurrentMonth, utils.getMonth(state.currentMonth)); + } + return { + ...state, + currentMonth: newCurrentMonth, + }; + } + case 'finishMonthSwitchingAnimation': return { ...state, @@ -149,7 +165,9 @@ export const useCalendarState = ( granularity: SECTION_TYPE_GRANULARITY.day, }); }, - [], // eslint-disable-line react-hooks/exhaustive-deps + // We want the `referenceDate` to update on prop and `timezone` change (https://github.com/mui/mui-x/issues/10804) + // eslint-disable-next-line react-hooks/exhaustive-deps + [referenceDateProp, timezone], ); const [calendarState, dispatch] = React.useReducer(reducerFn, { @@ -159,6 +177,15 @@ export const useCalendarState = ( slideDirection: 'left', }); + // Ensure that `calendarState.currentMonth` timezone is updated when `referenceDate` (or timezone changes) + // https://github.com/mui/mui-x/issues/10804 + React.useEffect(() => { + dispatch({ + type: 'changeMonthTimezone', + newTimezone: utils.getTimezone(referenceDate), + }); + }, [referenceDate, utils]); + const handleChangeMonth = React.useCallback( (payload: ChangeMonthPayload) => { dispatch({ diff --git a/packages/x-date-pickers/src/DateField/DateField.tsx b/packages/x-date-pickers/src/DateField/DateField.tsx index 2e99c220e38f..197f03a7a9e7 100644 --- a/packages/x-date-pickers/src/DateField/DateField.tsx +++ b/packages/x-date-pickers/src/DateField/DateField.tsx @@ -14,7 +14,7 @@ import { PickerValidDate } from '../models'; type DateFieldComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: DateFieldProps & React.RefAttributes, @@ -32,7 +32,7 @@ type DateFieldComponent = (< */ const DateField = React.forwardRef(function DateField< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: DateFieldProps, inRef: React.Ref, @@ -48,7 +48,7 @@ const DateField = React.forwardRef(function DateField< const TextField = slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure ? PickersTextField : MuiTextField); + (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); const textFieldProps = useSlotProps({ elementType: TextField, externalSlotProps: slotProps?.textField, @@ -123,7 +123,7 @@ DateField.propTypes = { */ disablePast: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.bool, /** diff --git a/packages/x-date-pickers/src/DateField/DateField.types.ts b/packages/x-date-pickers/src/DateField/DateField.types.ts index cf1bdfcc800a..2b6feddbc943 100644 --- a/packages/x-date-pickers/src/DateField/DateField.types.ts +++ b/packages/x-date-pickers/src/DateField/DateField.types.ts @@ -40,32 +40,28 @@ export interface UseDateFieldProps< BaseDateValidationProps, ExportedUseClearableFieldProps {} -export type UseDateFieldComponentProps< - TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean, - TChildProps extends {}, -> = Omit> & - UseDateFieldProps; - export type DateFieldProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, -> = UseDateFieldComponentProps< - TDate, - TEnableAccessibleFieldDOMStructure, - BuiltInFieldTextFieldProps -> & { - /** - * Overridable component slots. - * @default {} - */ - slots?: DateFieldSlots; - /** - * The props used for each component slot. - * @default {} - */ - slotProps?: DateFieldSlotProps; -}; + TEnableAccessibleFieldDOMStructure extends boolean = true, +> = + // The hook props + UseDateFieldProps & + // The TextField props + Omit< + BuiltInFieldTextFieldProps, + keyof UseDateFieldProps + > & { + /** + * Overridable component slots. + * @default {} + */ + slots?: DateFieldSlots; + /** + * The props used for each component slot. + * @default {} + */ + slotProps?: DateFieldSlotProps; + }; export type DateFieldOwnerState< TDate extends PickerValidDate, diff --git a/packages/x-date-pickers/src/DateField/index.ts b/packages/x-date-pickers/src/DateField/index.ts index cd6119e98a25..69c1964f1330 100644 --- a/packages/x-date-pickers/src/DateField/index.ts +++ b/packages/x-date-pickers/src/DateField/index.ts @@ -1,7 +1,3 @@ export { DateField } from './DateField'; export { useDateField as unstable_useDateField } from './useDateField'; -export type { - UseDateFieldProps, - UseDateFieldComponentProps, - DateFieldProps, -} from './DateField.types'; +export type { UseDateFieldProps, DateFieldProps } from './DateField.types'; diff --git a/packages/x-date-pickers/src/DateField/tests/describes.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/describes.DateField.test.tsx index 9ac060d09021..b5fdeb6d272a 100644 --- a/packages/x-date-pickers/src/DateField/tests/describes.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/describes.DateField.test.tsx @@ -21,7 +21,7 @@ describe(' - Describes', () => { componentFamily: 'field', })); - describeConformance(, () => ({ + describeConformance(, () => ({ classes: {} as any, inheritComponent: PickersTextField, render, 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 fafafc44a61e..2a0313757239 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 @@ -213,7 +213,7 @@ 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 + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, @@ -230,7 +230,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `${adapter.formats.month} ${adapter.formats.year}`, @@ -259,7 +259,7 @@ describe(' - Editing', () => { }); it('should clear all the sections when all sections are selected and all sections are completed', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, @@ -280,7 +280,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `${adapter.formats.month} ${adapter.formats.year}`, @@ -298,7 +298,7 @@ describe(' - Editing', () => { }); it('should clear all the sections when all sections are selected and not all sections are completed', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, @@ -322,7 +322,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `${adapter.formats.month} ${adapter.formats.year}`, @@ -345,7 +345,7 @@ describe(' - Editing', () => { }); it('should not keep query after typing again on a cleared section', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: adapter.formats.year, @@ -364,7 +364,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: adapter.formats.year, @@ -394,7 +394,7 @@ describe(' - Editing', () => { }); it('should not call `onChange` when clearing all sections and both dates are already empty', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -417,7 +417,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ @@ -437,7 +437,7 @@ describe(' - Editing', () => { }); it('should call `onChange` when clearing the first and last section', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -462,7 +462,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ @@ -488,7 +488,7 @@ describe(' - Editing', () => { }); it('should not call `onChange` if the section is already empty', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -508,7 +508,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ @@ -759,7 +759,7 @@ describe(' - Editing', () => { describeAdapters('Disabled field', DateField, ({ renderWithProps }) => { it('should not allow key editing on disabled field', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -793,7 +793,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ onChange: onChangeV6, @@ -976,7 +976,7 @@ describe(' - Editing', () => { }); it('should allow to type the date 29th of February for leap years', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: adapter.formats.keyboardDate, @@ -1007,7 +1007,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: adapter.formats.keyboardDate, @@ -1148,7 +1148,7 @@ describe(' - Editing', () => { DateField, ({ adapter, renderWithProps, testFieldChange }) => { it('should clear the selected section when only this section is completed (Backspace)', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, @@ -1163,7 +1163,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ format: `${adapter.formats.month} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, @@ -1179,7 +1179,7 @@ describe(' - Editing', () => { }); it('should clear the selected section when all sections are completed (Backspace)', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, @@ -1193,7 +1193,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), @@ -1208,7 +1208,7 @@ describe(' - Editing', () => { }); it('should clear all the sections when all sections are selected and all sections are completed (Backspace)', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, @@ -1229,7 +1229,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ format: `${adapter.formats.month} ${adapter.formats.year}`, defaultValue: adapter.date(), @@ -1247,7 +1247,7 @@ describe(' - Editing', () => { }); it('should clear all the sections when all sections are selected and not all sections are completed (Backspace)', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `${adapter.formats.month} ${adapter.formats.year}`, @@ -1269,7 +1269,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ format: `${adapter.formats.month} ${adapter.formats.year}`, enableAccessibleFieldDOMStructure: false, @@ -1320,7 +1320,7 @@ describe(' - Editing', () => { }); it('should call `onChange` when clearing the first and last section (Backspace)', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -1343,7 +1343,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ @@ -1380,7 +1380,6 @@ describe(' - Editing', () => { onChange, }); - // 1 for v7 and 1 for v7 input expect(onChange.callCount).to.equal(2); }); }, @@ -1443,7 +1442,7 @@ describe(' - Editing', () => { }; it('should set the date when all sections are selected, the pasted value is valid and a value is provided', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -1466,7 +1465,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ defaultValue: adapter.date(), @@ -1486,7 +1485,7 @@ 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 + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -1507,7 +1506,7 @@ describe(' - Editing', () => { expect(onChangeV7.lastCall.firstArg).toEqualDateTime(new Date(2022, 8, 16)); view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ onChange: onChangeV6, @@ -1526,7 +1525,7 @@ describe(' - Editing', () => { }); it('should not set the date when all sections are selected and the pasted value is not valid', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -1545,7 +1544,7 @@ describe(' - Editing', () => { expectFieldValueV7(view.getSectionsContainer(), 'MM/DD/YYYY'); view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ onChange: onChangeV6, @@ -1564,7 +1563,7 @@ describe(' - Editing', () => { it('should set the date when all sections are selected and the format contains escaped characters', () => { const { start: startChar, end: endChar } = adapter.escapedCharacters; - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -1586,7 +1585,7 @@ describe(' - Editing', () => { expect(adapter.getYear(onChangeV7.lastCall.firstArg)).to.equal(2014); view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ onChange: onChangeV6, @@ -1606,7 +1605,7 @@ describe(' - Editing', () => { }); it('should not set the date when all sections are selected and props.readOnly = true', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -1629,7 +1628,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ @@ -1649,7 +1648,7 @@ describe(' - Editing', () => { }); it('should set the section when one section is selected, the pasted value has the correct type and no value is provided', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -1667,7 +1666,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ @@ -1686,7 +1685,7 @@ describe(' - Editing', () => { }); it('should set the section when one section is selected, the pasted value has the correct type and value is provided', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -1705,7 +1704,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ @@ -1725,7 +1724,7 @@ describe(' - Editing', () => { }); it('should not update the section when one section is selected and the pasted value has incorrect type', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -1743,7 +1742,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ @@ -1762,7 +1761,7 @@ describe(' - Editing', () => { }); it('should reset sections internal state when pasting', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, defaultValue: adapter.date('2018-12-05'), @@ -1786,7 +1785,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ defaultValue: adapter.date('2018-12-05'), enableAccessibleFieldDOMStructure: false, @@ -1845,7 +1844,7 @@ describe(' - Editing', () => { }); it('should not allow pasting on disabled field', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -1868,7 +1867,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ onChange: onChangeV6, @@ -1891,7 +1890,7 @@ describe(' - Editing', () => { DateField, ({ adapter, renderWithProps }) => { it('should not loose time information when a value is provided', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -1904,7 +1903,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ defaultValue: adapter.date('2010-04-03T03:03:03'), @@ -1918,7 +1917,7 @@ describe(' - Editing', () => { }); it('should not loose time information when cleaning the date then filling it again', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -1956,7 +1955,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ @@ -1990,7 +1989,7 @@ describe(' - Editing', () => { }); it('should not loose date information when using the year format and value is provided', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -2007,7 +2006,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ @@ -2025,7 +2024,7 @@ describe(' - Editing', () => { }); it('should not loose date information when using the month format and value is provided', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -2041,7 +2040,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ @@ -2064,7 +2063,7 @@ describe(' - Editing', () => { DateField, ({ adapter, renderWithProps }) => { it('should set the date when the change value is valid and no value is provided', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -2077,7 +2076,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); renderWithProps({ onChange: onChangeV6, @@ -2091,7 +2090,7 @@ describe(' - Editing', () => { }); it('should set the date when the change value is valid and a value is provided', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); const view = renderWithProps({ @@ -2107,7 +2106,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); renderWithProps({ @@ -2175,7 +2174,7 @@ describe(' - Editing', () => { }); it('should support letter editing', () => { - // Test with v6 input + // Test with non-accessible DOM structure const view = renderWithProps({ defaultValue: adapter.date('2022-01-16'), format: `${adapter.formats.month} ${adapter.formats.year}`, @@ -2204,7 +2203,7 @@ 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 + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: adapter.date('2022-11-23'), @@ -2218,7 +2217,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ value: adapter.date('2022-11-23'), enableAccessibleFieldDOMStructure: false, @@ -2233,7 +2232,7 @@ describe(' - Editing', () => { }); it('should reset the input query state on an unfocused field', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, value: null }); view.selectSection('month'); @@ -2270,7 +2269,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false, value: null }); const input = getTextbox(); @@ -2313,7 +2312,7 @@ describe(' - Editing', () => { describeAdapters('Select all', DateField, ({ renderWithProps }) => { it('should edit the 1st section when all sections are selected', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); view.selectSection('month'); @@ -2331,7 +2330,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); view.selectSection('month'); const input = getTextbox(); 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 67c0df9d5edc..77b4ae01ba75 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 @@ -11,7 +11,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp it('should support escaped characters in start separator', () => { const { start: startChar, end: endChar } = adapter.escapedCharacters; - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, // For Day.js: "[Escaped] YYYY" @@ -24,7 +24,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ // For Day.js: "[Escaped] YYYY" format: `${startChar}Escaped${endChar} ${adapter.formats.year}`, @@ -40,7 +40,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp it('should support escaped characters between sections separator', () => { const { start: startChar, end: endChar } = adapter.escapedCharacters; - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, // For Day.js: "MMMM [Escaped] YYYY" @@ -54,7 +54,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ // For Day.js: "MMMM [Escaped] YYYY" format: `${adapter.formats.month} ${startChar}Escaped${endChar} ${adapter.formats.year}`, @@ -76,7 +76,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp this.skip(); } - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, // For Day.js: "MMMM [Escaped[] YYYY" @@ -90,7 +90,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ // For Day.js: "MMMM [Escaped[] YYYY" format: `${adapter.formats.month} ${startChar}Escaped ${startChar}${endChar} ${adapter.formats.year}`, @@ -107,7 +107,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp it('should support several escaped parts', () => { const { start: startChar, end: endChar } = adapter.escapedCharacters; - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, // For Day.js: "[Escaped] MMMM [Escaped] YYYY" @@ -121,7 +121,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ // For Day.js: "[Escaped] MMMM [Escaped] YYYY" format: `${startChar}Escaped${endChar} ${adapter.formats.month} ${startChar}Escaped${endChar} ${adapter.formats.year}`, @@ -138,7 +138,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp it('should support format with only escaped parts', function test() { const { start: startChar, end: endChar } = adapter.escapedCharacters; - // Test with v7 input + // Test with accessible DOM structure const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, // For Day.js: "[Escaped] [Escaped]" @@ -149,7 +149,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure renderWithProps({ // For Day.js: "[Escaped] [Escaped]" format: `${startChar}Escaped${endChar} ${startChar}Escaped${endChar}`, @@ -170,7 +170,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp }); it('should add spaces around `/` when `formatDensity = "spacious"`', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, formatDensity: `spacious`, @@ -183,7 +183,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ formatDensity: `spacious`, enableAccessibleFieldDOMStructure: false, @@ -197,7 +197,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp }); it('should add spaces around `.` when `formatDensity = "spacious"`', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, formatDensity: `spacious`, @@ -211,7 +211,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ formatDensity: `spacious`, format: adapter.expandFormat(adapter.formats.keyboardDate).replace(/\//g, '.'), @@ -226,7 +226,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp }); it('should add spaces around `-` when `formatDensity = "spacious"`', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, formatDensity: `spacious`, @@ -240,7 +240,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ formatDensity: `spacious`, format: adapter.expandFormat(adapter.formats.keyboardDate).replace(/\//g, '-'), 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 6ef20dec53f3..b59aba23f70d 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 @@ -17,7 +17,7 @@ describe(' - Selection', () => { describe('Focus', () => { it('should select 1st section (v7) / all sections (v6) on mount focus (`autoFocus = true`)', () => { - // Text with v7 input + // Test with accessible DOM structure const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, autoFocus: true, @@ -26,7 +26,7 @@ describe(' - Selection', () => { expect(getCleanedSelectedContent()).to.equal('MM'); view.unmount(); - // Text with v6 input + // Test with non-accessible DOM structure renderWithProps({ enableAccessibleFieldDOMStructure: false, autoFocus: true }); const input = getTextbox(); expectFieldValueV6(input, 'MM/DD/YYYY'); @@ -34,7 +34,7 @@ describe(' - Selection', () => { }); it('should select 1st section (v7) / all sections (v6) (`autoFocus = true`) with start separator', () => { - // Text with v7 input + // Test with accessible DOM structure const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, autoFocus: true, @@ -44,7 +44,7 @@ describe(' - Selection', () => { expect(getCleanedSelectedContent()).to.equal('YYYY'); view.unmount(); - // Text with v6 input + // Test with non-accessible DOM structure renderWithProps({ enableAccessibleFieldDOMStructure: false, autoFocus: true, @@ -56,7 +56,7 @@ describe(' - Selection', () => { }); it('should select all on focus (v6 only)', () => { - // Text with v6 input + // Test with non-accessible DOM structure renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); @@ -72,7 +72,7 @@ describe(' - Selection', () => { }); it('should select all on focus with start separator (v6 only)', () => { - // Text with v6 input + // Test with non-accessible DOM structure renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `- ${adapterToUse.formats.year}`, @@ -91,7 +91,7 @@ describe(' - Selection', () => { }); it('should select day on mobile (v6 only)', () => { - // Test with v6 input + // Test with non-accessible DOM structure renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); @@ -108,7 +108,7 @@ describe(' - Selection', () => { }); it('should select day on desktop (v6 only)', () => { - // Test with v6 input + // Test with non-accessible DOM structure const view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); @@ -121,7 +121,7 @@ describe(' - Selection', () => { describe('Click', () => { it('should select the clicked selection when the input is already focused', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); view.selectSection('day'); @@ -132,7 +132,7 @@ describe(' - Selection', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); view.selectSection('day'); @@ -143,7 +143,7 @@ describe(' - Selection', () => { }); it('should not change the selection when clicking on the only already selected section', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); view.selectSection('day'); @@ -154,7 +154,7 @@ describe(' - Selection', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); view.selectSection('day'); @@ -167,7 +167,7 @@ describe(' - Selection', () => { describe('key: Ctrl + A', () => { it('should select all sections', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); view.selectSection('month'); fireEvent.keyDown(view.getActiveSection(0), { @@ -179,7 +179,7 @@ describe(' - Selection', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); view.selectSection('month'); @@ -188,7 +188,7 @@ describe(' - Selection', () => { }); it('should select all sections with start separator', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: `- ${adapterToUse.formats.year}`, @@ -203,7 +203,7 @@ describe(' - Selection', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: `- ${adapterToUse.formats.year}`, @@ -217,7 +217,7 @@ describe(' - Selection', () => { describe('key: ArrowRight', () => { it('should move selection to the next section when one section is selected', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); @@ -225,7 +225,7 @@ describe(' - Selection', () => { expect(getCleanedSelectedContent()).to.equal('YYYY'); view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); view.selectSection('day'); @@ -235,7 +235,7 @@ describe(' - Selection', () => { }); it('should stay on the current section when the last section is selected', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); view.selectSection('year'); expect(getCleanedSelectedContent()).to.equal('YYYY'); @@ -243,7 +243,7 @@ describe(' - Selection', () => { expect(getCleanedSelectedContent()).to.equal('YYYY'); view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); view.selectSection('year'); @@ -253,7 +253,7 @@ describe(' - Selection', () => { }); it('should select the last section when all the sections are selected', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); view.selectSection('month'); @@ -270,7 +270,7 @@ describe(' - Selection', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); view.selectSection('month'); @@ -286,7 +286,7 @@ describe(' - Selection', () => { describe('key: ArrowLeft', () => { it('should move selection to the previous section when one section is selected', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); view.selectSection('day'); expect(getCleanedSelectedContent()).to.equal('DD'); @@ -294,7 +294,7 @@ describe(' - Selection', () => { expect(getCleanedSelectedContent()).to.equal('MM'); view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); view.selectSection('day'); @@ -304,7 +304,7 @@ describe(' - Selection', () => { }); it('should stay on the current section when the first section is selected', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); view.selectSection('month'); expect(getCleanedSelectedContent()).to.equal('MM'); @@ -312,7 +312,7 @@ describe(' - Selection', () => { expect(getCleanedSelectedContent()).to.equal('MM'); view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); view.selectSection('month'); @@ -322,7 +322,7 @@ describe(' - Selection', () => { }); it('should select the first section when all the sections are selected', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }); view.selectSection('month'); @@ -339,7 +339,7 @@ describe(' - Selection', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false }); const input = getTextbox(); view.selectSection('month'); diff --git a/packages/x-date-pickers/src/DatePicker/DatePicker.tsx b/packages/x-date-pickers/src/DatePicker/DatePicker.tsx index e939a07dbe88..d85567d1eae3 100644 --- a/packages/x-date-pickers/src/DatePicker/DatePicker.tsx +++ b/packages/x-date-pickers/src/DatePicker/DatePicker.tsx @@ -12,7 +12,7 @@ import { PickerValidDate } from '../models'; type DatePickerComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: DatePickerProps & React.RefAttributes, @@ -30,7 +30,7 @@ type DatePickerComponent = (< */ const DatePicker = React.forwardRef(function DatePicker< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: DatePickerProps, ref: React.Ref, @@ -115,7 +115,7 @@ DatePicker.propTypes = { */ displayWeekNumber: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.any, /** diff --git a/packages/x-date-pickers/src/DatePicker/DatePicker.types.ts b/packages/x-date-pickers/src/DatePicker/DatePicker.types.ts index 5a839bae4155..8ba281c9b024 100644 --- a/packages/x-date-pickers/src/DatePicker/DatePicker.types.ts +++ b/packages/x-date-pickers/src/DatePicker/DatePicker.types.ts @@ -30,7 +30,7 @@ export interface DatePickerSlotProps< export interface DatePickerProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > extends DesktopDatePickerProps, MobileDatePickerProps { /** @@ -61,7 +61,7 @@ export interface DatePickerProps< */ export type DatePickerFieldProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > = DefaultizedProps< UseDateFieldProps, 'format' | 'timezone' | keyof BaseDateValidationProps diff --git a/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx b/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx index 4f46b80c9709..3ad845d85883 100644 --- a/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx +++ b/packages/x-date-pickers/src/DatePicker/tests/DatePicker.test.tsx @@ -12,7 +12,7 @@ describe('', () => { const originalMatchMedia = window.matchMedia; window.matchMedia = stubMatchMedia(false); - render(); + render(); expect(screen.getByLabelText(/Choose date/)).to.have.class(pickersInputBaseClasses.input); diff --git a/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx b/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx index 311209132ddd..85fdbc0f272b 100644 --- a/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx +++ b/packages/x-date-pickers/src/DateTimeField/DateTimeField.tsx @@ -14,7 +14,7 @@ import { PickerValidDate } from '../models'; type DateTimeFieldComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: DateTimeFieldProps & React.RefAttributes, @@ -32,7 +32,7 @@ type DateTimeFieldComponent = (< */ const DateTimeField = React.forwardRef(function DateTimeField< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: DateTimeFieldProps, inRef: React.Ref, @@ -48,7 +48,7 @@ const DateTimeField = React.forwardRef(function DateTimeField< const TextField = slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure ? PickersTextField : MuiTextField); + (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); const textFieldProps = useSlotProps({ elementType: TextField, externalSlotProps: slotProps?.textField, @@ -133,7 +133,7 @@ DateTimeField.propTypes = { */ disablePast: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.bool, /** diff --git a/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts b/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts index 625f70532e55..f603981c1c95 100644 --- a/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts +++ b/packages/x-date-pickers/src/DateTimeField/DateTimeField.types.ts @@ -52,32 +52,28 @@ export interface UseDateTimeFieldProps< ampm?: boolean; } -export type UseDateTimeFieldComponentProps< - TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean, - TChildProps extends {}, -> = Omit> & - UseDateTimeFieldProps; - export type DateTimeFieldProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, -> = UseDateTimeFieldComponentProps< - TDate, - TEnableAccessibleFieldDOMStructure, - BuiltInFieldTextFieldProps -> & { - /** - * Overridable component slots. - * @default {} - */ - slots?: DateTimeFieldSlots; - /** - * The props used for each component slot. - * @default {} - */ - slotProps?: DateTimeFieldSlotProps; -}; + TEnableAccessibleFieldDOMStructure extends boolean = true, +> = + // The hook props + UseDateTimeFieldProps & + // The TextField props + Omit< + BuiltInFieldTextFieldProps, + keyof UseDateTimeFieldProps + > & { + /** + * Overridable component slots. + * @default {} + */ + slots?: DateTimeFieldSlots; + /** + * The props used for each component slot. + * @default {} + */ + slotProps?: DateTimeFieldSlotProps; + }; export type DateTimeFieldOwnerState< TDate extends PickerValidDate, diff --git a/packages/x-date-pickers/src/DateTimeField/index.ts b/packages/x-date-pickers/src/DateTimeField/index.ts index 95952dde9474..7f8882b9d04a 100644 --- a/packages/x-date-pickers/src/DateTimeField/index.ts +++ b/packages/x-date-pickers/src/DateTimeField/index.ts @@ -1,7 +1,3 @@ export { DateTimeField } from './DateTimeField'; export { useDateTimeField as unstable_useDateTimeField } from './useDateTimeField'; -export type { - UseDateTimeFieldProps, - UseDateTimeFieldComponentProps, - DateTimeFieldProps, -} from './DateTimeField.types'; +export type { UseDateTimeFieldProps, DateTimeFieldProps } from './DateTimeField.types'; diff --git a/packages/x-date-pickers/src/DateTimeField/tests/describes.DateTimeField.test.tsx b/packages/x-date-pickers/src/DateTimeField/tests/describes.DateTimeField.test.tsx index 57ee37114d1b..2ac6de801e45 100644 --- a/packages/x-date-pickers/src/DateTimeField/tests/describes.DateTimeField.test.tsx +++ b/packages/x-date-pickers/src/DateTimeField/tests/describes.DateTimeField.test.tsx @@ -21,7 +21,7 @@ describe(' - Describes', () => { componentFamily: 'field', })); - describeConformance(, () => ({ + describeConformance(, () => ({ classes: {} as any, inheritComponent: PickersTextField, render, diff --git a/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.tsx b/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.tsx index 5203009b6364..d18db93743ae 100644 --- a/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.tsx +++ b/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.tsx @@ -12,7 +12,7 @@ import { PickerValidDate } from '../models'; type DateTimePickerComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: DateTimePickerProps & React.RefAttributes, @@ -30,7 +30,7 @@ type DateTimePickerComponent = (< */ const DateTimePicker = React.forwardRef(function DateTimePicker< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: DateTimePickerProps, ref: React.Ref, @@ -130,7 +130,7 @@ DateTimePicker.propTypes = { */ displayWeekNumber: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.any, /** diff --git a/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.types.ts b/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.types.ts index 6ae28b8bc2fe..a26c276930b4 100644 --- a/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.types.ts +++ b/packages/x-date-pickers/src/DateTimePicker/DateTimePicker.types.ts @@ -36,7 +36,7 @@ export interface DateTimePickerSlotProps< export interface DateTimePickerProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > extends DesktopDateTimePickerProps, ExportedYearCalendarProps, Omit< @@ -76,7 +76,7 @@ export interface DateTimePickerProps< */ export type DateTimePickerFieldProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > = DefaultizedProps< UseDateTimeFieldProps, | 'format' diff --git a/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx b/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx index 94bfd89d1760..933f61c48f16 100644 --- a/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DateTimePicker/tests/DateTimePicker.test.tsx @@ -12,7 +12,7 @@ describe('', () => { const originalMatchMedia = window.matchMedia; window.matchMedia = stubMatchMedia(false); - render(); + render(); expect(screen.getByLabelText(/Choose date/)).to.have.class(pickersInputBaseClasses.input); diff --git a/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx b/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx index 3cb78020a8c0..5e6fedaeba68 100644 --- a/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx +++ b/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.tsx @@ -19,7 +19,7 @@ import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalizat type DesktopDatePickerComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: DesktopDatePickerProps & React.RefAttributes, @@ -37,7 +37,7 @@ type DesktopDatePickerComponent = (< */ const DesktopDatePicker = React.forwardRef(function DesktopDatePicker< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: DesktopDatePickerProps, ref: React.Ref, @@ -164,7 +164,7 @@ DesktopDatePicker.propTypes = { */ displayWeekNumber: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.any, /** diff --git a/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.types.ts b/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.types.ts index 340faa3f81cd..2f5d563c8047 100644 --- a/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.types.ts +++ b/packages/x-date-pickers/src/DesktopDatePicker/DesktopDatePicker.types.ts @@ -24,7 +24,7 @@ export interface DesktopDatePickerSlotProps< export interface DesktopDatePickerProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > extends BaseDatePickerProps, DesktopOnlyPickerProps, ExportedYearCalendarProps { 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 4ef2b7327fab..48deec371ed3 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 @@ -24,7 +24,7 @@ describe(' - Field', () => { }); it('should be able to reset a single section', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps( { enableAccessibleFieldDOMStructure: true as const, @@ -47,7 +47,7 @@ describe(' - Field', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps( { enableAccessibleFieldDOMStructure: false as const, @@ -72,7 +72,7 @@ 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 + // Test with accessible DOM structure let view = renderWithProps( { ...props, enableAccessibleFieldDOMStructure: true as const }, { componentFamily: 'picker' }, @@ -80,7 +80,7 @@ describe(' - Field', () => { expectFieldValueV7(view.getSectionsContainer(), expectedFormat); view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps( { ...props, enableAccessibleFieldDOMStructure: false as const }, { componentFamily: 'picker' }, diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx index 839da97030e5..c9d146bdf670 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx @@ -112,7 +112,7 @@ const rendererInterceptor = function rendererInterceptor< type DesktopDateTimePickerComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: DesktopDateTimePickerProps & React.RefAttributes, @@ -315,7 +315,7 @@ DesktopDateTimePicker.propTypes = { */ displayWeekNumber: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.any, /** diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.types.ts b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.types.ts index 2f64b71e5a0a..24d55d83dde6 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.types.ts +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.types.ts @@ -42,7 +42,7 @@ export interface DesktopDateTimePickerSlotProps< export interface DesktopDateTimePickerProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > extends BaseDateTimePickerProps, DesktopOnlyPickerProps, DesktopOnlyTimePickerProps, 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 c298769075bb..ea815c570641 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/DesktopDateTimePicker.test.tsx @@ -1,31 +1,31 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { screen } from '@mui/internal-test-utils'; +import { fireEvent, 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(); + const { render } = createPickerRenderer({ clock: 'fake' }); describe('picker state', () => { - it('should open when clicking "Choose date"', async () => { + it('should open when clicking "Choose date"', () => { const onOpen = spy(); - const { user } = render(); + render(); - await user.click(screen.getByLabelText(/Choose date/)); + fireEvent.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', async () => { + it('should call onAccept when selecting the same date and time after changing the year', () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); - const { user } = render( + render( ', () => { />, ); - await openPicker({ type: 'date-time', variant: 'desktop', click: user.click }); + openPicker({ type: 'date-time', variant: 'desktop' }); // Select year - await user.click(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, 11, 55)); expect(onAccept.callCount).to.equal(0); expect(onClose.callCount).to.equal(0); // Change the date (same value) - await user.click(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 // Change the hours (same value) - await user.click(screen.getByRole('option', { name: '11 hours' })); + fireEvent.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) - await user.click(screen.getByRole('option', { name: '55 minutes' })); + fireEvent.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) - await user.click(screen.getByRole('option', { name: 'AM' })); + fireEvent.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', async () => { + it('should allow selecting same view multiple times', () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); - const { user } = render( + render( ', () => { />, ); - await openPicker({ type: 'date-time', variant: 'desktop', click: user.click }); + openPicker({ type: 'date-time', variant: 'desktop' }); // Change the date multiple times to check that picker doesn't close after cycling through all views internally - 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' })); + fireEvent.click(screen.getByRole('gridcell', { name: '2' })); + fireEvent.click(screen.getByRole('gridcell', { name: '3' })); + fireEvent.click(screen.getByRole('gridcell', { name: '4' })); + fireEvent.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 - await user.click(screen.getByRole('option', { name: '10 hours' })); - await user.click(screen.getByRole('option', { name: '9 hours' })); + fireEvent.click(screen.getByRole('option', { name: '10 hours' })); + fireEvent.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 - await user.click(screen.getByRole('option', { name: '50 minutes' })); + fireEvent.click(screen.getByRole('option', { name: '50 minutes' })); expect(onChange.callCount).to.equal(7); // Change the meridiem - await user.click(screen.getByRole('option', { name: 'PM' })); + fireEvent.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', async () => { + it('should use "DigitalClock" view renderer, when "timeSteps.minutes" = 60', () => { const onChange = spy(); const onAccept = spy(); - const { user } = render( + render( ', () => { />, ); - await user.click(screen.getByLabelText(/Choose date/)); + fireEvent.click(screen.getByLabelText(/Choose date/)); - await user.click(screen.getByRole('gridcell', { name: '2' })); - await user.click(screen.getByRole('option', { name: '03:00 AM' })); + fireEvent.click(screen.getByRole('gridcell', { name: '2' })); + fireEvent.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', async () => { + it('should accept value and close picker when selecting time on "DigitalClock" view renderer', () => { const onChange = spy(); const onAccept = spy(); - const { user } = render( + render( ', () => { />, ); - await user.click(screen.getByLabelText(/Choose date/)); + fireEvent.click(screen.getByLabelText(/Choose date/)); - await user.click(screen.getByRole('option', { name: '03:00 AM' })); + fireEvent.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/field.DesktopDateTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/field.DesktopDateTimePicker.test.tsx index 54877db6fa12..37a7ca21c689 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 @@ -32,7 +32,7 @@ describe(' - Field', () => { it('should adapt the default field format based on the props of the picker', () => { const testFormat = (props: DesktopDateTimePickerProps, expectedFormat: string) => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps( { ...props, enableAccessibleFieldDOMStructure: true as const }, { componentFamily: 'picker' }, @@ -40,7 +40,7 @@ describe(' - Field', () => { expectFieldValueV7(view.getSectionsContainer(), expectedFormat); view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps( { ...props, enableAccessibleFieldDOMStructure: false as const }, { componentFamily: 'picker' }, diff --git a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx index 966c729b6365..98eb55cfc7d9 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx @@ -25,7 +25,7 @@ import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalizat type DesktopTimePickerComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: DesktopTimePickerProps & React.RefAttributes, @@ -43,7 +43,7 @@ type DesktopTimePickerComponent = (< */ const DesktopTimePicker = React.forwardRef(function DesktopTimePicker< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: DesktopTimePickerProps, ref: React.Ref, @@ -201,7 +201,7 @@ DesktopTimePicker.propTypes = { */ disablePast: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.any, /** diff --git a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.types.ts b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.types.ts index 77faf64730cf..73974cf1df82 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.types.ts +++ b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.types.ts @@ -38,7 +38,7 @@ export interface DesktopTimePickerSlotProps< export interface DesktopTimePickerProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > extends BaseTimePickerProps, DesktopOnlyPickerProps, DesktopOnlyTimePickerProps { 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 28663c2a7557..f6d1314d7694 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 } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { DesktopTimePicker } from '@mui/x-date-pickers/DesktopTimePicker'; import { adapterToUse, createPickerRenderer, openPicker } from 'test/utils/pickers'; describe('', () => { - describe('rendering behavior', () => { - const { render } = createPickerRenderer({ - clock: 'fake', - clockConfig: new Date('2018-01-01T10:05:05.000'), - }); + const { render } = createPickerRenderer({ + clock: 'fake', + clockConfig: new Date('2018-01-01T10:05:05.000'), + }); + describe('rendering behavior', () => { it('should render "accept" action and 3 time sections by default', () => { render(); @@ -60,14 +60,12 @@ describe('', () => { }); describe('selecting behavior', () => { - const { render } = createPickerRenderer(); - - it('should call "onAccept", "onChange", and "onClose" when selecting a single option', async () => { + it('should call "onAccept", "onChange", and "onClose" when selecting a single option', () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); - const { user } = render( + render( ', () => { />, ); - await openPicker({ type: 'time', variant: 'desktop', click: user.click }); + openPicker({ type: 'time', variant: 'desktop' }); - await user.click(screen.getByRole('option', { name: '09:00 AM' })); + fireEvent.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); @@ -87,12 +85,12 @@ describe('', () => { expect(onClose.callCount).to.equal(1); }); - it('should call "onAccept", "onChange", and "onClose" when selecting all section', async () => { + it('should call "onAccept", "onChange", and "onClose" when selecting all section', () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); - const { user } = render( + render( ', () => { />, ); - await openPicker({ type: 'time', variant: 'desktop', click: user.click }); + openPicker({ type: 'time', variant: 'desktop' }); - await user.click(screen.getByRole('option', { name: '2 hours' })); + fireEvent.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); - await user.click(screen.getByRole('option', { name: '15 minutes' })); + fireEvent.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); - await user.click(screen.getByRole('option', { name: 'PM' })); + fireEvent.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', async () => { + it('should allow out of order section selection', () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); - const { user } = render( + render( ', () => { />, ); - await openPicker({ type: 'time', variant: 'desktop', click: user.click }); + openPicker({ type: 'time', variant: 'desktop' }); - await user.click(screen.getByRole('option', { name: '15 minutes' })); + fireEvent.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); - await user.click(screen.getByRole('option', { name: '2 hours' })); + fireEvent.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); - await user.click(screen.getByRole('option', { name: '25 minutes' })); + fireEvent.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); - await user.click(screen.getByRole('option', { name: 'PM' })); + fireEvent.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', async () => { + it('should finish selection when selecting only the last section', () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); - const { user } = render( + render( ', () => { />, ); - await openPicker({ type: 'time', variant: 'desktop', click: user.click }); + openPicker({ type: 'time', variant: 'desktop' }); - await user.click(screen.getByRole('option', { name: 'PM' })); + fireEvent.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 7d4652b6c4df..8cb34d2fcf85 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 @@ -30,7 +30,7 @@ describe(' - Describes', () => { variant: 'desktop', })); - describeConformance(, () => ({ + describeConformance(, () => ({ classes: {} as any, render, muiName: 'MuiDesktopTimePicker', 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 88c95f981503..19a51f1dd600 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 @@ -29,7 +29,7 @@ describe(' - Field', () => { it('should adapt the default field format based on the props of the picker', () => { const testFormat = (props: DesktopTimePickerProps, expectedFormat: string) => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps( { ...props, enableAccessibleFieldDOMStructure: true as const }, { componentFamily: 'picker' }, @@ -37,7 +37,7 @@ describe(' - Field', () => { expectFieldValueV7(view.getSectionsContainer(), expectedFormat); view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps( { ...props, enableAccessibleFieldDOMStructure: false as const }, { componentFamily: 'picker' }, diff --git a/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx b/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx index e07706cf1863..3660a704f409 100644 --- a/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx +++ b/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx @@ -283,13 +283,14 @@ export const DigitalClock = React.forwardRef(function DigitalClock { + const result: TDate[] = []; const startOfDay = utils.startOfDay(valueOrReferenceDate); - return [ - startOfDay, - ...Array.from({ length: Math.ceil((24 * 60) / timeStep) - 1 }, (_, index) => - utils.addMinutes(startOfDay, timeStep * (index + 1)), - ), - ]; + let nextTimeStepOption = startOfDay; + while (utils.isSameDay(valueOrReferenceDate, nextTimeStepOption)) { + result.push(nextTimeStepOption); + nextTimeStepOption = utils.addMinutes(nextTimeStepOption, timeStep); + } + return result; }, [valueOrReferenceDate, timeStep, utils]); const focusedOptionIndex = timeOptions.findIndex((option) => @@ -348,10 +349,9 @@ export const DigitalClock = React.forwardRef(function DigitalClock !readOnly && handleItemSelect(option)} selected={isSelected} disabled={disabled || isTimeDisabled(option)} 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 3d6a353bf34d..88e23555c633 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 @@ -38,6 +38,71 @@ describe(' - Timezone', () => { expect(actualDate).toEqualDateTime(expectedDate); }); + it('should render correct time options when fall back DST occurs', () => { + render( + , + ); + const oneAM = adapter.setMinutes(adapter.setHours(adapter.date(undefined, 'default'), 1), 0); + const elevenPM = adapter.setMinutes( + adapter.setHours(adapter.date(undefined, 'default'), 23), + 0, + ); + expect( + screen.getAllByText( + adapter.format( + oneAM, + adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ), + ), + ).to.have.length(adapter.lib === 'dayjs' ? 1 : 2); + expect( + screen.getAllByText( + adapter.format( + elevenPM, + adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ), + ), + ).to.have.length(1); + }); + + it('should contain time options until the end of day when spring forward DST occurs', () => { + render( + , + ); + const startOfDay = adapter.setMinutes( + adapter.setHours(adapter.date(undefined, 'default'), 0), + 0, + ); + const eleven30PM = adapter.setMinutes( + adapter.setHours(adapter.date(undefined, 'default'), 23), + 30, + ); + expect( + screen.getAllByText( + adapter.format( + startOfDay, + adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ), + ), + ).to.have.length(1); + expect( + screen.getAllByText( + adapter.format( + eleven30PM, + adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ), + ), + ).to.have.length(1); + }); + TIMEZONE_TO_TEST.forEach((timezone) => { describe(`Timezone: ${timezone}`, () => { it('should use timezone prop for onChange when no value is provided', () => { diff --git a/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx b/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx index 32a33babcebc..4448fda1dfd7 100644 --- a/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx +++ b/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.tsx @@ -18,7 +18,7 @@ import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalizat type MobileDatePickerComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: MobileDatePickerProps & React.RefAttributes, @@ -36,7 +36,7 @@ type MobileDatePickerComponent = (< */ const MobileDatePicker = React.forwardRef(function MobileDatePicker< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: MobileDatePickerProps, ref: React.Ref, @@ -161,7 +161,7 @@ MobileDatePicker.propTypes = { */ displayWeekNumber: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.any, /** diff --git a/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.types.ts b/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.types.ts index b406fea447c3..84c9df58f2d0 100644 --- a/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.types.ts +++ b/packages/x-date-pickers/src/MobileDatePicker/MobileDatePicker.types.ts @@ -23,7 +23,7 @@ export interface MobileDatePickerSlotProps< export interface MobileDatePickerProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > extends BaseDatePickerProps, MobileOnlyPickerProps { /** 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 3f30960a217e..586d963955a2 100644 --- a/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDatePicker/tests/MobileDatePicker.test.tsx @@ -25,12 +25,7 @@ describe('', () => { it('allows to change only year', () => { const onChangeMock = spy(); render( - , + , ); fireEvent.click(screen.getByLabelText(/switch to year view/i)); @@ -43,7 +38,6 @@ describe('', () => { it('allows to select edge years from list', () => { render( ', () => { it('prop `onMonthChange` – dispatches callback when months switching', () => { const onMonthChangeMock = spy(); - render( - , - ); + render(); fireEvent.click(screen.getByLabelText('Next month')); expect(onMonthChangeMock.callCount).to.equal(1); }); it('prop `loading` – displays default loading indicator', () => { - render(); + render(); expect(screen.queryAllByTestId('day')).to.have.length(0); expect(screen.getByTestId('loading-progress')).toBeVisible(); @@ -76,7 +68,6 @@ describe('', () => { it('prop `renderLoading` – displays custom loading indicator', () => { render( } open @@ -91,7 +82,6 @@ describe('', () => { it('should render custom toolbar component', () => { render(
, @@ -105,7 +95,6 @@ describe('', () => { it('should format toolbar according to `toolbarFormat` prop', () => { render( ', () => { }); it('should render the toolbar when `hidden` is `false`', () => { - render( - , - ); + render(); expect(screen.getByTestId('picker-toolbar')).toBeVisible(); }); @@ -136,7 +119,6 @@ describe('', () => { it('should render custom day', () => { render( ', () => { it('should open when clicking the input', () => { const onOpen = spy(); - render(); + render(); fireEvent.click(getFieldSectionsContainer()); @@ -167,14 +149,7 @@ describe('', () => { function ControlledMobileDatePicker(props) { const [value, setValue] = React.useState(null); - return ( - - ); + return ; } render(); diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx b/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx index 24cddc84b97a..e8781d3a83c2 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.tsx @@ -22,7 +22,7 @@ import { buildGetOpenDialogAriaText } from '../locales/utils/getPickersLocalizat type MobileDateTimePickerComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: MobileDateTimePickerProps & React.RefAttributes, @@ -40,7 +40,7 @@ type MobileDateTimePickerComponent = (< */ const MobileDateTimePicker = React.forwardRef(function MobileDateTimePicker< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: MobileDateTimePickerProps, ref: React.Ref, @@ -191,7 +191,7 @@ MobileDateTimePicker.propTypes = { */ displayWeekNumber: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.any, /** diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.types.ts b/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.types.ts index ff38d040253e..1422063c0d67 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.types.ts +++ b/packages/x-date-pickers/src/MobileDateTimePicker/MobileDateTimePicker.types.ts @@ -28,7 +28,7 @@ export interface MobileDateTimePickerSlotProps< export interface MobileDateTimePickerProps< TDate extends PickerValidDate, TView extends DateOrTimeViewWithMeridiem = DateOrTimeView, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > extends BaseDateTimePickerProps, MobileOnlyPickerProps { /** 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 d12da995e405..da2eb24c177e 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx @@ -17,7 +17,6 @@ describe('', () => { it('should render date and time by default', () => { render( ', () => { }); it('should render toolbar and tabs by default', () => { - render( - , - ); + render(); expect(screen.queryByTestId('picker-toolbar-title')).not.to.equal(null); expect(screen.getByRole('tab', { name: 'pick date' })).not.to.equal(null); @@ -47,7 +40,6 @@ describe('', () => { it('can render seconds on view', () => { render( ', () => { it('should not render tabs when `hidden` is `true`', () => { render( ', () => { it('should not render only toolbar when `hidden` is `true`', () => { render( ', () => { it('should open when clicking the input', () => { const onOpen = spy(); - render(); + render(); fireEvent.click(getFieldSectionsContainer()); @@ -116,7 +106,6 @@ describe('', () => { render( ( props: MobileTimePickerProps & React.RefAttributes, @@ -36,7 +36,7 @@ type MobileTimePickerComponent = (< */ const MobileTimePicker = React.forwardRef(function MobileTimePicker< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: MobileTimePickerProps, ref: React.Ref, @@ -164,7 +164,7 @@ MobileTimePicker.propTypes = { */ disablePast: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.any, /** diff --git a/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.types.ts b/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.types.ts index a86ee69bf2bd..816cc7e0624c 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.types.ts +++ b/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.types.ts @@ -28,7 +28,7 @@ export interface MobileTimePickerSlotProps< export interface MobileTimePickerProps< TDate extends PickerValidDate, TView extends TimeViewWithMeridiem = TimeView, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > extends BaseTimePickerProps, MobileOnlyPickerProps { /** 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 d837030ef329..e5442cc50276 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 } from '@mui/internal-test-utils'; +import { fireEvent, fireTouchChangedEvent, screen } from '@mui/internal-test-utils'; import { MobileTimePicker } from '@mui/x-date-pickers/MobileTimePicker'; import { createPickerRenderer, @@ -12,27 +12,24 @@ import { } from 'test/utils/pickers'; describe('', () => { - const { render } = createPickerRenderer(); + const { render } = createPickerRenderer({ clock: 'fake' }); describe('picker state', () => { - it('should open when clicking the input', async () => { + it('should open when clicking the input', () => { const onOpen = spy(); - const { user } = render( - , - ); + render(); - await user.click(getFieldSectionsContainer()); + fireEvent.click(getFieldSectionsContainer()); expect(onOpen.callCount).to.equal(1); expect(screen.queryByRole('dialog')).toBeVisible(); }); - it('should fire a change event when meridiem changes', async () => { + it('should fire a change event when meridiem changes', () => { const handleChange = spy(); - const { user } = render( + render( ', () => { ); const buttonPM = screen.getByRole('button', { name: 'PM' }); - await user.click(buttonPM); + fireEvent.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', async function test() { + it('should call onChange when selecting each view', function test() { if (typeof window.Touch === 'undefined' || typeof window.TouchEvent === 'undefined') { this.skip(); } @@ -58,9 +55,8 @@ describe('', () => { const onClose = spy(); const defaultValue = adapterToUse.date('2018-01-01'); - const { user } = render( + render( ', () => { />, ); - await openPicker({ type: 'time', variant: 'mobile', click: user.click }); + openPicker({ type: 'time', variant: 'mobile' }); // 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 01807fb2c605..e84bc91b94ff 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 @@ -31,7 +31,7 @@ describe(' - Describes', () => { variant: 'mobile', })); - describeConformance(, () => ({ + describeConformance(, () => ({ classes: {} as any, render, muiName: 'MuiMobileTimePicker', diff --git a/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx b/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx index 58d3b35893ac..28d7e3026784 100644 --- a/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx +++ b/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; -import { screen } from '@mui/internal-test-utils'; +import { fireEvent, screen } from '@mui/internal-test-utils'; import { PickersActionBar } from '@mui/x-date-pickers/PickersActionBar'; import { createPickerRenderer } from 'test/utils/pickers'; describe('', () => { - const { render } = createPickerRenderer(); + const { render } = createPickerRenderer({ clock: 'fake' }); it('should not render buttons if actions array is empty', () => { const onAccept = () => {}; @@ -26,13 +26,13 @@ describe('', () => { expect(screen.queryByRole('button')).to.equal(null); }); - it('should render button for "clear" action calling the associated callback', async () => { + it('should render button for "clear" action calling the associated callback', () => { const onAccept = spy(); const onClear = spy(); const onCancel = spy(); const onSetToday = spy(); - const { user } = render( + render( ', () => { />, ); - await user.click(screen.getByText(/clear/i)); + fireEvent.click(screen.getByText(/clear/i)); expect(onClear.callCount).to.equal(1); }); - it('should render button for "cancel" action calling the associated callback', async () => { + it('should render button for "cancel" action calling the associated callback', () => { const onAccept = spy(); const onClear = spy(); const onCancel = spy(); const onSetToday = spy(); - const { user } = render( + render( ', () => { />, ); - await user.click(screen.getByText(/cancel/i)); + fireEvent.click(screen.getByText(/cancel/i)); expect(onCancel.callCount).to.equal(1); }); - it('should render button for "accept" action calling the associated callback', async () => { + it('should render button for "accept" action calling the associated callback', () => { const onAccept = spy(); const onClear = spy(); const onCancel = spy(); const onSetToday = spy(); - const { user } = render( + render( ', () => { />, ); - await user.click(screen.getByText(/ok/i)); + fireEvent.click(screen.getByText(/ok/i)); expect(onAccept.callCount).to.equal(1); }); - it('should render button for "today" action calling the associated callback', async () => { + it('should render button for "today" action calling the associated callback', () => { const onAccept = spy(); const onClear = spy(); const onCancel = spy(); const onSetToday = spy(); - const { user } = render( + render( ', () => { />, ); - await user.click(screen.getByText(/today/i)); + fireEvent.click(screen.getByText(/today/i)); expect(onSetToday.callCount).to.equal(1); }); diff --git a/packages/x-date-pickers/src/PickersLayout/PickersLayout.types.ts b/packages/x-date-pickers/src/PickersLayout/PickersLayout.types.ts index 69a3fac2cdc2..c63477c40062 100644 --- a/packages/x-date-pickers/src/PickersLayout/PickersLayout.types.ts +++ b/packages/x-date-pickers/src/PickersLayout/PickersLayout.types.ts @@ -12,7 +12,7 @@ import { ExportedPickersShortcutProps, PickersShortcuts, } from '../PickersShortcuts/PickersShortcuts'; -import { PickerValidDate } from '../models'; +import { PickerOwnerState, PickerValidDate } from '../models'; export interface ExportedPickersLayoutSlots< TValue, @@ -38,16 +38,9 @@ export interface ExportedPickersLayoutSlots< >; } -interface PickersLayoutActionBarOwnerState< - TValue, - TDate extends PickerValidDate, - TView extends DateOrTimeViewWithMeridiem, -> extends PickersLayoutProps { - wrapperVariant: WrapperVariant; -} - -interface PickersShortcutsOwnerState extends PickersShortcutsProps { +export interface PickersLayoutOwnerState extends PickerOwnerState { wrapperVariant: WrapperVariant; + isLandscape: boolean; } export interface ExportedPickersLayoutSlotProps< @@ -58,15 +51,11 @@ export interface ExportedPickersLayoutSlotProps< /** * Props passed down to the action bar component. */ - actionBar?: SlotComponentProps< - typeof PickersActionBar, - {}, - PickersLayoutActionBarOwnerState - >; + actionBar?: SlotComponentProps; /** * Props passed down to the shortcuts component. */ - shortcuts?: SlotComponentProps>; + shortcuts?: SlotComponentProps; /** * Props passed down to the layoutRoot component. */ diff --git a/packages/x-date-pickers/src/PickersLayout/usePickerLayout.tsx b/packages/x-date-pickers/src/PickersLayout/usePickerLayout.tsx index 7f9b8ad28ddf..aa0b068d1a67 100644 --- a/packages/x-date-pickers/src/PickersLayout/usePickerLayout.tsx +++ b/packages/x-date-pickers/src/PickersLayout/usePickerLayout.tsx @@ -3,12 +3,13 @@ import * as React from 'react'; import useSlotProps from '@mui/utils/useSlotProps'; import composeClasses from '@mui/utils/composeClasses'; import { PickersActionBar, PickersActionBarAction } from '../PickersActionBar'; -import { PickersLayoutProps, SubComponents } from './PickersLayout.types'; -import { getPickersLayoutUtilityClass } from './pickersLayoutClasses'; +import { PickersLayoutOwnerState, PickersLayoutProps, SubComponents } from './PickersLayout.types'; +import { getPickersLayoutUtilityClass, PickersLayoutClasses } from './pickersLayoutClasses'; import { PickersShortcuts } from '../PickersShortcuts'; import { BaseToolbarProps } from '../internals/models/props/toolbar'; import { DateOrTimeViewWithMeridiem } from '../internals/models'; import { PickerValidDate } from '../models'; +import { usePickersPrivateContext } from '../internals/hooks/usePickersPrivateContext'; function toolbarHasView( toolbarProps: BaseToolbarProps | any, @@ -16,8 +17,11 @@ function toolbarHasView( return toolbarProps.view !== null; } -const useUtilityClasses = (ownerState: PickersLayoutProps) => { - const { classes, isLandscape } = ownerState; +const useUtilityClasses = ( + classes: Partial | undefined, + ownerState: PickersLayoutOwnerState, +) => { + const { isLandscape } = ownerState; const slots = { root: ['root', isLandscape && 'landscape'], contentWrapper: ['contentWrapper'], @@ -47,6 +51,8 @@ const usePickerLayout = < >( props: PickersLayoutProps, ): UsePickerLayoutResponse => { + const { ownerState: pickersOwnerState } = usePickersPrivateContext(); + const { wrapperVariant, onAccept, @@ -66,13 +72,19 @@ const usePickerLayout = < children, slots, slotProps, + classes: classesProp, // TODO: Remove this "as" hack. It get introduced to mark `value` prop in PickersLayoutProps as not required. // The true type should be // - For pickers value: TDate | null // - For range pickers value: [TDate | null, TDate | null] } = props as PickersLayoutPropsWithValueRequired; - const classes = useUtilityClasses(props); + const ownerState: PickersLayoutOwnerState = { + ...pickersOwnerState, + wrapperVariant, + isLandscape, + }; + const classes = useUtilityClasses(classesProp, ownerState); // Action bar const ActionBar = slots?.actionBar ?? PickersActionBar; @@ -88,7 +100,7 @@ const usePickerLayout = < wrapperVariant === 'desktop' ? [] : (['cancel', 'accept'] as PickersActionBarAction[]), }, className: classes.actionBar, - ownerState: { ...props, wrapperVariant }, + ownerState, }); const actionBar = ; @@ -108,7 +120,7 @@ const usePickerLayout = < readOnly, }, className: classes.toolbar, - ownerState: { ...props, wrapperVariant }, + ownerState, }); const toolbar = toolbarHasView(toolbarProps) && !!Toolbar ? : null; @@ -133,12 +145,7 @@ const usePickerLayout = < onChange: onSelectShortcut, }, className: classes.shortcuts, - ownerState: { - isValid, - isLandscape, - onChange: onSelectShortcut, - wrapperVariant, - }, + ownerState, }); const shortcuts = view && !!Shortcuts ? : null; diff --git a/packages/x-date-pickers/src/TimeField/TimeField.tsx b/packages/x-date-pickers/src/TimeField/TimeField.tsx index 9dd69bdf8a68..6471420f1630 100644 --- a/packages/x-date-pickers/src/TimeField/TimeField.tsx +++ b/packages/x-date-pickers/src/TimeField/TimeField.tsx @@ -14,7 +14,7 @@ import { PickerValidDate } from '../models'; type TimeFieldComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: TimeFieldProps & React.RefAttributes, @@ -32,7 +32,7 @@ type TimeFieldComponent = (< */ const TimeField = React.forwardRef(function TimeField< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: TimeFieldProps, inRef: React.Ref, @@ -48,7 +48,7 @@ const TimeField = React.forwardRef(function TimeField< const TextField = slots?.textField ?? - (inProps.enableAccessibleFieldDOMStructure ? PickersTextField : MuiTextField); + (inProps.enableAccessibleFieldDOMStructure === false ? MuiTextField : PickersTextField); const textFieldProps = useSlotProps({ elementType: TextField, externalSlotProps: slotProps?.textField, @@ -133,7 +133,7 @@ TimeField.propTypes = { */ disablePast: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.bool, /** diff --git a/packages/x-date-pickers/src/TimeField/TimeField.types.ts b/packages/x-date-pickers/src/TimeField/TimeField.types.ts index 478cf08ee9ee..03d2c63932c3 100644 --- a/packages/x-date-pickers/src/TimeField/TimeField.types.ts +++ b/packages/x-date-pickers/src/TimeField/TimeField.types.ts @@ -39,32 +39,28 @@ export interface UseTimeFieldProps< ampm?: boolean; } -export type UseTimeFieldComponentProps< - TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean, - TChildProps extends {}, -> = Omit> & - UseTimeFieldProps; - export type TimeFieldProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, -> = UseTimeFieldComponentProps< - TDate, - TEnableAccessibleFieldDOMStructure, - BuiltInFieldTextFieldProps -> & { - /** - * Overridable component slots. - * @default {} - */ - slots?: TimeFieldSlots; - /** - * The props used for each component slot. - * @default {} - */ - slotProps?: TimeFieldSlotProps; -}; + TEnableAccessibleFieldDOMStructure extends boolean = true, +> = + // The hook props + UseTimeFieldProps & + // The TextField props + Omit< + BuiltInFieldTextFieldProps, + keyof UseTimeFieldProps + > & { + /** + * Overridable component slots. + * @default {} + */ + slots?: TimeFieldSlots; + /** + * The props used for each component slot. + * @default {} + */ + slotProps?: TimeFieldSlotProps; + }; export type TimeFieldOwnerState< TDate extends PickerValidDate, diff --git a/packages/x-date-pickers/src/TimeField/index.ts b/packages/x-date-pickers/src/TimeField/index.ts index f335f0f8fd76..b721d815213c 100644 --- a/packages/x-date-pickers/src/TimeField/index.ts +++ b/packages/x-date-pickers/src/TimeField/index.ts @@ -1,7 +1,3 @@ export { TimeField } from './TimeField'; export { useTimeField as unstable_useTimeField } from './useTimeField'; -export type { - UseTimeFieldProps, - UseTimeFieldComponentProps, - TimeFieldProps, -} from './TimeField.types'; +export type { UseTimeFieldProps, TimeFieldProps } from './TimeField.types'; diff --git a/packages/x-date-pickers/src/TimeField/tests/describes.TimeField.test.tsx b/packages/x-date-pickers/src/TimeField/tests/describes.TimeField.test.tsx index 80f331774005..88f95a134af9 100644 --- a/packages/x-date-pickers/src/TimeField/tests/describes.TimeField.test.tsx +++ b/packages/x-date-pickers/src/TimeField/tests/describes.TimeField.test.tsx @@ -22,7 +22,7 @@ describe(' - Describes', () => { componentFamily: 'field', })); - describeConformance(, () => ({ + describeConformance(, () => ({ classes: {} as any, inheritComponent: PickersTextField, render, 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 f1c4c0811bd3..e38354e925cd 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 @@ -496,7 +496,7 @@ describe(' - Editing', () => { }); it('should go to the next section when pressing `2` in a 12-hours format', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: adapter.formats.fullTime12h, @@ -510,7 +510,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: adapter.formats.fullTime12h, @@ -526,7 +526,7 @@ describe(' - Editing', () => { }); it('should go to the next section when pressing `1` then `3` in a 12-hours format', () => { - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, format: adapter.formats.fullTime12h, @@ -545,7 +545,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false, format: adapter.formats.fullTime12h, @@ -635,7 +635,7 @@ describe(' - Editing', () => { TimeField, ({ adapter, renderWithProps }) => { it('should not loose date information when a value is provided', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -651,7 +651,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ @@ -668,7 +668,7 @@ describe(' - Editing', () => { }); it('should not loose date information when cleaning the date then filling it again', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -696,7 +696,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ @@ -721,7 +721,7 @@ describe(' - Editing', () => { }); it('should not loose time information when using the hour format and value is provided', () => { - // Test with v7 input + // Test with accessible DOM structure const onChangeV7 = spy(); let view = renderWithProps({ @@ -738,7 +738,7 @@ describe(' - Editing', () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const onChangeV6 = spy(); view = renderWithProps({ diff --git a/packages/x-date-pickers/src/TimePicker/TimePicker.tsx b/packages/x-date-pickers/src/TimePicker/TimePicker.tsx index b6749e642eba..30fcfd7e9ebc 100644 --- a/packages/x-date-pickers/src/TimePicker/TimePicker.tsx +++ b/packages/x-date-pickers/src/TimePicker/TimePicker.tsx @@ -12,7 +12,7 @@ import { PickerValidDate } from '../models'; type TimePickerComponent = (< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( props: TimePickerProps & React.RefAttributes, @@ -30,7 +30,7 @@ type TimePickerComponent = (< */ const TimePicker = React.forwardRef(function TimePicker< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, >( inProps: TimePickerProps, ref: React.Ref, @@ -114,7 +114,7 @@ TimePicker.propTypes = { */ disablePast: PropTypes.bool, /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure: PropTypes.any, /** diff --git a/packages/x-date-pickers/src/TimePicker/TimePicker.types.ts b/packages/x-date-pickers/src/TimePicker/TimePicker.types.ts index ea7f4fd343b1..a5ba2479c1e8 100644 --- a/packages/x-date-pickers/src/TimePicker/TimePicker.types.ts +++ b/packages/x-date-pickers/src/TimePicker/TimePicker.types.ts @@ -31,7 +31,7 @@ export interface TimePickerSlotProps< export interface TimePickerProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > extends DesktopTimePickerProps, Omit< MobileTimePickerProps, @@ -61,7 +61,7 @@ export interface TimePickerProps< */ export type TimePickerFieldProps< TDate extends PickerValidDate, - TEnableAccessibleFieldDOMStructure extends boolean = false, + TEnableAccessibleFieldDOMStructure extends boolean = true, > = DefaultizedProps< UseTimeFieldProps, 'format' | 'timezone' | 'ampm' | keyof BaseTimeValidationProps diff --git a/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx b/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx index 11a7ba2bd055..aaa91fd391fa 100644 --- a/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx +++ b/packages/x-date-pickers/src/TimePicker/tests/TimePicker.test.tsx @@ -12,7 +12,7 @@ describe('', () => { const originalMatchMedia = window.matchMedia; window.matchMedia = stubMatchMedia(false); - render(); + render(); expect(screen.getByLabelText(/Choose time/)).to.have.class(pickersInputBaseClasses.input); diff --git a/packages/x-date-pickers/src/internals/components/PickersProvider.tsx b/packages/x-date-pickers/src/internals/components/PickersProvider.tsx index 8dd0654a846c..758fe02252f3 100644 --- a/packages/x-date-pickers/src/internals/components/PickersProvider.tsx +++ b/packages/x-date-pickers/src/internals/components/PickersProvider.tsx @@ -1,10 +1,12 @@ import * as React from 'react'; -import { PickerValidDate } from '../../models'; +import { PickerOwnerState, PickerValidDate } from '../../models'; import { PickersInputLocaleText } from '../../locales'; import { LocalizationProvider } from '../../LocalizationProvider'; export const PickersContext = React.createContext(null); +export const PickersPrivateContext = React.createContext(null); + /** * Provides the context for the various parts of a picker component: * - contextValue: the context for the picker sub-components. @@ -12,20 +14,21 @@ export const PickersContext = React.createContext(nu * * @ignore - do not document. */ -export function PickersProvider( - props: PickersFieldProviderProps, -) { - const { contextValue, localeText, children } = props; +export function PickersProvider(props: PickersProviderProps) { + const { contextValue, privateContextValue, localeText, children } = props; return ( - {children} + + {children} + ); } -interface PickersFieldProviderProps { +export interface PickersProviderProps { contextValue: PickersContextValue; + privateContextValue: PickersPrivateContextValue; localeText: PickersInputLocaleText | undefined; children: React.ReactNode; } @@ -46,3 +49,9 @@ export interface PickersContextValue { */ open: boolean; } +export interface PickersPrivateContextValue { + /** + * The ownerState of the picker. + */ + ownerState: PickerOwnerState; +} diff --git a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx index a874ca8cd3f1..c564adee2636 100644 --- a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx @@ -18,6 +18,7 @@ import { FieldRef, BaseSingleInputFieldProps, InferError, + PickerOwnerState, } from '../../../models'; import { DateOrTimeViewWithMeridiem } from '../../models'; import { PickersProvider } from '../../components/PickersProvider'; @@ -76,15 +77,16 @@ export const useDesktopPicker = < actions, hasUIView, layoutProps, + providerProps, renderCurrentView, shouldRestoreFocus, fieldProps: pickerFieldProps, - contextValue, ownerState, } = usePicker({ ...pickerParams, props, fieldRef, + localeText, autoFocusView: true, additionalViewProps: {}, wrapperVariant: 'desktop', @@ -97,7 +99,7 @@ export const useDesktopPicker = < additionalProps: { position: 'end' as const, }, - ownerState: props, + ownerState, }); const OpenPickerButton = slots.openPickerButton ?? IconButton; @@ -110,7 +112,7 @@ export const useDesktopPicker = < 'aria-label': getOpenDialogAriaText(pickerFieldProps.value), edge: inputAdornmentProps.position, }, - ownerState: props, + ownerState, }); const OpenPickerIcon = slots.openPickerIcon; @@ -133,7 +135,7 @@ export const useDesktopPicker = < InferError > >, - TExternalProps + PickerOwnerState >({ elementType: Field, externalSlotProps: innerSlotProps?.field, @@ -156,7 +158,7 @@ export const useDesktopPicker = < focused: open ? true : undefined, ...(inputRef ? { inputRef } : {}), }, - ownerState: props, + ownerState, }); // TODO: Move to `useSlotProps` when https://github.com/mui/material-ui/pull/35088 will be merged @@ -208,7 +210,7 @@ export const useDesktopPicker = < const handleFieldRef = useForkRef(fieldRef, fieldProps.unstableFieldRef); const renderPicker = () => ( - + , {}, - UsePickerProps + PickerOwnerState >; textField?: SlotComponentProps>; - inputAdornment?: Partial; - openPickerButton?: SlotComponentProps< - typeof IconButton, - {}, - UseDesktopPickerProps - >; - openPickerIcon?: SlotComponentPropsFromProps< - Record, - {}, - PickerOwnerState - >; + inputAdornment?: SlotComponentPropsFromProps; + openPickerButton?: SlotComponentProps; + openPickerIcon?: SlotComponentPropsFromProps, {}, PickerOwnerState>; } export interface DesktopOnlyPickerProps diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts index c271f2a4f42b..6fb6529b949e 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.ts @@ -54,7 +54,7 @@ export const useField = < internalProps: { unstableFieldRef, minutesStep, - enableAccessibleFieldDOMStructure = false, + enableAccessibleFieldDOMStructure = true, disabled = false, readOnly = false, }, diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts b/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts index e558a7b3b7ee..4b3535b1bff2 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useField.types.ts @@ -124,7 +124,7 @@ export interface UseFieldInternalProps< */ unstableFieldRef?: React.Ref>; /** - * @default false + * @default true */ enableAccessibleFieldDOMStructure?: TEnableAccessibleFieldDOMStructure; /** diff --git a/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts b/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts index 0e87524ab21e..735f80229ed9 100644 --- a/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts +++ b/packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts @@ -108,7 +108,7 @@ export const useFieldState = < onSelectedSectionsChange, shouldRespectLeadingZeros = false, timezone: timezoneProp, - enableAccessibleFieldDOMStructure = false, + enableAccessibleFieldDOMStructure = true, }, } = params; diff --git a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx index 89108c2cb621..7fce7070a4db 100644 --- a/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useMobilePicker/useMobilePicker.tsx @@ -17,6 +17,7 @@ import { PickerValidDate, FieldRef, InferError, + PickerOwnerState, } from '../../../models'; import { DateOrTimeViewWithMeridiem } from '../../models'; import { PickersProvider } from '../../components/PickersProvider'; @@ -71,13 +72,15 @@ export const useMobilePicker = < open, actions, layoutProps, + providerProps, renderCurrentView, fieldProps: pickerFieldProps, - contextValue, + ownerState, } = usePicker({ ...pickerParams, props, fieldRef, + localeText, autoFocusView: true, additionalViewProps: {}, wrapperVariant: 'mobile', @@ -96,7 +99,7 @@ export const useMobilePicker = < InferError > >, - TExternalProps + PickerOwnerState >({ elementType: Field, externalSlotProps: innerSlotProps?.field, @@ -121,7 +124,7 @@ export const useMobilePicker = < name, ...(inputRef ? { inputRef } : {}), }, - ownerState: props, + ownerState, }); // TODO: Move to `useSlotProps` when https://github.com/mui/material-ui/pull/35088 will be merged @@ -160,7 +163,7 @@ export const useMobilePicker = < const handleFieldRef = useForkRef(fieldRef, fieldProps.unstableFieldRef); const renderPicker = () => ( - + , {}, - UsePickerProps + PickerOwnerState >; textField?: SlotComponentProps>; } diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts index 45c351390d05..4a9aeafff9d3 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts @@ -6,6 +6,7 @@ import { usePickerLayoutProps } from './usePickerLayoutProps'; import { FieldSection, PickerValidDate, InferError } from '../../../models'; import { DateOrTimeViewWithMeridiem } from '../../models'; import { usePickerOwnerState } from './usePickerOwnerState'; +import { usePickerProvider } from './usePickerProvider'; export const usePicker = < TValue, @@ -24,6 +25,7 @@ export const usePicker = < autoFocusView, rendererInterceptor, fieldRef, + localeText, }: UsePickerParams< TValue, TDate, @@ -31,7 +33,7 @@ export const usePicker = < TSection, TExternalProps, TAdditionalProps ->): UsePickerResponse> => { +>): UsePickerResponse> => { if (process.env.NODE_ENV !== 'production') { if ((props as any).renderInput != null) { warnOnce([ @@ -72,7 +74,13 @@ export const usePicker = < propsFromPickerViews: pickerViewsResponse.layoutProps, }); - const pickerOwnerState = usePickerOwnerState({ props, pickerValueResponse }); + const pickerOwnerState = usePickerOwnerState({ props, pickerValueResponse, valueManager }); + + const providerProps = usePickerProvider({ + pickerValueResponse, + ownerState: pickerOwnerState, + localeText, + }); return { // Picker value @@ -88,8 +96,8 @@ export const usePicker = < // Picker layout layoutProps: pickerLayoutResponse.layoutProps, - // Picker context - contextValue: pickerValueResponse.contextValue, + // Picker provider + providerProps, // Picker owner state ownerState: pickerOwnerState, diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts index 5bc23631a90d..2c3759d734d0 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts @@ -13,6 +13,7 @@ import { import { UsePickerLayoutProps, UsePickerLayoutPropsResponse } from './usePickerLayoutProps'; import { FieldSection, PickerOwnerState, PickerValidDate } from '../../../models'; import { DateOrTimeViewWithMeridiem } from '../../models'; +import { UsePickerProviderParameters, UsePickerProviderReturnValue } from './usePickerProvider'; /** * Props common to all picker headless implementations. @@ -53,17 +54,20 @@ export interface UsePickerParams< Pick< UsePickerViewParams, 'additionalViewProps' | 'autoFocusView' | 'rendererInterceptor' | 'fieldRef' - > { + >, + Pick, 'localeText'> { props: TExternalProps; } export interface UsePickerResponse< TValue, + TDate extends PickerValidDate, TView extends DateOrTimeViewWithMeridiem, TSection extends FieldSection, TError, > extends Omit, 'viewProps' | 'layoutProps'>, Omit, 'layoutProps'>, UsePickerLayoutPropsResponse { - ownerState: PickerOwnerState; + ownerState: PickerOwnerState; + providerProps: UsePickerProviderReturnValue; } diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerOwnerState.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerOwnerState.ts index 07b5fbfccebb..7fe68d2d836b 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerOwnerState.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerOwnerState.ts @@ -1,25 +1,40 @@ import * as React from 'react'; import { FieldSection, PickerOwnerState } from '../../../models'; import type { UsePickerProps } from './usePicker.types'; -import { UsePickerValueResponse } from './usePickerValue.types'; +import { PickerValueManager, UsePickerValueResponse } from './usePickerValue.types'; +import { useUtils } from '../useUtils'; interface UsePickerOwnerStateParameters { props: UsePickerProps; pickerValueResponse: UsePickerValueResponse; + valueManager: PickerValueManager; } export function usePickerOwnerState( parameters: UsePickerOwnerStateParameters, -): PickerOwnerState { - const { props, pickerValueResponse } = parameters; +): PickerOwnerState { + const { props, pickerValueResponse, valueManager } = parameters; + + const utils = useUtils(); return React.useMemo( () => ({ - value: pickerValueResponse.viewProps.value, - open: pickerValueResponse.open, - disabled: props.disabled ?? false, - readOnly: props.readOnly ?? false, + isPickerValueEmpty: valueManager.areValuesEqual( + utils, + pickerValueResponse.viewProps.value, + valueManager.emptyValue, + ), + isPickerOpen: pickerValueResponse.open, + isPickerDisabled: props.disabled ?? false, + isPickerReadOnly: props.readOnly ?? false, }), - [pickerValueResponse.viewProps.value, pickerValueResponse.open, props.disabled, props.readOnly], + [ + utils, + valueManager, + pickerValueResponse.viewProps.value, + pickerValueResponse.open, + props.disabled, + props.readOnly, + ], ); } diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts new file mode 100644 index 000000000000..3bff7c032e67 --- /dev/null +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts @@ -0,0 +1,47 @@ +import * as React from 'react'; +import { FieldSection, PickerOwnerState, PickerValidDate } from '../../../models'; +import { UsePickerValueResponse } from './usePickerValue.types'; +import { + PickersProviderProps, + PickersContextValue, + PickersPrivateContextValue, +} from '../../components/PickersProvider'; + +export interface UsePickerProviderParameters + extends Pick, 'localeText'> { + pickerValueResponse: UsePickerValueResponse; + ownerState: PickerOwnerState; +} + +export interface UsePickerProviderReturnValue + extends Omit, 'children'> {} + +export function usePickerProvider( + parameters: UsePickerProviderParameters, +): UsePickerProviderReturnValue { + const { pickerValueResponse, ownerState, localeText } = parameters; + + const contextValue = React.useMemo( + () => ({ + onOpen: pickerValueResponse.actions.onOpen, + onClose: pickerValueResponse.actions.onClose, + open: pickerValueResponse.open, + }), + [ + pickerValueResponse.actions.onOpen, + pickerValueResponse.actions.onClose, + pickerValueResponse.open, + ], + ); + + const privateContextValue = React.useMemo( + () => ({ ownerState }), + [ownerState], + ); + + return { + localeText, + contextValue, + privateContextValue, + }; +} diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts index ae7553494e0a..8d6255b5f562 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts @@ -28,7 +28,6 @@ import { PickerValueUpdaterParams, } from './usePickerValue.types'; import { useValueWithTimezone } from '../useValueWithTimezone'; -import { PickersContextValue } from '../../components/PickersProvider'; /** * Decide if the new value should be published @@ -458,21 +457,11 @@ export const usePickerValue = < isValid, }; - const contextValue = React.useMemo( - () => ({ - onOpen: handleOpen, - onClose: handleClose, - open: isOpen, - }), - [isOpen, handleClose, handleOpen], - ); - return { open: isOpen, fieldProps: fieldResponse, viewProps: viewResponse, layoutProps: layoutResponse, actions, - contextValue, }; }; diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts index 5b5952a1d3d0..a2882f62f57f 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts @@ -17,7 +17,6 @@ import { PickerShortcutChangeImportance, PickersShortcutsItemContext, } from '../../../PickersShortcuts'; -import { PickersContextValue } from '../../components/PickersProvider'; export interface PickerValueManager { /** @@ -328,5 +327,4 @@ export interface UsePickerValueResponse; fieldProps: UsePickerValueFieldResponse; layoutProps: UsePickerValueLayoutResponse; - contextValue: PickersContextValue; } diff --git a/packages/x-date-pickers/src/internals/hooks/usePickersPrivateContext.ts b/packages/x-date-pickers/src/internals/hooks/usePickersPrivateContext.ts new file mode 100644 index 000000000000..9d03e894944a --- /dev/null +++ b/packages/x-date-pickers/src/internals/hooks/usePickersPrivateContext.ts @@ -0,0 +1,17 @@ +'use client'; +import * as React from 'react'; +import { PickersContext, PickersPrivateContextValue } from '../components/PickersProvider'; + +/** + * Returns the private context passed by the picker that wraps the current component. + */ +export const usePickersPrivateContext = () => { + const value = React.useContext(PickersContext) as PickersPrivateContextValue | null; + if (value == null) { + throw new Error( + 'MUI X: The `usePickersPrivateContext` can only be called in components that are used inside a picker component.', + ); + } + + return value; +}; diff --git a/packages/x-date-pickers/src/internals/hooks/useStaticPicker/useStaticPicker.tsx b/packages/x-date-pickers/src/internals/hooks/useStaticPicker/useStaticPicker.tsx index 08873f535dd2..9d06d0e630c3 100644 --- a/packages/x-date-pickers/src/internals/hooks/useStaticPicker/useStaticPicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useStaticPicker/useStaticPicker.tsx @@ -3,7 +3,7 @@ import clsx from 'clsx'; import { styled } from '@mui/material/styles'; import { UseStaticPickerParams, UseStaticPickerProps } from './useStaticPicker.types'; import { usePicker } from '../usePicker'; -import { LocalizationProvider } from '../../../LocalizationProvider'; +import { PickersProvider } from '../../components/PickersProvider'; import { PickersLayout } from '../../../PickersLayout'; import { DIALOG_WIDTH } from '../../constants/dimensions'; import { FieldSection, PickerValidDate } from '../../../models'; @@ -32,7 +32,7 @@ export const useStaticPicker = < }: UseStaticPickerParams) => { const { localeText, slots, slotProps, className, sx, displayStaticWrapperAs, autoFocus } = props; - const { layoutProps, renderCurrentView } = usePicker< + const { layoutProps, providerProps, renderCurrentView } = usePicker< TDate | null, TDate, TView, @@ -44,6 +44,7 @@ export const useStaticPicker = < props, autoFocusView: autoFocus ?? false, fieldRef: undefined, + localeText, additionalViewProps: {}, wrapperVariant: displayStaticWrapperAs, }); @@ -51,7 +52,7 @@ export const useStaticPicker = < const Layout = slots?.layout ?? PickerStaticLayout; const renderPicker = () => ( - + {renderCurrentView()} - + ); return { renderPicker }; diff --git a/packages/x-date-pickers/src/models/pickers.ts b/packages/x-date-pickers/src/models/pickers.ts index c15c2e5d1e71..d255b1519e29 100644 --- a/packages/x-date-pickers/src/models/pickers.ts +++ b/packages/x-date-pickers/src/models/pickers.ts @@ -15,21 +15,21 @@ export type PickerValidDate = keyof PickerValidDateLookup extends never ? any : PickerValidDateLookup[keyof PickerValidDateLookup]; -export interface PickerOwnerState { +export interface PickerOwnerState { /** - * The value currently displayed in the field and in the view. + * `true` if the value is currently empty. */ - value: TValue; + isPickerValueEmpty: boolean; /** * `true` if the picker is open, `false` otherwise. */ - open: boolean; + isPickerOpen: boolean; /** * `true` if the picker is disabled, `false` otherwise. */ - disabled: boolean; + isPickerDisabled: boolean; /** * `true` if the picker is read-only, `false` otherwise. */ - readOnly: boolean; + isPickerReadOnly: boolean; } diff --git a/packages/x-date-pickers/src/tests/fieldKeyboardInteraction.test.tsx b/packages/x-date-pickers/src/tests/fieldKeyboardInteraction.test.tsx index 3d7436f158ea..ee3c5b235f79 100644 --- a/packages/x-date-pickers/src/tests/fieldKeyboardInteraction.test.tsx +++ b/packages/x-date-pickers/src/tests/fieldKeyboardInteraction.test.tsx @@ -74,7 +74,7 @@ describe(`RTL - test arrows navigation`, () => { it('should move selected section to the next section respecting RTL order in empty field', () => { const expectedValues = ['hh', 'mm', 'YYYY', 'MM', 'DD', 'DD']; - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }, { direction: 'rtl' }); view.selectSection('hours'); @@ -86,7 +86,7 @@ describe(`RTL - test arrows navigation`, () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false }, { direction: 'rtl' }); const input = getTextbox(); @@ -101,7 +101,7 @@ describe(`RTL - test arrows navigation`, () => { it('should move selected section to the previous section respecting RTL order in empty field', () => { const expectedValues = ['DD', 'MM', 'YYYY', 'mm', 'hh', 'hh']; - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true }, { direction: 'rtl' }); view.selectSection('day'); @@ -113,7 +113,7 @@ describe(`RTL - test arrows navigation`, () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps({ enableAccessibleFieldDOMStructure: false }, { direction: 'rtl' }); const input = getTextbox(); @@ -129,7 +129,7 @@ describe(`RTL - test arrows navigation`, () => { // 25/04/2018 => 1397/02/05 const expectedValues = ['11', '54', '1397', '02', '05', '05']; - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps( { enableAccessibleFieldDOMStructure: true, @@ -147,7 +147,7 @@ describe(`RTL - test arrows navigation`, () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps( { defaultValue: adapter.date('2018-04-25T11:54:00'), @@ -169,7 +169,7 @@ describe(`RTL - test arrows navigation`, () => { // 25/04/2018 => 1397/02/05 const expectedValues = ['05', '02', '1397', '54', '11', '11']; - // Test with v7 input + // Test with accessible DOM structure let view = renderWithProps( { enableAccessibleFieldDOMStructure: true, @@ -187,7 +187,7 @@ describe(`RTL - test arrows navigation`, () => { view.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure view = renderWithProps( { defaultValue: adapter.date('2018-04-25T11:54:00'), diff --git a/packages/x-internals/package.json b/packages/x-internals/package.json index 2aafdced02cc..148ed86ce6e9 100644 --- a/packages/x-internals/package.json +++ b/packages/x-internals/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-internals", - "version": "7.20.0", + "version": "7.21.0", "description": "Utility functions for the MUI X packages (internal use only).", "author": "MUI Team", "license": "MIT", diff --git a/packages/x-license/package.json b/packages/x-license/package.json index e1ec343ddd59..8dc6070e560a 100644 --- a/packages/x-license/package.json +++ b/packages/x-license/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-license", - "version": "7.20.0", + "version": "7.21.0", "description": "MUI X License verification", "author": "MUI Team", "main": "src/index.ts", diff --git a/packages/x-tree-view-pro/package.json b/packages/x-tree-view-pro/package.json index bfc58d01f83f..de2ebcdfb5c5 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.20.0", + "version": "7.21.0", "description": "The Pro plan edition of the Tree View components (MUI X).", "author": "MUI Team", "main": "src/index.ts", diff --git a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx index 5f6da5132f33..d1cf77859bda 100644 --- a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx +++ b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx @@ -68,7 +68,7 @@ const RichTreeViewPro = React.forwardRef(function RichTreeViewPro< if (process.env.NODE_ENV !== 'production') { if ((props as any).children != null) { warnOnce([ - 'MUI X: The `RichTreeViewPro` component does not support JSX children.', + 'MUI X: The `` component does not support JSX children.', 'If you want to add items, you need to use the `items` prop.', 'Check the documentation for more details: https://mui.com/x/react-tree-view/rich-tree-view/items/.', ]); @@ -140,7 +140,7 @@ RichTreeViewPro.propTypes = { */ canMoveItemToNewPosition: PropTypes.func, /** - * If `true`, the tree view renders a checkbox at the left of its label that allows selecting it. + * If `true`, the Tree View renders a checkbox at the left of its label that allows selecting it. * @default false */ checkboxSelection: PropTypes.bool, @@ -258,26 +258,26 @@ RichTreeViewPro.propTypes = { */ multiSelect: PropTypes.bool, /** - * Callback fired when tree items are expanded/collapsed. + * Callback fired when Tree Items are expanded/collapsed. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemIds The ids of the expanded items. */ onExpandedItemsChange: PropTypes.func, /** - * Callback fired when the `content` slot of a given tree item is clicked. + * Callback fired when the `content` slot of a given Tree Item is clicked. * @param {React.MouseEvent} event The DOM event that triggered the change. * @param {string} itemId The id of the focused item. */ onItemClick: PropTypes.func, /** - * Callback fired when a tree item is expanded or collapsed. + * Callback fired when a Tree Item is expanded or collapsed. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemId The itemId of the modified item. * @param {array} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed. */ onItemExpansionToggle: PropTypes.func, /** - * Callback fired when a given tree item is focused. + * Callback fired when a given Tree Item is focused. * @param {React.SyntheticEvent | null} event The DOM event that triggered the change. **Warning**: This is a generic event not a focus event. * @param {string} itemId The id of the focused item. */ @@ -289,7 +289,7 @@ RichTreeViewPro.propTypes = { */ onItemLabelChange: PropTypes.func, /** - * Callback fired when a tree item is moved in the tree. + * Callback fired when a Tree Item is moved in the tree. * @param {object} params The params describing the item re-ordering. * @param {string} params.itemId The id of the item moved. * @param {TreeViewItemReorderPosition} params.oldPosition The old position of the item. @@ -297,14 +297,14 @@ RichTreeViewPro.propTypes = { */ onItemPositionChange: PropTypes.func, /** - * Callback fired when a tree item is selected or deselected. + * Callback fired when a Tree Item is selected or deselected. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemId The itemId of the modified item. * @param {array} isSelected `true` if the item has just been selected, `false` if it has just been deselected. */ onItemSelectionToggle: PropTypes.func, /** - * Callback fired when tree items are selected/deselected. + * Callback fired when Tree Items are selected/deselected. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {string[] | string} itemIds The ids of the selected items. * When `multiSelect` is `true`, this is an array of strings; when false (default) a string. @@ -315,6 +315,26 @@ RichTreeViewPro.propTypes = { * When `multiSelect` is true this takes an array of strings; when false (default) a string. */ selectedItems: PropTypes.any, + /** + * When `selectionPropagation.descendants` is set to `true`. + * + * - Selecting a parent selects all its descendants automatically. + * - Deselecting a parent deselects all its descendants automatically. + * + * When `selectionPropagation.parents` is set to `true`. + * + * - Selecting all the descendants of a parent selects the parent automatically. + * - Deselecting a descendant of a selected parent deselects the parent automatically. + * + * Only works when `multiSelect` is `true`. + * On the , only the expanded items are considered (since the collapsed item are not passed to the Tree View component at all) + * + * @default { parents: false, descendants: false } + */ + selectionPropagation: PropTypes.shape({ + descendants: PropTypes.bool, + parents: PropTypes.bool, + }), /** * The props used for each component slot. * @default {} diff --git a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.types.ts b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.types.ts index 81b1eeac8782..ff865e8fe213 100644 --- a/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.types.ts +++ b/packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.types.ts @@ -3,7 +3,6 @@ import { Theme } from '@mui/material/styles'; import { SxProps } from '@mui/system'; import { SlotComponentProps } from '@mui/utils'; import { TreeItem, TreeItemProps } from '@mui/x-tree-view/TreeItem'; -import { TreeItem2Props } from '@mui/x-tree-view/TreeItem2'; import { TreeViewItemId } from '@mui/x-tree-view/models'; import { TreeViewPublicAPI, TreeViewExperimentalFeatures } from '@mui/x-tree-view/internals'; import { RichTreeViewProClasses } from './richTreeViewProClasses'; @@ -29,7 +28,7 @@ export interface RichTreeViewProSlots extends RichTreeViewProPluginSlots { * Custom component for the item. * @default TreeItem. */ - item?: React.JSXElementConstructor | React.JSXElementConstructor; + item?: React.JSXElementConstructor; } export interface RichTreeViewProSlotProps diff --git a/packages/x-tree-view-pro/src/index.ts b/packages/x-tree-view-pro/src/index.ts index cb79fa4f9128..b09ae6fff6ea 100644 --- a/packages/x-tree-view-pro/src/index.ts +++ b/packages/x-tree-view-pro/src/index.ts @@ -5,10 +5,11 @@ export * from './RichTreeViewPro'; // Tree Item export * from '@mui/x-tree-view/TreeItem'; -export * from '@mui/x-tree-view/TreeItem2'; -export * from '@mui/x-tree-view/useTreeItem2'; -export * from '@mui/x-tree-view/TreeItem2Icon'; -export * from '@mui/x-tree-view/TreeItem2Provider'; +export * from '@mui/x-tree-view/useTreeItem'; +export * from '@mui/x-tree-view/TreeItemIcon'; +export * from '@mui/x-tree-view/TreeItemProvider'; +export * from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; +export * from '@mui/x-tree-view/TreeItemLabelInput'; export { unstable_resetCleanupTracking } from '@mui/x-tree-view/internals'; diff --git a/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.itemPlugin.ts b/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.itemPlugin.ts index 18d5014bfccf..999d7b2b3e3f 100644 --- a/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.itemPlugin.ts +++ b/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.itemPlugin.ts @@ -1,25 +1,22 @@ import * as React from 'react'; +import { TreeViewCancellableEvent } from '@mui/x-tree-view/models'; import { - MuiCancellableEvent, TreeViewItemPlugin, useTreeViewContext, UseTreeViewItemsSignature, isTargetInDescendants, } from '@mui/x-tree-view/internals'; -import { TreeItem2Props } from '@mui/x-tree-view/TreeItem2'; import { - UseTreeItem2DragAndDropOverlaySlotPropsFromItemsReordering, - UseTreeItem2RootSlotPropsFromItemsReordering, + UseTreeItemDragAndDropOverlaySlotPropsFromItemsReordering, + UseTreeItemRootSlotPropsFromItemsReordering, UseTreeViewItemsReorderingSignature, TreeViewItemItemReorderingValidActions, - UseTreeItem2ContentSlotPropsFromItemsReordering, + UseTreeItemContentSlotPropsFromItemsReordering, } from './useTreeViewItemsReordering.types'; export const isAndroid = () => navigator.userAgent.toLowerCase().includes('android'); -export const useTreeViewItemsReorderingItemPlugin: TreeViewItemPlugin = ({ - props, -}) => { +export const useTreeViewItemsReorderingItemPlugin: TreeViewItemPlugin = ({ props }) => { const { itemsReordering, instance } = useTreeViewContext<[UseTreeViewItemsSignature, UseTreeViewItemsReorderingSignature]>(); const { itemId } = props; @@ -32,13 +29,13 @@ export const useTreeViewItemsReorderingItemPlugin: TreeViewItemPlugin { + }): UseTreeItemRootSlotPropsFromItemsReordering => { const draggable = instance.canItemBeDragged(itemId); if (!draggable) { return {}; } - const handleDragStart = (event: React.DragEvent & MuiCancellableEvent) => { + const handleDragStart = (event: React.DragEvent & TreeViewCancellableEvent) => { externalEventHandlers.onDragStart?.(event); if (event.defaultMuiPrevented || event.defaultPrevented) { return; @@ -66,7 +63,7 @@ export const useTreeViewItemsReorderingItemPlugin: TreeViewItemPlugin { + const handleRootDragOver = (event: React.DragEvent & TreeViewCancellableEvent) => { externalEventHandlers.onDragOver?.(event); if (event.defaultMuiPrevented) { return; @@ -75,7 +72,7 @@ export const useTreeViewItemsReorderingItemPlugin: TreeViewItemPlugin { + const handleRootDragEnd = (event: React.DragEvent & TreeViewCancellableEvent) => { externalEventHandlers.onDragEnd?.(event); if (event.defaultMuiPrevented) { return; @@ -94,13 +91,13 @@ export const useTreeViewItemsReorderingItemPlugin: TreeViewItemPlugin { + }): UseTreeItemContentSlotPropsFromItemsReordering => { const currentDrag = itemsReordering.currentDrag; if (!currentDrag || currentDrag.draggedItemId === itemId) { return {}; } - const handleDragOver = (event: React.DragEvent & MuiCancellableEvent) => { + const handleDragOver = (event: React.DragEvent & TreeViewCancellableEvent) => { externalEventHandlers.onDragOver?.(event); if (event.defaultMuiPrevented || validActionsRef.current == null) { return; @@ -119,7 +116,7 @@ export const useTreeViewItemsReorderingItemPlugin: TreeViewItemPlugin { + const handleDragEnter = (event: React.DragEvent & TreeViewCancellableEvent) => { externalEventHandlers.onDragEnter?.(event); if (event.defaultMuiPrevented) { return; @@ -133,7 +130,7 @@ export const useTreeViewItemsReorderingItemPlugin: TreeViewItemPlugin { + dragAndDropOverlay: (): UseTreeItemDragAndDropOverlaySlotPropsFromItemsReordering => { const currentDrag = itemsReordering.currentDrag; if (!currentDrag || currentDrag.targetItemId !== itemId || currentDrag.action == null) { return {}; 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 263083dce4a9..fe0d7a8a29c6 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 @@ -256,7 +256,7 @@ useTreeViewItemsReordering.getDefaultizedParams = ({ params, experimentalFeature if (params.itemsReordering && !canUseFeature) { warnOnce([ 'MUI X: The items reordering feature requires the `indentationAtItemLevel` and `itemsReordering` experimental features to be enabled.', - 'You can do it by passing `experimentalFeatures={{ indentationAtItemLevel: true, itemsReordering: true }}` to the `RichTreeViewPro` component.', + 'You can do it by passing `experimentalFeatures={{ indentationAtItemLevel: true, itemsReordering: true }}` to the ``component.', 'Check the documentation for more details: https://mui.com/x/react-tree-view/rich-tree-view/items/', ]); } diff --git a/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.types.ts b/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.types.ts index 25f2fe2a4b30..d8d55ced0a6a 100644 --- a/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.types.ts +++ b/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.types.ts @@ -3,10 +3,13 @@ import { DefaultizedProps, TreeViewPluginSignature, UseTreeViewItemsSignature, - MuiCancellableEventHandler, } from '@mui/x-tree-view/internals'; -import { TreeViewItemId, TreeViewItemsReorderingAction } from '@mui/x-tree-view/models'; -import { TreeItem2DragAndDropOverlayProps } from '@mui/x-tree-view/TreeItem2DragAndDropOverlay'; +import { + TreeViewItemId, + TreeViewItemsReorderingAction, + TreeViewCancellableEventHandler, +} from '@mui/x-tree-view/models'; +import { TreeItemDragAndDropOverlayProps } from '@mui/x-tree-view/TreeItemDragAndDropOverlay'; export interface UseTreeViewItemsReorderingInstance { /** @@ -90,7 +93,7 @@ export interface UseTreeViewItemsReorderingParameters { newPosition: TreeViewItemReorderPosition; }) => boolean; /** - * Callback fired when a tree item is moved in the tree. + * Callback fired when a Tree Item is moved in the tree. * @param {object} params The params describing the item re-ordering. * @param {string} params.itemId The id of the item moved. * @param {TreeViewItemReorderPosition} params.oldPosition The old position of the item. @@ -134,27 +137,26 @@ export type UseTreeViewItemsReorderingSignature = TreeViewPluginSignature<{ dependencies: [UseTreeViewItemsSignature]; }>; -export interface UseTreeItem2RootSlotPropsFromItemsReordering { +export interface UseTreeItemRootSlotPropsFromItemsReordering { draggable?: true; - onDragStart?: MuiCancellableEventHandler; - onDragOver?: MuiCancellableEventHandler; - onDragEnd?: MuiCancellableEventHandler; + onDragStart?: TreeViewCancellableEventHandler; + onDragOver?: TreeViewCancellableEventHandler; + onDragEnd?: TreeViewCancellableEventHandler; } -export interface UseTreeItem2ContentSlotPropsFromItemsReordering { - onDragEnter?: MuiCancellableEventHandler; - onDragOver?: MuiCancellableEventHandler; +export interface UseTreeItemContentSlotPropsFromItemsReordering { + onDragEnter?: TreeViewCancellableEventHandler; + onDragOver?: TreeViewCancellableEventHandler; } -export interface UseTreeItem2DragAndDropOverlaySlotPropsFromItemsReordering - extends TreeItem2DragAndDropOverlayProps {} +export interface UseTreeItemDragAndDropOverlaySlotPropsFromItemsReordering + extends TreeItemDragAndDropOverlayProps {} -declare module '@mui/x-tree-view/useTreeItem2' { - interface UseTreeItem2RootSlotOwnProps extends UseTreeItem2RootSlotPropsFromItemsReordering {} +declare module '@mui/x-tree-view/useTreeItem' { + interface UseTreeItemRootSlotOwnProps extends UseTreeItemRootSlotPropsFromItemsReordering {} - interface UseTreeItem2ContentSlotOwnProps - extends UseTreeItem2ContentSlotPropsFromItemsReordering {} + interface UseTreeItemContentSlotOwnProps extends UseTreeItemContentSlotPropsFromItemsReordering {} - interface UseTreeItem2DragAndDropOverlaySlotOwnProps - extends UseTreeItem2DragAndDropOverlaySlotPropsFromItemsReordering {} + interface UseTreeItemDragAndDropOverlaySlotOwnProps + extends UseTreeItemDragAndDropOverlaySlotPropsFromItemsReordering {} } diff --git a/packages/x-tree-view/package.json b/packages/x-tree-view/package.json index bab5e2987428..8e46c3d60740 100644 --- a/packages/x-tree-view/package.json +++ b/packages/x-tree-view/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-tree-view", - "version": "7.20.0", + "version": "7.21.0", "description": "The community edition of the Tree View components (MUI X).", "author": "MUI Team", "main": "src/index.ts", diff --git a/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx b/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx index d030df14bd9a..ccaccc5bcc78 100644 --- a/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx +++ b/packages/x-tree-view/src/RichTreeView/RichTreeView.tsx @@ -61,7 +61,7 @@ const RichTreeView = React.forwardRef(function RichTreeView< if (process.env.NODE_ENV !== 'production') { if ((props as any).children != null) { warnOnce([ - 'MUI X: The `RichTreeView` component does not support JSX children.', + 'MUI X: The Rich Tree View component does not support JSX children.', 'If you want to add items, you need to use the `items` prop.', 'Check the documentation for more details: https://mui.com/x/react-tree-view/rich-tree-view/items/.', ]); @@ -123,7 +123,7 @@ RichTreeView.propTypes = { }), }), /** - * If `true`, the tree view renders a checkbox at the left of its label that allows selecting it. + * If `true`, the Tree View renders a checkbox at the left of its label that allows selecting it. * @default false */ checkboxSelection: PropTypes.bool, @@ -226,26 +226,26 @@ RichTreeView.propTypes = { */ multiSelect: PropTypes.bool, /** - * Callback fired when tree items are expanded/collapsed. + * Callback fired when Tree Items are expanded/collapsed. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemIds The ids of the expanded items. */ onExpandedItemsChange: PropTypes.func, /** - * Callback fired when the `content` slot of a given tree item is clicked. + * Callback fired when the `content` slot of a given Tree Item is clicked. * @param {React.MouseEvent} event The DOM event that triggered the change. * @param {string} itemId The id of the focused item. */ onItemClick: PropTypes.func, /** - * Callback fired when a tree item is expanded or collapsed. + * Callback fired when a Tree Item is expanded or collapsed. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemId The itemId of the modified item. * @param {array} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed. */ onItemExpansionToggle: PropTypes.func, /** - * Callback fired when a given tree item is focused. + * Callback fired when a given Tree Item is focused. * @param {React.SyntheticEvent | null} event The DOM event that triggered the change. **Warning**: This is a generic event not a focus event. * @param {string} itemId The id of the focused item. */ @@ -257,14 +257,14 @@ RichTreeView.propTypes = { */ onItemLabelChange: PropTypes.func, /** - * Callback fired when a tree item is selected or deselected. + * Callback fired when a Tree Item is selected or deselected. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemId The itemId of the modified item. * @param {array} isSelected `true` if the item has just been selected, `false` if it has just been deselected. */ onItemSelectionToggle: PropTypes.func, /** - * Callback fired when tree items are selected/deselected. + * Callback fired when Tree Items are selected/deselected. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {string[] | string} itemIds The ids of the selected items. * When `multiSelect` is `true`, this is an array of strings; when false (default) a string. @@ -275,6 +275,26 @@ RichTreeView.propTypes = { * When `multiSelect` is true this takes an array of strings; when false (default) a string. */ selectedItems: PropTypes.any, + /** + * When `selectionPropagation.descendants` is set to `true`. + * + * - Selecting a parent selects all its descendants automatically. + * - Deselecting a parent deselects all its descendants automatically. + * + * When `selectionPropagation.parents` is set to `true`. + * + * - Selecting all the descendants of a parent selects the parent automatically. + * - Deselecting a descendant of a selected parent deselects the parent automatically. + * + * Only works when `multiSelect` is `true`. + * On the , only the expanded items are considered (since the collapsed item are not passed to the Tree View component at all) + * + * @default { parents: false, descendants: false } + */ + selectionPropagation: PropTypes.shape({ + descendants: PropTypes.bool, + parents: PropTypes.bool, + }), /** * The props used for each component slot. * @default {} diff --git a/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts b/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts index d229ed984215..7fd57000b705 100644 --- a/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts +++ b/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts @@ -10,7 +10,6 @@ import { RichTreeViewPluginSignatures, } from './RichTreeView.plugins'; import { TreeItemProps } from '../TreeItem'; -import { TreeItem2Props } from '../TreeItem2'; import { TreeViewItemId } from '../models'; import { SlotComponentPropsFromProps, @@ -33,17 +32,13 @@ export interface RichTreeViewSlots extends RichTreeViewPluginSlots { * Custom component for the item. * @default TreeItem. */ - item?: React.JSXElementConstructor | React.JSXElementConstructor; + item?: React.JSXElementConstructor; } export interface RichTreeViewSlotProps extends RichTreeViewPluginSlotProps { root?: SlotComponentProps<'ul', {}, RichTreeViewProps>; - item?: SlotComponentPropsFromProps< - TreeItemProps | TreeItem2Props, - {}, - RichTreeViewItemSlotOwnerState - >; + item?: SlotComponentPropsFromProps; } export type RichTreeViewApiRef = React.MutableRefObject< diff --git a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx index adfe2d35415b..5eb1c66c9cbb 100644 --- a/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx +++ b/packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx @@ -62,7 +62,7 @@ const SimpleTreeView = React.forwardRef(function SimpleTreeView< if (process.env.NODE_ENV !== 'production') { if ((props as any).items != null) { warnOnce([ - 'MUI X: The `SimpleTreeView` component does not support the `items` prop.', + 'MUI X: The Simple Tree View component does not support the `items` prop.', 'If you want to add items, you need to pass them as JSX children.', 'Check the documentation for more details: https://mui.com/x/react-tree-view/simple-tree-view/items/.', ]); @@ -117,7 +117,7 @@ SimpleTreeView.propTypes = { }), }), /** - * If `true`, the tree view renders a checkbox at the left of its label that allows selecting it. + * If `true`, the Tree View renders a checkbox at the left of its label that allows selecting it. * @default false */ checkboxSelection: PropTypes.bool, @@ -187,39 +187,39 @@ SimpleTreeView.propTypes = { */ multiSelect: PropTypes.bool, /** - * Callback fired when tree items are expanded/collapsed. + * Callback fired when Tree Items are expanded/collapsed. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemIds The ids of the expanded items. */ onExpandedItemsChange: PropTypes.func, /** - * Callback fired when the `content` slot of a given tree item is clicked. + * Callback fired when the `content` slot of a given Tree Item is clicked. * @param {React.MouseEvent} event The DOM event that triggered the change. * @param {string} itemId The id of the focused item. */ onItemClick: PropTypes.func, /** - * Callback fired when a tree item is expanded or collapsed. + * Callback fired when a Tree Item is expanded or collapsed. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemId The itemId of the modified item. * @param {array} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed. */ onItemExpansionToggle: PropTypes.func, /** - * Callback fired when a given tree item is focused. + * Callback fired when a given Tree Item is focused. * @param {React.SyntheticEvent | null} event The DOM event that triggered the change. **Warning**: This is a generic event not a focus event. * @param {string} itemId The id of the focused item. */ onItemFocus: PropTypes.func, /** - * Callback fired when a tree item is selected or deselected. + * Callback fired when a Tree Item is selected or deselected. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemId The itemId of the modified item. * @param {array} isSelected `true` if the item has just been selected, `false` if it has just been deselected. */ onItemSelectionToggle: PropTypes.func, /** - * Callback fired when tree items are selected/deselected. + * Callback fired when Tree Items are selected/deselected. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {string[] | string} itemIds The ids of the selected items. * When `multiSelect` is `true`, this is an array of strings; when false (default) a string. @@ -230,6 +230,26 @@ SimpleTreeView.propTypes = { * When `multiSelect` is true this takes an array of strings; when false (default) a string. */ selectedItems: PropTypes.any, + /** + * When `selectionPropagation.descendants` is set to `true`. + * + * - Selecting a parent selects all its descendants automatically. + * - Deselecting a parent deselects all its descendants automatically. + * + * When `selectionPropagation.parents` is set to `true`. + * + * - Selecting all the descendants of a parent selects the parent automatically. + * - Deselecting a descendant of a selected parent deselects the parent automatically. + * + * Only works when `multiSelect` is `true`. + * On the , only the expanded items are considered (since the collapsed item are not passed to the Tree View component at all) + * + * @default { parents: false, descendants: false } + */ + selectionPropagation: PropTypes.shape({ + descendants: PropTypes.bool, + parents: PropTypes.bool, + }), /** * The props used for each component slot. */ diff --git a/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx b/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx index ff89ba1e4f88..ebd9853ccc7a 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx +++ b/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx @@ -1,28 +1,11 @@ import * as React from 'react'; -import { expect } from 'chai'; -import PropTypes from 'prop-types'; import { createRenderer } from '@mui/internal-test-utils'; +import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; import { TreeItem, treeItemClasses as classes } from '@mui/x-tree-view/TreeItem'; import { TreeViewContext } from '@mui/x-tree-view/internals/TreeViewProvider/TreeViewContext'; import { describeConformance } from 'test/utils/describeConformance'; -import { describeTreeView } from 'test/utils/tree-view/describeTreeView'; import { getFakeContextValue } from 'test/utils/tree-view/fakeContextValue'; - -describeTreeView<[]>('TreeItem component', ({ render, treeItemComponentName }) => { - describe('ContentComponent / ContentProps props (TreeItem only)', () => { - it('should render TreeItem when itemId prop is escaping characters without throwing an error', function test() { - if (treeItemComponentName === 'TreeItem2') { - this.skip(); - } - - const view = render({ - items: [{ id: 'C:\\\\', label: 'ABCDEF' }], - }); - - expect(view.getItemContent('C:\\\\').textContent).to.equal('ABCDEF'); - }); - }); -}); +import { describeSlotsConformance } from 'test/utils/describeSlotsConformance'; describe('', () => { const { render } = createRenderer(); @@ -40,20 +23,18 @@ describe('', () => { skip: ['componentProp', 'componentsProp', 'themeVariants'], })); - describe('PropTypes warnings', () => { - beforeEach(() => { - PropTypes.resetWarningCache(); - }); - - it('should warn if an onFocus callback is supplied', () => { - expect(() => { - PropTypes.checkPropTypes( - TreeItem.propTypes, - { itemId: 'one', onFocus: () => {} }, - 'prop', - 'TreeItem', - ); - }).toErrorDev('Failed prop type: The prop `onFocus` is not supported.'); - }); + describeSlotsConformance({ + render, + getElement: ({ props, slotName }) => ( + + + + ), + slots: { + label: { className: classes.label }, + iconContainer: { className: classes.iconContainer }, + content: { className: classes.content }, + checkbox: { className: classes.checkbox }, + }, }); }); diff --git a/packages/x-tree-view/src/TreeItem/TreeItem.tsx b/packages/x-tree-view/src/TreeItem/TreeItem.tsx index 898f14212517..ce6e5c252b18 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItem.tsx +++ b/packages/x-tree-view/src/TreeItem/TreeItem.tsx @@ -2,87 +2,47 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; +import unsupportedProp from '@mui/utils/unsupportedProp'; +import { alpha } from '@mui/material/styles'; import Collapse from '@mui/material/Collapse'; -import useForkRef from '@mui/utils/useForkRef'; +import MuiCheckbox, { CheckboxProps } from '@mui/material/Checkbox'; +import useSlotProps from '@mui/utils/useSlotProps'; import { shouldForwardProp } from '@mui/system/createStyled'; -import { alpha } from '@mui/material/styles'; -import { TransitionProps } from '@mui/material/transitions'; import composeClasses from '@mui/utils/composeClasses'; -import extractEventHandlers from '@mui/utils/extractEventHandlers'; -import resolveComponentProps from '@mui/utils/resolveComponentProps'; -import useSlotProps from '@mui/utils/useSlotProps'; -import unsupportedProp from '@mui/utils/unsupportedProp'; -import elementTypeAcceptingRef from '@mui/utils/elementTypeAcceptingRef'; -import { warnOnce } from '@mui/x-internals/warning'; import { styled, createUseThemeProps } from '../internals/zero-styled'; -import { TreeItemContent } from './TreeItemContent'; -import { treeItemClasses, getTreeItemUtilityClass } from './treeItemClasses'; +import { TreeItemProps, TreeItemOwnerState } from './TreeItem.types'; import { - TreeItemMinimalPlugins, - TreeItemOptionalPlugins, - TreeItemOwnerState, - TreeItemProps, -} from './TreeItem.types'; -import { useTreeViewContext } from '../internals/TreeViewProvider'; -import { TreeViewCollapseIcon, TreeViewExpandIcon } from '../icons'; -import { TreeItem2Provider } from '../TreeItem2Provider'; -import { TreeViewItemDepthContext } from '../internals/TreeViewItemDepthContext'; -import { useTreeItemState } from './useTreeItemState'; -import { isTargetInDescendants } from '../internals/utils/tree'; -import { TreeViewItemPluginSlotPropsEnhancerParams } from '../internals/models'; -import { generateTreeItemIdAttribute } from '../internals/corePlugins/useTreeViewId/useTreeViewId.utils'; + useTreeItem, + UseTreeItemContentSlotOwnProps, + UseTreeItemLabelSlotOwnProps, + UseTreeItemStatus, +} from '../useTreeItem'; +import { getTreeItemUtilityClass } from './treeItemClasses'; +import { TreeItemIcon } from '../TreeItemIcon'; +import { TreeItemDragAndDropOverlay } from '../TreeItemDragAndDropOverlay'; +import { TreeItemProvider } from '../TreeItemProvider'; +import { TreeItemLabelInput } from '../TreeItemLabelInput'; const useThemeProps = createUseThemeProps('MuiTreeItem'); -const useUtilityClasses = (ownerState: TreeItemOwnerState) => { - const { classes } = ownerState; - - const slots = { - root: ['root'], - content: ['content'], - expanded: ['expanded'], - selected: ['selected'], - focused: ['focused'], - disabled: ['disabled'], - iconContainer: ['iconContainer'], - checkbox: ['checkbox'], - label: ['label'], - labelInput: ['labelInput'], - editing: ['editing'], - editable: ['editable'], - groupTransition: ['groupTransition'], - }; - - return composeClasses(slots, getTreeItemUtilityClass, classes); -}; - -const TreeItemRoot = styled('li', { +export const TreeItemRoot = styled('li', { name: 'MuiTreeItem', slot: 'Root', overridesResolver: (props, styles) => styles.root, -})<{ ownerState: TreeItemOwnerState }>({ +})({ listStyle: 'none', margin: 0, padding: 0, outline: 0, }); -const StyledTreeItemContent = styled(TreeItemContent, { +export const TreeItemContent = styled('div', { name: 'MuiTreeItem', slot: 'Content', - overridesResolver: (props, styles) => { - return [ - styles.content, - styles.iconContainer && { - [`& .${treeItemClasses.iconContainer}`]: styles.iconContainer, - }, - styles.label && { - [`& .${treeItemClasses.label}`]: styles.label, - }, - ]; - }, - shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'indentationAtItemLevel', -})<{ ownerState: TreeItemOwnerState }>(({ theme }) => ({ + overridesResolver: (props, styles) => styles.content, + shouldForwardProp: (prop) => + shouldForwardProp(prop) && prop !== 'status' && prop !== 'indentationAtItemLevel', +})<{ status: UseTreeItemStatus; indentationAtItemLevel?: true }>(({ theme }) => ({ padding: theme.spacing(0.5, 1), borderRadius: theme.shape.borderRadius, width: '100%', @@ -100,76 +60,103 @@ const StyledTreeItemContent = styled(TreeItemContent, { backgroundColor: 'transparent', }, }, - [`&.${treeItemClasses.disabled}`]: { - opacity: (theme.vars || theme).palette.action.disabledOpacity, - backgroundColor: 'transparent', - }, - [`&.${treeItemClasses.focused}`]: { - backgroundColor: (theme.vars || theme).palette.action.focus, - }, - [`&.${treeItemClasses.selected}`]: { - backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.primary.mainChannel} / ${theme.vars.palette.action.selectedOpacity})` - : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity), - '&:hover': { - backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.primary.mainChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.hoverOpacity}))` - : alpha( - theme.palette.primary.main, - theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity, - ), - // Reset on touch devices, it doesn't add specificity - '@media (hover: none)': { + variants: [ + { + props: { indentationAtItemLevel: true }, + style: { + paddingLeft: `calc(${theme.spacing(1)} + var(--TreeView-itemChildrenIndentation) * var(--TreeView-itemDepth))`, + }, + }, + { + props: ({ status }: UseTreeItemContentSlotOwnProps) => status.disabled, + style: { + opacity: (theme.vars || theme).palette.action.disabledOpacity, + backgroundColor: 'transparent', + }, + }, + { + props: ({ status }: UseTreeItemContentSlotOwnProps) => status.focused, + style: { backgroundColor: (theme.vars || theme).palette.action.focus }, + }, + { + props: ({ status }: UseTreeItemContentSlotOwnProps) => status.selected, + style: { backgroundColor: theme.vars ? `rgba(${theme.vars.palette.primary.mainChannel} / ${theme.vars.palette.action.selectedOpacity})` : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity), + '&:hover': { + backgroundColor: theme.vars + ? `rgba(${theme.vars.palette.primary.mainChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.hoverOpacity}))` + : alpha( + theme.palette.primary.main, + theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity, + ), + // Reset on touch devices, it doesn't add specificity + '@media (hover: none)': { + backgroundColor: theme.vars + ? `rgba(${theme.vars.palette.primary.mainChannel} / ${theme.vars.palette.action.selectedOpacity})` + : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity), + }, + }, }, }, - [`&.${treeItemClasses.focused}`]: { - backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.primary.mainChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.focusOpacity}))` - : alpha( - theme.palette.primary.main, - theme.palette.action.selectedOpacity + theme.palette.action.focusOpacity, - ), - }, - }, - [`& .${treeItemClasses.iconContainer}`]: { - width: 16, - display: 'flex', - flexShrink: 0, - justifyContent: 'center', - '& svg': { - fontSize: 18, + { + props: ({ status }: UseTreeItemContentSlotOwnProps) => status.selected && status.focused, + style: { + backgroundColor: theme.vars + ? `rgba(${theme.vars.palette.primary.mainChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.focusOpacity}))` + : alpha( + theme.palette.primary.main, + theme.palette.action.selectedOpacity + theme.palette.action.focusOpacity, + ), + }, }, - }, - [`& .${treeItemClasses.label}`]: { - 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', - ...theme.typography.body1, - }, - [`& .${treeItemClasses.checkbox}`]: { - padding: 0, - }, + ], +})); + +export const TreeItemLabel = styled('div', { + name: 'MuiTreeItem', + slot: 'Label', + overridesResolver: (props, styles) => styles.label, + 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: { indentationAtItemLevel: true }, + props: ({ editable }: UseTreeItemLabelSlotOwnProps) => editable, style: { - paddingLeft: `calc(${theme.spacing(1)} + var(--TreeView-itemChildrenIndentation) * var(--TreeView-itemDepth))`, + paddingLeft: '2px', }, }, ], })); -const TreeItemGroup = styled(Collapse, { +export const TreeItemIconContainer = styled('div', { + name: 'MuiTreeItem', + slot: 'IconContainer', + overridesResolver: (props, styles) => styles.iconContainer, +})({ + width: 16, + display: 'flex', + flexShrink: 0, + justifyContent: 'center', + '& svg': { + fontSize: 18, + }, +}); + +export const TreeItemGroupTransition = styled(Collapse, { name: 'MuiTreeItem', slot: 'GroupTransition', overridesResolver: (props, styles) => styles.groupTransition, shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'indentationAtItemLevel', -})({ +})<{ indentationAtItemLevel?: true }>({ margin: 0, padding: 0, paddingLeft: 'var(--TreeView-itemChildrenIndentation)', @@ -181,6 +168,53 @@ const TreeItemGroup = styled(Collapse, { ], }); +export const TreeItemCheckbox = styled( + React.forwardRef( + (props: CheckboxProps & { visible?: boolean }, ref: React.Ref) => { + const { visible, ...other } = props; + if (!visible) { + return null; + } + + return ; + }, + ), + { + name: 'MuiTreeItem', + slot: 'Checkbox', + overridesResolver: (props, styles) => styles.checkbox, + }, +)({ + padding: 0, +}); + +const useUtilityClasses = (ownerState: TreeItemOwnerState) => { + const { classes } = ownerState; + + const slots = { + root: ['root'], + content: ['content'], + expanded: ['expanded'], + editing: ['editing'], + editable: ['editable'], + selected: ['selected'], + focused: ['focused'], + disabled: ['disabled'], + iconContainer: ['iconContainer'], + checkbox: ['checkbox'], + label: ['label'], + groupTransition: ['groupTransition'], + labelInput: ['labelInput'], + dragAndDropOverlay: ['dragAndDropOverlay'], + }; + + return composeClasses(slots, getTreeItemUtilityClass, classes); +}; + +type TreeItemComponent = (( + props: TreeItemProps & React.RefAttributes, +) => React.JSX.Element) & { propTypes?: any }; + /** * * Demos: @@ -189,323 +223,141 @@ const TreeItemGroup = styled(Collapse, { * * API: * - * - [TreeItem API](https://mui.com/x/api/tree-view/tree-item/) + * - [TreeItem API](https://mui.com/x/api/tree-view/tree-item-2/) */ export const TreeItem = React.forwardRef(function TreeItem( inProps: TreeItemProps, - inRef: React.Ref, + forwardedRef: React.Ref, ) { - const { - icons: contextIcons, - runItemPlugins, - items: { disabledItemsFocusable, indentationAtItemLevel }, - selection: { disableSelection }, - expansion: { expansionTrigger }, - treeId, - instance, - } = useTreeViewContext(); - const depthContext = React.useContext(TreeViewItemDepthContext); - const props = useThemeProps({ props: inProps, name: 'MuiTreeItem' }); + const { id, itemId, label, disabled, children, slots = {}, slotProps = {}, ...other } = props; + const { - children, - className, - slots: inSlots, - slotProps: inSlotProps, - ContentComponent = TreeItemContent, - ContentProps, - itemId, + getRootProps, + getContentProps, + getIconContainerProps, + getCheckboxProps, + getLabelProps, + getGroupTransitionProps, + getLabelInputProps, + getDragAndDropOverlayProps, + status, + } = useTreeItem({ id, + itemId, + children, label, - onClick, - onMouseDown, - onFocus, - onBlur, - onKeyDown, - ...other - } = props; - - const { - expanded, - focused, - selected, disabled, - editing, - handleExpansion, - handleCancelItemLabelEditing, - handleSaveItemLabel, - } = useTreeItemState(itemId); - - if (process.env.NODE_ENV !== 'production') { - // Checking directly the `props` to avoid having the default value applied - if (props.ContentComponent) { - warnOnce([ - 'MUI X: The ContentComponent prop of the TreeItem component is deprecated and will be removed in the next major release.', - 'You can use the new TreeItem2 component or the new useTreeItem2 hook to customize the rendering of the content.', - 'For more detail, see https://mui.com/x/react-tree-view/tree-item-customization/.', - ]); - } - - if (props.ContentProps) { - warnOnce([ - 'MUI X: The ContentProps prop of the TreeItem component is deprecated and will be removed in the next major release.', - 'You can use the new TreeItem2 component or the new useTreeItem2 hook to customize the rendering of the content.', - 'For more detail, see https://mui.com/x/react-tree-view/tree-item-customization/.', - ]); - } - } - - const { contentRef, rootRef, propsEnhancers } = runItemPlugins(props); - const rootRefObject = React.useRef(null); - const contentRefObject = React.useRef(null); - const handleRootRef = useForkRef(inRef, rootRef, rootRefObject); - const handleContentRef = useForkRef(ContentProps?.ref, contentRef, contentRefObject); - - const slots = { - expandIcon: inSlots?.expandIcon ?? contextIcons.slots.expandIcon ?? TreeViewExpandIcon, - collapseIcon: inSlots?.collapseIcon ?? contextIcons.slots.collapseIcon ?? TreeViewCollapseIcon, - endIcon: inSlots?.endIcon ?? contextIcons.slots.endIcon, - icon: inSlots?.icon, - groupTransition: inSlots?.groupTransition, - }; - - const isExpandable = (reactChildren: React.ReactNode) => { - if (Array.isArray(reactChildren)) { - return reactChildren.length > 0 && reactChildren.some(isExpandable); - } - return Boolean(reactChildren); - }; - const expandable = isExpandable(children); + }); const ownerState: TreeItemOwnerState = { ...props, - expanded, - focused, - selected, - disabled, - indentationAtItemLevel, + ...status, }; const classes = useUtilityClasses(ownerState); - const GroupTransition: React.ElementType | undefined = slots.groupTransition ?? undefined; - const groupTransitionProps: TransitionProps = useSlotProps({ - elementType: GroupTransition, - ownerState: {}, - externalSlotProps: inSlotProps?.groupTransition, + const Root: React.ElementType = slots.root ?? TreeItemRoot; + const rootProps = useSlotProps({ + elementType: Root, + getSlotProps: getRootProps, + externalForwardedProps: other, + externalSlotProps: slotProps.root, additionalProps: { - unmountOnExit: true, - in: expanded, - component: 'ul', - role: 'group', - ...(indentationAtItemLevel ? { indentationAtItemLevel: true } : {}), + ref: forwardedRef, }, - className: classes.groupTransition, + ownerState: {}, + className: classes.root, }); - const handleIconContainerClick = (event: React.MouseEvent) => { - if (expansionTrigger === 'iconContainer') { - handleExpansion(event); - } - }; - const ExpansionIcon = expanded ? slots.collapseIcon : slots.expandIcon; - const { ownerState: expansionIconOwnerState, ...expansionIconProps } = useSlotProps({ - elementType: ExpansionIcon, + const Content: React.ElementType = slots.content ?? TreeItemContent; + const contentProps = useSlotProps({ + elementType: Content, + getSlotProps: getContentProps, + externalSlotProps: slotProps.content, ownerState: {}, - externalSlotProps: (tempOwnerState: any) => { - if (expanded) { - return { - ...resolveComponentProps(contextIcons.slotProps.collapseIcon, tempOwnerState), - ...resolveComponentProps(inSlotProps?.collapseIcon, tempOwnerState), - }; - } - - return { - ...resolveComponentProps(contextIcons.slotProps.expandIcon, tempOwnerState), - ...resolveComponentProps(inSlotProps?.expandIcon, tempOwnerState), - }; - }, - additionalProps: { - onClick: handleIconContainerClick, - }, + className: clsx(classes.content, { + [classes.expanded]: status.expanded, + [classes.selected]: status.selected, + [classes.focused]: status.focused, + [classes.disabled]: status.disabled, + [classes.editing]: status.editing, + [classes.editable]: status.editable, + }), }); - const expansionIcon = - expandable && !!ExpansionIcon ? : null; - const DisplayIcon = expandable ? undefined : slots.endIcon; - const { ownerState: displayIconOwnerState, ...displayIconProps } = useSlotProps({ - elementType: DisplayIcon, + const IconContainer: React.ElementType = slots.iconContainer ?? TreeItemIconContainer; + const iconContainerProps = useSlotProps({ + elementType: IconContainer, + getSlotProps: getIconContainerProps, + externalSlotProps: slotProps.iconContainer, ownerState: {}, - externalSlotProps: (tempOwnerState: any) => { - if (expandable) { - return {}; - } - - return { - ...resolveComponentProps(contextIcons.slotProps.endIcon, tempOwnerState), - ...resolveComponentProps(inSlotProps?.endIcon, tempOwnerState), - }; - }, + className: classes.iconContainer, }); - const displayIcon = DisplayIcon ? : null; - const Icon = slots.icon; - const { ownerState: iconOwnerState, ...iconProps } = useSlotProps({ - elementType: Icon, + const Label: React.ElementType = slots.label ?? TreeItemLabel; + const labelProps = useSlotProps({ + elementType: Label, + getSlotProps: getLabelProps, + externalSlotProps: slotProps.label, ownerState: {}, - externalSlotProps: inSlotProps?.icon, + className: classes.label, }); - const icon = Icon ? : null; - - // https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ - let ariaSelected: boolean | undefined; - if (selected) { - // - each selected node has aria-selected set to true. - ariaSelected = true; - } else if (disableSelection || disabled) { - // - if the tree contains nodes that are not selectable, aria-selected is not present on those nodes. - ariaSelected = undefined; - } else { - // - all nodes that are selectable but not selected have aria-selected set to false. - ariaSelected = false; - } - - function handleFocus(event: React.FocusEvent) { - const canBeFocused = !disabled || disabledItemsFocusable; - if (!focused && canBeFocused && event.currentTarget === event.target) { - instance.focusItem(event, itemId); - } - } - 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); - }; + const Checkbox: React.ElementType = slots.checkbox ?? TreeItemCheckbox; + const checkboxProps = useSlotProps({ + elementType: Checkbox, + getSlotProps: getCheckboxProps, + externalSlotProps: slotProps.checkbox, + ownerState: {}, + className: classes.checkbox, + }); - const idAttribute = generateTreeItemIdAttribute({ itemId, treeId, id }); - const tabIndex = instance.canItemBeTabbed(itemId) ? 0 : -1; + const GroupTransition: React.ElementType | undefined = slots.groupTransition ?? undefined; + const groupTransitionProps = useSlotProps({ + elementType: GroupTransition, + getSlotProps: getGroupTransitionProps, + externalSlotProps: slotProps.groupTransition, + ownerState: {}, + className: classes.groupTransition, + }); - const sharedPropsEnhancerParams: Omit< - TreeViewItemPluginSlotPropsEnhancerParams, - 'externalEventHandlers' - > = { - rootRefObject, - contentRefObject, - interactions: { handleSaveItemLabel, handleCancelItemLabelEditing }, - }; + const LabelInput: React.ElementType = slots.labelInput ?? TreeItemLabelInput; + const labelInputProps = useSlotProps({ + elementType: LabelInput, + getSlotProps: getLabelInputProps, + externalSlotProps: slotProps.labelInput, + ownerState: {}, + className: classes.labelInput, + }); - const enhancedRootProps = - propsEnhancers.root?.({ - ...sharedPropsEnhancerParams, - externalEventHandlers: extractEventHandlers(other), - }) ?? {}; - const enhancedContentProps = - propsEnhancers.content?.({ - ...sharedPropsEnhancerParams, - externalEventHandlers: extractEventHandlers(ContentProps), - }) ?? {}; - const enhancedDragAndDropOverlayProps = - propsEnhancers.dragAndDropOverlay?.({ - ...sharedPropsEnhancerParams, - externalEventHandlers: {}, - }) ?? {}; - const enhancedLabelInputProps = - propsEnhancers.labelInput?.({ - ...sharedPropsEnhancerParams, - externalEventHandlers: {}, - }) ?? {}; + const DragAndDropOverlay: React.ElementType | undefined = + slots.dragAndDropOverlay ?? TreeItemDragAndDropOverlay; + const dragAndDropOverlayProps = useSlotProps({ + elementType: DragAndDropOverlay, + getSlotProps: getDragAndDropOverlayProps, + externalSlotProps: slotProps.dragAndDropOverlay, + ownerState: {}, + className: classes.dragAndDropOverlay, + }); return ( - - - - {children && ( - - {children} - - )} - - + + + + + + + + {status.editing ? : + {children && } + + ); -}); +}) as TreeItemComponent; TreeItem.propTypes = { // ----------------------------- Warning -------------------------------- @@ -521,37 +373,35 @@ TreeItem.propTypes = { */ classes: PropTypes.object, className: PropTypes.string, - /** - * The component used to render the content of the item. - * @deprecated Consider using the `TreeItem2` component or the `useTreeItem2` hook instead. For more detail, see https://mui.com/x/react-tree-view/tree-item-customization/. - * @default TreeItemContent - */ - ContentComponent: elementTypeAcceptingRef, - /** - * Props applied to ContentComponent. - * @deprecated Consider using the `TreeItem2` component or the `useTreeItem2` hook instead. For more detail, see https://mui.com/x/react-tree-view/tree-item-customization/. - */ - ContentProps: PropTypes.object, /** * If `true`, the item is disabled. * @default false */ disabled: PropTypes.bool, + /** + * The id attribute of the item. If not provided, it will be generated. + */ + id: PropTypes.string, /** * The id of the item. + * Must be unique. */ itemId: PropTypes.string.isRequired, /** - * The tree item label. + * The label of the item. */ label: PropTypes.node, + /** + * Callback fired when the item root is blurred. + */ + onBlur: PropTypes.func, /** * This prop isn't supported. - * Use the `onItemFocus` callback on the tree if you need to monitor a item's focus. + * Use the `onItemFocus` callback on the tree if you need to monitor an item's focus. */ onFocus: unsupportedProp, /** - * Callback fired when a key of the keyboard is pressed on the item. + * Callback fired when a key is pressed on the keyboard and the tree is in focus. */ onKeyDown: PropTypes.func, /** @@ -564,12 +414,4 @@ TreeItem.propTypes = { * @default {} */ slots: PropTypes.object, - /** - * The system prop that allows defining system overrides as well as additional CSS styles. - */ - sx: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), - PropTypes.func, - PropTypes.object, - ]), } as any; diff --git a/packages/x-tree-view/src/TreeItem/TreeItem.types.ts b/packages/x-tree-view/src/TreeItem/TreeItem.types.ts index d8e49fc3cea5..3772bc870627 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItem.types.ts +++ b/packages/x-tree-view/src/TreeItem/TreeItem.types.ts @@ -1,57 +1,69 @@ import * as React from 'react'; -import { Theme } from '@mui/material/styles'; import { SlotComponentProps } from '@mui/utils'; -import { TransitionProps } from '@mui/material/transitions'; -import { SxProps } from '@mui/system'; -import { TreeItemContentProps } from './TreeItemContent'; +import { UseTreeItemParameters, UseTreeItemStatus } from '../useTreeItem'; import { TreeItemClasses } from './treeItemClasses'; -import { TreeViewItemId } from '../models'; -import { SlotComponentPropsFromProps } from '../internals/models'; -import { MuiCancellableEventHandler } from '../internals/models/MuiCancellableEvent'; -import { UseTreeViewIconsSignature } from '../internals/plugins/useTreeViewIcons'; -import { UseTreeViewSelectionSignature } from '../internals/plugins/useTreeViewSelection'; -import { UseTreeViewItemsSignature } from '../internals/plugins/useTreeViewItems'; -import { UseTreeViewFocusSignature } from '../internals/plugins/useTreeViewFocus'; -import { UseTreeViewExpansionSignature } from '../internals/plugins/useTreeViewExpansion'; -import { UseTreeViewKeyboardNavigationSignature } from '../internals/plugins/useTreeViewKeyboardNavigation'; +import { TreeItemIconSlotProps, TreeItemIconSlots } from '../TreeItemIcon'; +import { TreeViewCancellableEventHandler } from '../models'; -export interface TreeItemSlots { +export interface TreeItemSlots extends TreeItemIconSlots { /** - * The icon used to collapse the item. + * The component that renders the root. + * @default TreeItemRoot */ - collapseIcon?: React.ElementType; + root?: React.ElementType; /** - * The icon used to expand the item. + * The component that renders the content of the item. + * (e.g.: everything related to this item, not to its children). + * @default TreeItemContent */ - expandIcon?: React.ElementType; + content?: React.ElementType; /** - * The icon displayed next to an end item. + * The component that renders the children of the item. + * @default TreeItemGroupTransition */ - endIcon?: React.ElementType; + groupTransition?: React.ElementType; /** - * The icon to display next to the tree item's label. + * The component that renders the icon. + * @default TreeItemIconContainer */ - icon?: React.ElementType; + iconContainer?: React.ElementType; /** - * The component that animates the appearance / disappearance of the item's children. - * @default TreeItem2Group + * The component that renders the item checkbox for selection. + * @default TreeItemCheckbox */ - groupTransition?: React.ElementType; + checkbox?: React.ElementType; + /** + * The component that renders the item label. + * @default TreeItemLabel + */ + label?: React.ElementType; + /** + * The component that renders the input to edit the label when the item is editable and is currently being edited. + * @default TreeItemLabelInput + */ + labelInput?: React.ElementType; + /** + * The component that renders the overlay when an item reordering is ongoing. + * Warning: This slot is only useful when using the `` component. + * @default TreeItemDragAndDropOverlay + */ + dragAndDropOverlay?: React.ElementType; } -export interface TreeItemSlotProps { - collapseIcon?: SlotComponentProps<'svg', {}, {}>; - expandIcon?: SlotComponentProps<'svg', {}, {}>; - endIcon?: SlotComponentProps<'svg', {}, {}>; - icon?: SlotComponentProps<'svg', {}, {}>; - groupTransition?: SlotComponentPropsFromProps; +export interface TreeItemSlotProps extends TreeItemIconSlotProps { + root?: SlotComponentProps<'li', {}, {}>; + content?: SlotComponentProps<'div', {}, {}>; + groupTransition?: SlotComponentProps<'div', {}, {}>; + iconContainer?: SlotComponentProps<'div', {}, {}>; + checkbox?: SlotComponentProps<'button', {}, {}>; + label?: SlotComponentProps<'div', {}, {}>; + labelInput?: SlotComponentProps<'input', {}, {}>; + dragAndDropOverlay?: SlotComponentProps<'div', {}, {}>; } -export interface TreeItemProps extends Omit, 'onFocus'> { - /** - * The content of the component. - */ - children?: React.ReactNode; +export interface TreeItemProps + extends Omit, + Omit, 'onFocus'> { className?: string; /** * Override or extend the styles applied to the component. @@ -67,66 +79,19 @@ export interface TreeItemProps extends Omit, * @default {} */ slotProps?: TreeItemSlotProps; - /** - * The component used to render the content of the item. - * @deprecated Consider using the `TreeItem2` component or the `useTreeItem2` hook instead. For more detail, see https://mui.com/x/react-tree-view/tree-item-customization/. - * @default TreeItemContent - */ - ContentComponent?: React.JSXElementConstructor; - /** - * Props applied to ContentComponent. - * @deprecated Consider using the `TreeItem2` component or the `useTreeItem2` hook instead. For more detail, see https://mui.com/x/react-tree-view/tree-item-customization/. - */ - ContentProps?: React.HTMLAttributes & { ref?: React.Ref }; - /** - * If `true`, the item is disabled. - * @default false - */ - disabled?: boolean; /** * This prop isn't supported. - * Use the `onItemFocus` callback on the tree if you need to monitor a item's focus. + * Use the `onItemFocus` callback on the tree if you need to monitor an item's focus. */ onFocus?: null; /** - * The tree item label. - */ - label?: React.ReactNode; - /** - * The id of the item. - */ - itemId: TreeViewItemId; - /** - * The system prop that allows defining system overrides as well as additional CSS styles. + * Callback fired when the item root is blurred. */ - sx?: SxProps; + onBlur?: TreeViewCancellableEventHandler>; /** - * Callback fired when a key of the keyboard is pressed on the item. + * Callback fired when a key is pressed on the keyboard and the tree is in focus. */ - onKeyDown?: MuiCancellableEventHandler>; + onKeyDown?: TreeViewCancellableEventHandler>; } -export interface TreeItemOwnerState extends TreeItemProps { - expanded: boolean; - focused: boolean; - selected: boolean; - disabled: boolean; - indentationAtItemLevel: boolean; -} - -/** - * Plugins that need to be present in the Tree View in order for `TreeItem` to work correctly. - */ -export type TreeItemMinimalPlugins = readonly [ - UseTreeViewIconsSignature, - UseTreeViewSelectionSignature, - UseTreeViewItemsSignature, - UseTreeViewFocusSignature, - UseTreeViewExpansionSignature, - UseTreeViewKeyboardNavigationSignature, -]; - -/** - * Plugins that `TreeItem` can use if they are present, but are not required. - */ -export type TreeItemOptionalPlugins = readonly []; +export interface TreeItemOwnerState extends Omit, UseTreeItemStatus {} diff --git a/packages/x-tree-view/src/TreeItem/TreeItemContent.tsx b/packages/x-tree-view/src/TreeItem/TreeItemContent.tsx deleted file mode 100644 index 61f3bacc9203..000000000000 --- a/packages/x-tree-view/src/TreeItem/TreeItemContent.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import * as React from 'react'; -import PropTypes from 'prop-types'; -import clsx from 'clsx'; -import Checkbox from '@mui/material/Checkbox'; -import { useTreeItemState } from './useTreeItemState'; -import { - TreeItem2DragAndDropOverlay, - TreeItem2DragAndDropOverlayProps, -} from '../TreeItem2DragAndDropOverlay'; -import { TreeItem2LabelInput, TreeItem2LabelInputProps } from '../TreeItem2LabelInput'; -import { MuiCancellableEvent } from '../internals/models'; - -export interface TreeItemContentProps extends React.HTMLAttributes { - className?: string; - /** - * Override or extend the styles applied to the component. - */ - classes: { - /** Styles applied to the root element. */ - root: string; - /** State class applied to the content element when expanded. */ - expanded: string; - /** State class applied to the content element when selected. */ - selected: string; - /** State class applied to the content element when focused. */ - focused: string; - /** State class applied to the element when disabled. */ - disabled: string; - /** Styles applied to the tree item icon and collapse/expand icon. */ - iconContainer: string; - /** Styles applied to the label element. */ - 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. - */ - label?: React.ReactNode; - /** - * The id of the item. - */ - itemId: string; - /** - * The icon to display next to the tree item's label. - */ - icon?: React.ReactNode; - /** - * The icon to display next to the tree item's label. Either an expansion or collapse icon. - */ - expansionIcon?: React.ReactNode; - /** - * The icon to display next to the tree item's label. Either a parent or end icon. - */ - displayIcon?: React.ReactNode; - dragAndDropOverlayProps?: TreeItem2DragAndDropOverlayProps; - labelInputProps?: TreeItem2LabelInputProps; -} - -export type TreeItemContentClassKey = keyof NonNullable; - -/** - * @ignore - internal component. - */ -const TreeItemContent = React.forwardRef(function TreeItemContent( - props: TreeItemContentProps, - ref: React.Ref, -) { - const { - classes, - className, - displayIcon, - expansionIcon, - icon: iconProp, - label, - itemId, - onClick, - onMouseDown, - dragAndDropOverlayProps, - labelInputProps, - ...other - } = props; - - const { - disabled, - expanded, - selected, - focused, - editing, - editable, - disableSelection, - checkboxSelection, - handleExpansion, - handleSelection, - handleCheckboxSelection, - handleContentClick, - preventSelection, - expansionTrigger, - toggleItemEditing, - } = useTreeItemState(itemId); - - const icon = iconProp || expansionIcon || displayIcon; - const checkboxRef = React.useRef(null); - - const handleMouseDown = (event: React.MouseEvent) => { - preventSelection(event); - - if (onMouseDown) { - onMouseDown(event); - } - }; - - const handleClick = (event: React.MouseEvent) => { - handleContentClick?.(event, itemId); - - if (checkboxRef.current?.contains(event.target as HTMLElement)) { - return; - } - - if (expansionTrigger === 'content') { - handleExpansion(event); - } - - if (!checkboxSelection) { - handleSelection(event); - } - - if (onClick) { - onClick(event); - } - }; - - const handleLabelDoubleClick = (event: React.MouseEvent & MuiCancellableEvent) => { - if (event.defaultMuiPrevented) { - return; - } - toggleItemEditing(); - }; - - 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 */ -
-
{icon}
- {checkboxSelection && ( - - )} - - {editing ? ( - - ) : ( -
- {label} -
- )} - - {dragAndDropOverlayProps && } -
- ); -}); - -TreeItemContent.propTypes = { - // ----------------------------- Warning -------------------------------- - // | These PropTypes are generated from the TypeScript type definitions | - // | To update them edit the TypeScript types and run "pnpm proptypes" | - // ---------------------------------------------------------------------- - /** - * Override or extend the styles applied to the component. - */ - classes: PropTypes.object.isRequired, - className: PropTypes.string, - /** - * The icon to display next to the tree item's label. Either a parent or end icon. - */ - displayIcon: PropTypes.node, - dragAndDropOverlayProps: PropTypes.shape({ - action: PropTypes.oneOf(['make-child', 'move-to-parent', 'reorder-above', 'reorder-below']), - style: PropTypes.object, - }), - /** - * The icon to display next to the tree item's label. Either an expansion or collapse icon. - */ - expansionIcon: PropTypes.node, - /** - * The icon to display next to the tree item's label. - */ - icon: PropTypes.node, - /** - * The id of the item. - */ - itemId: PropTypes.string.isRequired, - /** - * The tree item label. - */ - label: PropTypes.node, - labelInputProps: PropTypes.shape({ - autoFocus: PropTypes.oneOf([true]), - 'data-element': PropTypes.oneOf(['labelInput']), - onBlur: PropTypes.func, - onChange: PropTypes.func, - onKeyDown: PropTypes.func, - type: PropTypes.oneOf(['text']), - value: PropTypes.string, - }), -} as any; - -export { TreeItemContent }; diff --git a/packages/x-tree-view/src/TreeItem/index.ts b/packages/x-tree-view/src/TreeItem/index.ts index 14485d1fd779..f8efc740e754 100644 --- a/packages/x-tree-view/src/TreeItem/index.ts +++ b/packages/x-tree-view/src/TreeItem/index.ts @@ -1,6 +1,12 @@ -export { TreeItem } from './TreeItem'; +export { + TreeItem, + TreeItemRoot, + TreeItemContent, + TreeItemIconContainer, + TreeItemGroupTransition, + TreeItemCheckbox, + TreeItemLabel, +} from './TreeItem'; export type { TreeItemProps, TreeItemSlots, TreeItemSlotProps } from './TreeItem.types'; + export * from './treeItemClasses'; -export * from './useTreeItemState'; -export { TreeItemContent } from './TreeItemContent'; -export type { TreeItemContentProps, TreeItemContentClassKey } from './TreeItemContent'; diff --git a/packages/x-tree-view/src/TreeItem/treeItemClasses.ts b/packages/x-tree-view/src/TreeItem/treeItemClasses.ts index 6b0efd7279ce..2769166f4676 100644 --- a/packages/x-tree-view/src/TreeItem/treeItemClasses.ts +++ b/packages/x-tree-view/src/TreeItem/treeItemClasses.ts @@ -16,7 +16,7 @@ export interface TreeItemClasses { focused: string; /** State class applied to the element when disabled. */ disabled: string; - /** Styles applied to the tree item icon. */ + /** Styles applied to the Tree Item icon. */ iconContainer: string; /** Styles applied to the label element. */ label: string; diff --git a/packages/x-tree-view/src/TreeItem/useTreeItemState.ts b/packages/x-tree-view/src/TreeItem/useTreeItemState.ts deleted file mode 100644 index f28549c2603c..000000000000 --- a/packages/x-tree-view/src/TreeItem/useTreeItemState.ts +++ /dev/null @@ -1,158 +0,0 @@ -'use client'; -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, - UseTreeViewExpansionSignature, - UseTreeViewFocusSignature, - UseTreeViewItemsSignature, -]; - -type UseTreeItemStateOptionalPlugins = readonly [UseTreeViewLabelSignature]; - -export function useTreeItemState(itemId: string) { - const { - instance, - items: { onItemClick }, - selection: { multiSelect, checkboxSelection, disableSelection }, - expansion: { expansionTrigger }, - } = useTreeViewContext(); - - const expandable = instance.isItemExpandable(itemId); - const expanded = instance.isItemExpanded(itemId); - 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) { - if (!focused) { - instance.focusItem(event, itemId); - } - - const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey); - - // If already expanded and trying to toggle selection don't close - if (expandable && !(multiple && instance.isItemExpanded(itemId))) { - instance.toggleItemExpansion(event, itemId); - } - } - }; - - const handleSelection = (event: React.MouseEvent) => { - if (!disabled) { - if (!focused) { - instance.focusItem(event, itemId); - } - - const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey); - if (multiple) { - if (event.shiftKey) { - instance.expandSelectionRange(event, itemId); - } else { - instance.selectItem({ event, itemId, keepExistingSelection: true }); - } - } else { - instance.selectItem({ event, itemId, shouldBeSelected: true }); - } - } - }; - - const handleCheckboxSelection = (event: React.ChangeEvent) => { - if (disableSelection || disabled) { - return; - } - - const hasShift = (event.nativeEvent as PointerEvent).shiftKey; - if (multiSelect && hasShift) { - instance.expandSelectionRange(event, itemId); - } else { - instance.selectItem({ - event, - itemId, - keepExistingSelection: multiSelect, - shouldBeSelected: event.target.checked, - }); - } - }; - - const preventSelection = (event: React.MouseEvent) => { - if (event.shiftKey || event.ctrlKey || event.metaKey || disabled) { - // Prevent text selection - event.preventDefault(); - } - }; - - 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, - handleSelection, - handleCheckboxSelection, - handleContentClick: onItemClick, - preventSelection, - expansionTrigger, - toggleItemEditing, - handleSaveItemLabel, - handleCancelItemLabelEditing, - }; -} diff --git a/packages/x-tree-view/src/TreeItem2/TreeItem2.test.tsx b/packages/x-tree-view/src/TreeItem2/TreeItem2.test.tsx deleted file mode 100644 index 524b3aca824f..000000000000 --- a/packages/x-tree-view/src/TreeItem2/TreeItem2.test.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import * as React from 'react'; -import { createRenderer } from '@mui/internal-test-utils'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; -import { treeItemClasses as classes } from '@mui/x-tree-view/TreeItem'; -import { TreeViewContext } from '@mui/x-tree-view/internals/TreeViewProvider/TreeViewContext'; -import { describeConformance } from 'test/utils/describeConformance'; -import { getFakeContextValue } from 'test/utils/tree-view/fakeContextValue'; -import { describeSlotsConformance } from 'test/utils/describeSlotsConformance'; - -describe('', () => { - const { render } = createRenderer(); - - describeConformance(, () => ({ - classes, - inheritComponent: 'li', - render: (item) => { - return render( - {item}, - ); - }, - muiName: 'MuiTreeItem2', - refInstanceof: window.HTMLLIElement, - skip: ['componentProp', 'componentsProp', 'themeVariants'], - })); - - describeSlotsConformance({ - render, - getElement: ({ props, slotName }) => ( - - - - ), - slots: { - label: { className: classes.label }, - iconContainer: { className: classes.iconContainer }, - content: { className: classes.content }, - checkbox: { className: classes.checkbox }, - }, - }); -}); diff --git a/packages/x-tree-view/src/TreeItem2/TreeItem2.tsx b/packages/x-tree-view/src/TreeItem2/TreeItem2.tsx deleted file mode 100644 index 86a398dee5d4..000000000000 --- a/packages/x-tree-view/src/TreeItem2/TreeItem2.tsx +++ /dev/null @@ -1,417 +0,0 @@ -'use client'; -import * as React from 'react'; -import PropTypes from 'prop-types'; -import clsx from 'clsx'; -import unsupportedProp from '@mui/utils/unsupportedProp'; -import { alpha } from '@mui/material/styles'; -import Collapse from '@mui/material/Collapse'; -import MuiCheckbox, { CheckboxProps } from '@mui/material/Checkbox'; -import useSlotProps from '@mui/utils/useSlotProps'; -import { shouldForwardProp } from '@mui/system/createStyled'; -import composeClasses from '@mui/utils/composeClasses'; -import { styled, createUseThemeProps } from '../internals/zero-styled'; -import { TreeItem2Props, TreeItem2OwnerState } from './TreeItem2.types'; -import { - 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'); - -export const TreeItem2Root = styled('li', { - name: 'MuiTreeItem2', - slot: 'Root', - overridesResolver: (props, styles) => styles.root, -})({ - listStyle: 'none', - margin: 0, - padding: 0, - outline: 0, -}); - -export const TreeItem2Content = styled('div', { - name: 'MuiTreeItem2', - slot: 'Content', - overridesResolver: (props, styles) => styles.content, - shouldForwardProp: (prop) => - shouldForwardProp(prop) && prop !== 'status' && prop !== 'indentationAtItemLevel', -})<{ status: UseTreeItem2Status; indentationAtItemLevel?: true }>(({ theme }) => ({ - padding: theme.spacing(0.5, 1), - borderRadius: theme.shape.borderRadius, - width: '100%', - boxSizing: 'border-box', // prevent width + padding to overflow - position: 'relative', - display: 'flex', - alignItems: 'center', - gap: theme.spacing(1), - cursor: 'pointer', - WebkitTapHighlightColor: 'transparent', - '&:hover': { - backgroundColor: (theme.vars || theme).palette.action.hover, - // Reset on touch devices, it doesn't add specificity - '@media (hover: none)': { - backgroundColor: 'transparent', - }, - }, - variants: [ - { - props: { indentationAtItemLevel: true }, - style: { - paddingLeft: `calc(${theme.spacing(1)} + var(--TreeView-itemChildrenIndentation) * var(--TreeView-itemDepth))`, - }, - }, - { - props: ({ status }: UseTreeItem2ContentSlotOwnProps) => status.disabled, - style: { - opacity: (theme.vars || theme).palette.action.disabledOpacity, - backgroundColor: 'transparent', - }, - }, - { - props: ({ status }: UseTreeItem2ContentSlotOwnProps) => status.focused, - style: { backgroundColor: (theme.vars || theme).palette.action.focus }, - }, - { - props: ({ status }: UseTreeItem2ContentSlotOwnProps) => status.selected, - style: { - backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.primary.mainChannel} / ${theme.vars.palette.action.selectedOpacity})` - : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity), - '&:hover': { - backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.primary.mainChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.hoverOpacity}))` - : alpha( - theme.palette.primary.main, - theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity, - ), - // Reset on touch devices, it doesn't add specificity - '@media (hover: none)': { - backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.primary.mainChannel} / ${theme.vars.palette.action.selectedOpacity})` - : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity), - }, - }, - }, - }, - { - props: ({ status }: UseTreeItem2ContentSlotOwnProps) => status.selected && status.focused, - style: { - backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.primary.mainChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.focusOpacity}))` - : alpha( - theme.palette.primary.main, - theme.palette.action.selectedOpacity + theme.palette.action.focusOpacity, - ), - }, - }, - ], -})); - -export const TreeItem2Label = styled('div', { - name: 'MuiTreeItem2', - slot: 'Label', - overridesResolver: (props, styles) => styles.label, - 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', { - name: 'MuiTreeItem2', - slot: 'IconContainer', - overridesResolver: (props, styles) => styles.iconContainer, -})({ - width: 16, - display: 'flex', - flexShrink: 0, - justifyContent: 'center', - '& svg': { - fontSize: 18, - }, -}); - -export const TreeItem2GroupTransition = styled(Collapse, { - name: 'MuiTreeItem2', - slot: 'GroupTransition', - overridesResolver: (props, styles) => styles.groupTransition, - shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'indentationAtItemLevel', -})<{ indentationAtItemLevel?: true }>({ - margin: 0, - padding: 0, - paddingLeft: 'var(--TreeView-itemChildrenIndentation)', - variants: [ - { - props: { indentationAtItemLevel: true }, - style: { paddingLeft: 0 }, - }, - ], -}); - -export const TreeItem2Checkbox = styled( - React.forwardRef( - (props: CheckboxProps & { visible: boolean }, ref: React.Ref) => { - const { visible, ...other } = props; - if (!visible) { - return null; - } - - return ; - }, - ), - { - name: 'MuiTreeItem2', - slot: 'Checkbox', - overridesResolver: (props, styles) => styles.checkbox, - }, -)({ - padding: 0, -}); - -const useUtilityClasses = (ownerState: TreeItem2OwnerState) => { - const { classes } = ownerState; - - const slots = { - root: ['root'], - content: ['content'], - expanded: ['expanded'], - editing: ['editing'], - editable: ['editable'], - selected: ['selected'], - focused: ['focused'], - disabled: ['disabled'], - iconContainer: ['iconContainer'], - checkbox: ['checkbox'], - label: ['label'], - groupTransition: ['groupTransition'], - labelInput: ['labelInput'], - dragAndDropOverlay: ['dragAndDropOverlay'], - }; - - return composeClasses(slots, getTreeItemUtilityClass, classes); -}; - -type TreeItem2Component = (( - props: TreeItem2Props & React.RefAttributes, -) => React.JSX.Element) & { propTypes?: any }; - -/** - * - * Demos: - * - * - [Tree View](https://mui.com/x/react-tree-view/) - * - * API: - * - * - [TreeItem2 API](https://mui.com/x/api/tree-view/tree-item-2/) - */ -export const TreeItem2 = React.forwardRef(function TreeItem2( - inProps: TreeItem2Props, - forwardedRef: React.Ref, -) { - const props = useThemeProps({ props: inProps, name: 'MuiTreeItem2' }); - - const { id, itemId, label, disabled, children, slots = {}, slotProps = {}, ...other } = props; - - const { - getRootProps, - getContentProps, - getIconContainerProps, - getCheckboxProps, - getLabelProps, - getGroupTransitionProps, - getLabelInputProps, - getDragAndDropOverlayProps, - status, - } = useTreeItem2({ - id, - itemId, - children, - label, - disabled, - }); - - const ownerState: TreeItem2OwnerState = { - ...props, - ...status, - }; - - const classes = useUtilityClasses(ownerState); - - const Root: React.ElementType = slots.root ?? TreeItem2Root; - const rootProps = useSlotProps({ - elementType: Root, - getSlotProps: getRootProps, - externalForwardedProps: other, - externalSlotProps: slotProps.root, - additionalProps: { - ref: forwardedRef, - }, - ownerState: {}, - className: classes.root, - }); - - const Content: React.ElementType = slots.content ?? TreeItem2Content; - const contentProps = useSlotProps({ - elementType: Content, - getSlotProps: getContentProps, - externalSlotProps: slotProps.content, - ownerState: {}, - className: clsx(classes.content, { - [classes.expanded]: status.expanded, - [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, - getSlotProps: getIconContainerProps, - externalSlotProps: slotProps.iconContainer, - ownerState: {}, - className: classes.iconContainer, - }); - - const Label: React.ElementType = slots.label ?? TreeItem2Label; - const labelProps = useSlotProps({ - elementType: Label, - getSlotProps: getLabelProps, - externalSlotProps: slotProps.label, - ownerState: {}, - className: classes.label, - }); - - const Checkbox: React.ElementType = slots.checkbox ?? TreeItem2Checkbox; - const checkboxProps = useSlotProps({ - elementType: Checkbox, - getSlotProps: getCheckboxProps, - externalSlotProps: slotProps.checkbox, - ownerState: {}, - className: classes.checkbox, - }); - - const GroupTransition: React.ElementType | undefined = slots.groupTransition ?? undefined; - const groupTransitionProps = useSlotProps({ - elementType: GroupTransition, - getSlotProps: getGroupTransitionProps, - externalSlotProps: slotProps.groupTransition, - ownerState: {}, - 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({ - elementType: DragAndDropOverlay, - getSlotProps: getDragAndDropOverlayProps, - externalSlotProps: slotProps.dragAndDropOverlay, - ownerState: {}, - className: classes.dragAndDropOverlay, - }); - - return ( - - - - - - - - {status.editing ? : - {children && } - - - ); -}) as TreeItem2Component; - -TreeItem2.propTypes = { - // ----------------------------- Warning -------------------------------- - // | These PropTypes are generated from the TypeScript type definitions | - // | To update them edit the TypeScript types and run "pnpm proptypes" | - // ---------------------------------------------------------------------- - /** - * The content of the component. - */ - children: PropTypes.node, - /** - * Override or extend the styles applied to the component. - */ - classes: PropTypes.object, - className: PropTypes.string, - /** - * If `true`, the item is disabled. - * @default false - */ - disabled: PropTypes.bool, - /** - * The id attribute of the item. If not provided, it will be generated. - */ - id: PropTypes.string, - /** - * The id of the item. - * Must be unique. - */ - itemId: PropTypes.string.isRequired, - /** - * The label of the item. - */ - label: PropTypes.node, - /** - * Callback fired when the item root is blurred. - */ - onBlur: PropTypes.func, - /** - * This prop isn't supported. - * Use the `onItemFocus` callback on the tree if you need to monitor an item's focus. - */ - onFocus: unsupportedProp, - /** - * Callback fired when a key is pressed on the keyboard and the tree is in focus. - */ - onKeyDown: PropTypes.func, - /** - * The props used for each component slot. - * @default {} - */ - slotProps: PropTypes.object, - /** - * Overridable component slots. - * @default {} - */ - slots: PropTypes.object, -} as any; diff --git a/packages/x-tree-view/src/TreeItem2/TreeItem2.types.ts b/packages/x-tree-view/src/TreeItem2/TreeItem2.types.ts deleted file mode 100644 index 192e62a92716..000000000000 --- a/packages/x-tree-view/src/TreeItem2/TreeItem2.types.ts +++ /dev/null @@ -1,97 +0,0 @@ -import * as React from 'react'; -import { SlotComponentProps } from '@mui/utils'; -import { UseTreeItem2Parameters, UseTreeItem2Status } from '../useTreeItem2'; -import { TreeItemClasses } from '../TreeItem'; -import { TreeItem2IconSlotProps, TreeItem2IconSlots } from '../TreeItem2Icon'; -import { MuiCancellableEventHandler } from '../internals/models/MuiCancellableEvent'; - -export interface TreeItem2Slots extends TreeItem2IconSlots { - /** - * The component that renders the root. - * @default TreeItem2Root - */ - root?: React.ElementType; - /** - * The component that renders the content of the item. - * (e.g.: everything related to this item, not to its children). - * @default TreeItem2Content - */ - content?: React.ElementType; - /** - * The component that renders the children of the item. - * @default TreeItem2GroupTransition - */ - groupTransition?: React.ElementType; - /** - * The component that renders the icon. - * @default TreeItem2IconContainer - */ - iconContainer?: React.ElementType; - /** - * The component that renders the item checkbox for selection. - * @default TreeItem2Checkbox - */ - checkbox?: React.ElementType; - /** - * The component that renders the item label. - * @default TreeItem2Label - */ - label?: React.ElementType; - /** - * The component that renders the input to edit the label when the item is editable and is currently being edited. - * @default TreeItem2LabelInput - */ - labelInput?: React.ElementType; - /** - * The component that renders the overlay when an item reordering is ongoing. - * Warning: This slot is only useful when using the `RichTreeViewPro` component. - * @default TreeItem2DragAndDropOverlay - */ - dragAndDropOverlay?: React.ElementType; -} - -export interface TreeItem2SlotProps extends TreeItem2IconSlotProps { - root?: SlotComponentProps<'li', {}, {}>; - content?: SlotComponentProps<'div', {}, {}>; - groupTransition?: SlotComponentProps<'div', {}, {}>; - iconContainer?: SlotComponentProps<'div', {}, {}>; - checkbox?: SlotComponentProps<'button', {}, {}>; - label?: SlotComponentProps<'div', {}, {}>; - labelInput?: SlotComponentProps<'input', {}, {}>; - dragAndDropOverlay?: SlotComponentProps<'div', {}, {}>; -} - -export interface TreeItem2Props - extends Omit, - Omit, 'onFocus'> { - className?: string; - /** - * Override or extend the styles applied to the component. - */ - classes?: Partial; - /** - * Overridable component slots. - * @default {} - */ - slots?: TreeItem2Slots; - /** - * The props used for each component slot. - * @default {} - */ - slotProps?: TreeItem2SlotProps; - /** - * This prop isn't supported. - * Use the `onItemFocus` callback on the tree if you need to monitor an item's focus. - */ - onFocus?: null; - /** - * Callback fired when the item root is blurred. - */ - onBlur?: MuiCancellableEventHandler>; - /** - * Callback fired when a key is pressed on the keyboard and the tree is in focus. - */ - onKeyDown?: MuiCancellableEventHandler>; -} - -export interface TreeItem2OwnerState extends Omit, UseTreeItem2Status {} diff --git a/packages/x-tree-view/src/TreeItem2/index.ts b/packages/x-tree-view/src/TreeItem2/index.ts deleted file mode 100644 index 9bc803abb6c8..000000000000 --- a/packages/x-tree-view/src/TreeItem2/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export { - TreeItem2, - TreeItem2Root, - TreeItem2Content, - TreeItem2IconContainer, - TreeItem2GroupTransition, - TreeItem2Checkbox, - TreeItem2Label, -} from './TreeItem2'; -export type { TreeItem2Props, TreeItem2Slots, TreeItem2SlotProps } from './TreeItem2.types'; diff --git a/packages/x-tree-view/src/TreeItem2DragAndDropOverlay/TreeItem2DragAndDropOverlay.tsx b/packages/x-tree-view/src/TreeItem2DragAndDropOverlay/TreeItem2DragAndDropOverlay.tsx deleted file mode 100644 index db551010137a..000000000000 --- a/packages/x-tree-view/src/TreeItem2DragAndDropOverlay/TreeItem2DragAndDropOverlay.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from 'react'; -import { alpha } from '@mui/material/styles'; -import { shouldForwardProp } from '@mui/system'; -import { TreeItem2DragAndDropOverlayProps } from './TreeItem2DragAndDropOverlay.types'; -import { TreeViewItemsReorderingAction } from '../models'; -import { styled } from '../internals/zero-styled'; - -const TreeItem2DragAndDropOverlayRoot = styled('div', { - name: 'MuiTreeItem2DragAndDropOverlay', - slot: 'Root', - overridesResolver: (props, styles) => styles.root, - shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'action', -})<{ action?: TreeViewItemsReorderingAction | null }>(({ theme }) => ({ - position: 'absolute', - left: 0, - display: 'flex', - top: 0, - bottom: 0, - right: 0, - pointerEvents: 'none', - variants: [ - { - props: { action: 'make-child' }, - style: { - marginLeft: 'calc(var(--TreeView-indentMultiplier) * var(--TreeView-itemDepth))', - borderRadius: theme.shape.borderRadius, - backgroundColor: theme.vars - ? `rgba(${theme.vars.palette.primary.dark} / ${0.15})` - : alpha(theme.palette.primary.dark, 0.15), - }, - }, - { - props: { action: 'reorder-above' }, - style: { - marginLeft: 'calc(var(--TreeView-indentMultiplier) * var(--TreeView-itemDepth))', - borderTop: theme.vars - ? `1px solid rgba(${theme.vars.palette.grey[900]} / ${0.6})` - : `1px solid ${alpha(theme.palette.grey[900], 0.6)}`, - ...theme.applyStyles('dark', { - borderTopColor: theme.vars - ? `rgba(${theme.vars.palette.grey[100]} / ${0.6})` - : alpha(theme.palette.grey[100], 0.6), - }), - }, - }, - { - props: { action: 'reorder-below' }, - style: { - marginLeft: 'calc(var(--TreeView-indentMultiplier) * var(--TreeView-itemDepth))', - borderBottom: theme.vars - ? `1px solid rgba(${theme.vars.palette.grey[900]} / ${0.6})` - : `1px solid ${alpha(theme.palette.grey[900], 0.6)}`, - ...theme.applyStyles('dark', { - borderBottomColor: theme.vars - ? `rgba(${theme.vars.palette.grey[100]} / ${0.6})` - : alpha(theme.palette.grey[100], 0.6), - }), - }, - }, - { - props: { action: 'move-to-parent' }, - style: { - marginLeft: - 'calc(var(--TreeView-indentMultiplier) * calc(var(--TreeView-itemDepth) - 1))' as any, - borderBottom: theme.vars - ? `1px solid rgba(${theme.vars.palette.grey[900]} / ${0.6})` - : `1px solid ${alpha(theme.palette.grey[900], 0.6)}`, - ...theme.applyStyles('dark', { - borderBottomColor: theme.vars - ? `rgba(${theme.vars.palette.grey[100]} / ${0.6})` - : alpha(theme.palette.grey[100], 0.6), - }), - }, - }, - ], -})); - -function TreeItem2DragAndDropOverlay(props: TreeItem2DragAndDropOverlayProps) { - if (props.action == null) { - return null; - } - - return ; -} - -export { TreeItem2DragAndDropOverlay }; diff --git a/packages/x-tree-view/src/TreeItem2DragAndDropOverlay/index.ts b/packages/x-tree-view/src/TreeItem2DragAndDropOverlay/index.ts deleted file mode 100644 index 548f1827a8bc..000000000000 --- a/packages/x-tree-view/src/TreeItem2DragAndDropOverlay/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { TreeItem2DragAndDropOverlay } from './TreeItem2DragAndDropOverlay'; -export type { TreeItem2DragAndDropOverlayProps } from './TreeItem2DragAndDropOverlay.types'; diff --git a/packages/x-tree-view/src/TreeItem2Icon/index.ts b/packages/x-tree-view/src/TreeItem2Icon/index.ts deleted file mode 100644 index d24d4ccc8963..000000000000 --- a/packages/x-tree-view/src/TreeItem2Icon/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { TreeItem2Icon } from './TreeItem2Icon'; -export type { - TreeItem2IconProps, - TreeItem2IconSlots, - TreeItem2IconSlotProps, -} from './TreeItem2Icon.types'; diff --git a/packages/x-tree-view/src/TreeItem2LabelInput/index.ts b/packages/x-tree-view/src/TreeItem2LabelInput/index.ts deleted file mode 100644 index c779e21303b1..000000000000 --- a/packages/x-tree-view/src/TreeItem2LabelInput/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { TreeItem2LabelInput } from './TreeItem2LabelInput'; -export type { TreeItem2LabelInputProps } from './TreeItem2LabelInput.types'; diff --git a/packages/x-tree-view/src/TreeItem2Provider/index.ts b/packages/x-tree-view/src/TreeItem2Provider/index.ts deleted file mode 100644 index d569e8cd1516..000000000000 --- a/packages/x-tree-view/src/TreeItem2Provider/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { TreeItem2Provider } from './TreeItem2Provider'; -export type { TreeItem2ProviderProps } from './TreeItem2Provider.types'; diff --git a/packages/x-tree-view/src/TreeItemDragAndDropOverlay/TreeItemDragAndDropOverlay.tsx b/packages/x-tree-view/src/TreeItemDragAndDropOverlay/TreeItemDragAndDropOverlay.tsx new file mode 100644 index 000000000000..3957823b2919 --- /dev/null +++ b/packages/x-tree-view/src/TreeItemDragAndDropOverlay/TreeItemDragAndDropOverlay.tsx @@ -0,0 +1,76 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { alpha } from '@mui/material/styles'; +import { shouldForwardProp } from '@mui/system'; +import { TreeItemDragAndDropOverlayProps } from './TreeItemDragAndDropOverlay.types'; +import { TreeViewItemsReorderingAction } from '../models'; +import { styled } from '../internals/zero-styled'; + +const TreeItemDragAndDropOverlayRoot = styled('div', { + name: 'MuiTreeItemDragAndDropOverlay', + slot: 'Root', + overridesResolver: (props, styles) => styles.root, + shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'action', +})<{ action?: TreeViewItemsReorderingAction | null }>(({ theme }) => ({ + position: 'absolute', + left: 0, + display: 'flex', + top: 0, + bottom: 0, + right: 0, + pointerEvents: 'none', + variants: [ + { + props: { action: 'make-child' }, + style: { + marginLeft: 'calc(var(--TreeView-indentMultiplier) * var(--TreeView-itemDepth))', + borderRadius: theme.shape.borderRadius, + backgroundColor: theme.vars + ? `rgba(${theme.vars.palette.primary.darkChannel} / ${theme.vars.palette.action.focusOpacity})` + : alpha(theme.palette.primary.dark, theme.palette.action.focusOpacity), + }, + }, + { + props: { action: 'reorder-above' }, + style: { + marginLeft: 'calc(var(--TreeView-indentMultiplier) * var(--TreeView-itemDepth))', + borderTop: `1px solid ${(theme.vars || theme).palette.action.active}`, + }, + }, + { + props: { action: 'reorder-below' }, + style: { + marginLeft: 'calc(var(--TreeView-indentMultiplier) * var(--TreeView-itemDepth))', + borderBottom: `1px solid ${(theme.vars || theme).palette.action.active}`, + }, + }, + { + props: { action: 'move-to-parent' }, + style: { + marginLeft: + 'calc(var(--TreeView-indentMultiplier) * calc(var(--TreeView-itemDepth) - 1))' as any, + borderBottom: `1px solid ${(theme.vars || theme).palette.action.active}`, + }, + }, + ], +})); + +function TreeItemDragAndDropOverlay(props: TreeItemDragAndDropOverlayProps) { + if (props.action == null) { + return null; + } + + return ; +} + +TreeItemDragAndDropOverlay.propTypes = { + // ----------------------------- Warning -------------------------------- + // | These PropTypes are generated from the TypeScript type definitions | + // | To update them edit the TypeScript types and run "pnpm proptypes" | + // ---------------------------------------------------------------------- + action: PropTypes.oneOf(['make-child', 'move-to-parent', 'reorder-above', 'reorder-below']), + style: PropTypes.object, +} as any; + +export { TreeItemDragAndDropOverlay }; diff --git a/packages/x-tree-view/src/TreeItem2DragAndDropOverlay/TreeItem2DragAndDropOverlay.types.ts b/packages/x-tree-view/src/TreeItemDragAndDropOverlay/TreeItemDragAndDropOverlay.types.ts similarity index 76% rename from packages/x-tree-view/src/TreeItem2DragAndDropOverlay/TreeItem2DragAndDropOverlay.types.ts rename to packages/x-tree-view/src/TreeItemDragAndDropOverlay/TreeItemDragAndDropOverlay.types.ts index a2b3b4d22468..983e0c01b4c3 100644 --- a/packages/x-tree-view/src/TreeItem2DragAndDropOverlay/TreeItem2DragAndDropOverlay.types.ts +++ b/packages/x-tree-view/src/TreeItemDragAndDropOverlay/TreeItemDragAndDropOverlay.types.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { TreeViewItemsReorderingAction } from '../models'; -export interface TreeItem2DragAndDropOverlayProps { +export interface TreeItemDragAndDropOverlayProps { action?: TreeViewItemsReorderingAction; style?: React.CSSProperties; } diff --git a/packages/x-tree-view/src/TreeItemDragAndDropOverlay/index.ts b/packages/x-tree-view/src/TreeItemDragAndDropOverlay/index.ts new file mode 100644 index 000000000000..965280342e86 --- /dev/null +++ b/packages/x-tree-view/src/TreeItemDragAndDropOverlay/index.ts @@ -0,0 +1,2 @@ +export { TreeItemDragAndDropOverlay } from './TreeItemDragAndDropOverlay'; +export type { TreeItemDragAndDropOverlayProps } from './TreeItemDragAndDropOverlay.types'; diff --git a/packages/x-tree-view/src/TreeItem2Icon/TreeItem2Icon.tsx b/packages/x-tree-view/src/TreeItemIcon/TreeItemIcon.tsx similarity index 93% rename from packages/x-tree-view/src/TreeItem2Icon/TreeItem2Icon.tsx rename to packages/x-tree-view/src/TreeItemIcon/TreeItemIcon.tsx index 7c5c20b56b4e..77db494f4bf8 100644 --- a/packages/x-tree-view/src/TreeItem2Icon/TreeItem2Icon.tsx +++ b/packages/x-tree-view/src/TreeItemIcon/TreeItemIcon.tsx @@ -1,13 +1,14 @@ +'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; import resolveComponentProps from '@mui/utils/resolveComponentProps'; import useSlotProps from '@mui/utils/useSlotProps'; -import { TreeItem2IconProps } from './TreeItem2Icon.types'; +import { TreeItemIconProps } from './TreeItemIcon.types'; import { useTreeViewContext } from '../internals/TreeViewProvider'; import { UseTreeViewIconsSignature } from '../internals/plugins/useTreeViewIcons'; import { TreeViewCollapseIcon, TreeViewExpandIcon } from '../icons'; -function TreeItem2Icon(props: TreeItem2IconProps) { +function TreeItemIcon(props: TreeItemIconProps) { const { slots, slotProps, status } = props; const context = useTreeViewContext<[UseTreeViewIconsSignature]>(); @@ -54,7 +55,7 @@ function TreeItem2Icon(props: TreeItem2IconProps) { return ; } -TreeItem2Icon.propTypes = { +TreeItemIcon.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "pnpm proptypes" | @@ -80,4 +81,4 @@ TreeItem2Icon.propTypes = { }).isRequired, } as any; -export { TreeItem2Icon }; +export { TreeItemIcon }; diff --git a/packages/x-tree-view/src/TreeItem2Icon/TreeItem2Icon.types.ts b/packages/x-tree-view/src/TreeItemIcon/TreeItemIcon.types.ts similarity index 68% rename from packages/x-tree-view/src/TreeItem2Icon/TreeItem2Icon.types.ts rename to packages/x-tree-view/src/TreeItemIcon/TreeItemIcon.types.ts index cd66abb4f8a2..fd04bb422fd6 100644 --- a/packages/x-tree-view/src/TreeItem2Icon/TreeItem2Icon.types.ts +++ b/packages/x-tree-view/src/TreeItemIcon/TreeItemIcon.types.ts @@ -1,8 +1,8 @@ import * as React from 'react'; import { SlotComponentProps } from '@mui/utils'; -import { UseTreeItem2Status } from '../useTreeItem2'; +import { UseTreeItemStatus } from '../useTreeItem'; -export interface TreeItem2IconSlots { +export interface TreeItemIconSlots { /** * The icon used to collapse the item. */ @@ -16,28 +16,28 @@ export interface TreeItem2IconSlots { */ endIcon?: React.ElementType; /** - * The icon to display next to the tree item's label. + * The icon to display next to the Tree Item's label. */ icon?: React.ElementType; } -export interface TreeItem2IconSlotProps { +export interface TreeItemIconSlotProps { collapseIcon?: SlotComponentProps<'svg', {}, {}>; expandIcon?: SlotComponentProps<'svg', {}, {}>; endIcon?: SlotComponentProps<'svg', {}, {}>; icon?: SlotComponentProps<'svg', {}, {}>; } -export interface TreeItem2IconProps { - status: UseTreeItem2Status; +export interface TreeItemIconProps { + status: UseTreeItemStatus; /** * Overridable component slots. * @default {} */ - slots?: TreeItem2IconSlots; + slots?: TreeItemIconSlots; /** * The props used for each component slot. * @default {} */ - slotProps?: TreeItem2IconSlotProps; + slotProps?: TreeItemIconSlotProps; } diff --git a/packages/x-tree-view/src/TreeItemIcon/index.ts b/packages/x-tree-view/src/TreeItemIcon/index.ts new file mode 100644 index 000000000000..d94baff08113 --- /dev/null +++ b/packages/x-tree-view/src/TreeItemIcon/index.ts @@ -0,0 +1,6 @@ +export { TreeItemIcon } from './TreeItemIcon'; +export type { + TreeItemIconProps, + TreeItemIconSlots, + TreeItemIconSlotProps, +} from './TreeItemIcon.types'; diff --git a/packages/x-tree-view/src/TreeItem2LabelInput/TreeItem2LabelInput.tsx b/packages/x-tree-view/src/TreeItemLabelInput/TreeItemLabelInput.tsx similarity index 75% rename from packages/x-tree-view/src/TreeItem2LabelInput/TreeItem2LabelInput.tsx rename to packages/x-tree-view/src/TreeItemLabelInput/TreeItemLabelInput.tsx index f12f822d8407..1ae9ebe7f804 100644 --- a/packages/x-tree-view/src/TreeItem2LabelInput/TreeItem2LabelInput.tsx +++ b/packages/x-tree-view/src/TreeItemLabelInput/TreeItemLabelInput.tsx @@ -1,7 +1,10 @@ import { styled } from '../internals/zero-styled'; -const TreeItem2LabelInput = styled('input', { - name: 'MuiTreeItem2', +/** + * @ignore - internal component. + */ +const TreeItemLabelInput = styled('input', { + name: 'MuiTreeItem', slot: 'LabelInput', overridesResolver: (props, styles) => styles.labelInput, })(({ theme }) => ({ @@ -17,4 +20,4 @@ const TreeItem2LabelInput = styled('input', { }, })); -export { TreeItem2LabelInput }; +export { TreeItemLabelInput }; diff --git a/packages/x-tree-view/src/TreeItem2LabelInput/TreeItem2LabelInput.types.ts b/packages/x-tree-view/src/TreeItemLabelInput/TreeItemLabelInput.types.ts similarity index 52% rename from packages/x-tree-view/src/TreeItem2LabelInput/TreeItem2LabelInput.types.ts rename to packages/x-tree-view/src/TreeItemLabelInput/TreeItemLabelInput.types.ts index 099b8c5b2b5e..d4a810ee0067 100644 --- a/packages/x-tree-view/src/TreeItem2LabelInput/TreeItem2LabelInput.types.ts +++ b/packages/x-tree-view/src/TreeItemLabelInput/TreeItemLabelInput.types.ts @@ -1,15 +1,15 @@ import * as React from 'react'; -import { MuiCancellableEventHandler } from '../internals/models/MuiCancellableEvent'; +import { TreeViewCancellableEventHandler } from '../models'; -export interface TreeItem2LabelInputProps { +export interface TreeItemLabelInputProps { value?: string; /** * Used to determine if the target of keydown or blur events is the input and prevent the event from propagating to the root. */ 'data-element'?: 'labelInput'; onChange?: React.ChangeEventHandler; - onKeyDown?: MuiCancellableEventHandler>; - onBlur?: MuiCancellableEventHandler>; + onKeyDown?: TreeViewCancellableEventHandler>; + onBlur?: TreeViewCancellableEventHandler>; autoFocus?: true; type?: 'text'; } diff --git a/packages/x-tree-view/src/TreeItemLabelInput/index.ts b/packages/x-tree-view/src/TreeItemLabelInput/index.ts new file mode 100644 index 000000000000..e616f5d5b401 --- /dev/null +++ b/packages/x-tree-view/src/TreeItemLabelInput/index.ts @@ -0,0 +1,2 @@ +export { TreeItemLabelInput } from './TreeItemLabelInput'; +export type { TreeItemLabelInputProps } from './TreeItemLabelInput.types'; diff --git a/packages/x-tree-view/src/TreeItem2Provider/TreeItem2Provider.tsx b/packages/x-tree-view/src/TreeItemProvider/TreeItemProvider.tsx similarity index 73% rename from packages/x-tree-view/src/TreeItem2Provider/TreeItem2Provider.tsx rename to packages/x-tree-view/src/TreeItemProvider/TreeItemProvider.tsx index ad586535f16f..e50cc7f9f81b 100644 --- a/packages/x-tree-view/src/TreeItem2Provider/TreeItem2Provider.tsx +++ b/packages/x-tree-view/src/TreeItemProvider/TreeItemProvider.tsx @@ -1,15 +1,18 @@ import PropTypes from 'prop-types'; -import { TreeItem2ProviderProps } from './TreeItem2Provider.types'; +import { TreeItemProviderProps } from './TreeItemProvider.types'; import { useTreeViewContext } from '../internals/TreeViewProvider'; -function TreeItem2Provider(props: TreeItem2ProviderProps) { +/** + * @ignore - internal component. + */ +function TreeItemProvider(props: TreeItemProviderProps) { const { children, itemId } = props; const { wrapItem, instance } = useTreeViewContext<[]>(); return wrapItem({ children, itemId, instance }); } -TreeItem2Provider.propTypes = { +TreeItemProvider.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "pnpm proptypes" | @@ -18,4 +21,4 @@ TreeItem2Provider.propTypes = { itemId: PropTypes.string.isRequired, } as any; -export { TreeItem2Provider }; +export { TreeItemProvider }; diff --git a/packages/x-tree-view/src/TreeItem2Provider/TreeItem2Provider.types.ts b/packages/x-tree-view/src/TreeItemProvider/TreeItemProvider.types.ts similarity index 76% rename from packages/x-tree-view/src/TreeItem2Provider/TreeItem2Provider.types.ts rename to packages/x-tree-view/src/TreeItemProvider/TreeItemProvider.types.ts index 8ad31aca2197..5214935419c7 100644 --- a/packages/x-tree-view/src/TreeItem2Provider/TreeItem2Provider.types.ts +++ b/packages/x-tree-view/src/TreeItemProvider/TreeItemProvider.types.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { TreeViewItemId } from '../models'; -export interface TreeItem2ProviderProps { +export interface TreeItemProviderProps { children: React.ReactNode; itemId: TreeViewItemId; } diff --git a/packages/x-tree-view/src/TreeItemProvider/index.ts b/packages/x-tree-view/src/TreeItemProvider/index.ts new file mode 100644 index 000000000000..8ec7d54218bd --- /dev/null +++ b/packages/x-tree-view/src/TreeItemProvider/index.ts @@ -0,0 +1,2 @@ +export { TreeItemProvider } from './TreeItemProvider'; +export type { TreeItemProviderProps } from './TreeItemProvider.types'; diff --git a/packages/x-tree-view/src/TreeView/TreeView.tsx b/packages/x-tree-view/src/TreeView/TreeView.tsx index 73c38055ee9a..06be3ecba63a 100644 --- a/packages/x-tree-view/src/TreeView/TreeView.tsx +++ b/packages/x-tree-view/src/TreeView/TreeView.tsx @@ -50,7 +50,7 @@ const warn = () => { }; /** - * This component has been deprecated in favor of the new `SimpleTreeView` component. + * This component has been deprecated in favor of the new Simple Tree View component. * You can have a look at how to migrate to the new component in the v7 [migration guide](https://mui.com/x/migration/migration-tree-view-v6/#use-simpletreeview-instead-of-treeview) * * Demos: @@ -104,7 +104,7 @@ TreeView.propTypes = { }), }), /** - * If `true`, the tree view renders a checkbox at the left of its label that allows selecting it. + * If `true`, the Tree View renders a checkbox at the left of its label that allows selecting it. * @default false */ checkboxSelection: PropTypes.bool, @@ -174,39 +174,39 @@ TreeView.propTypes = { */ multiSelect: PropTypes.bool, /** - * Callback fired when tree items are expanded/collapsed. + * Callback fired when Tree Items are expanded/collapsed. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemIds The ids of the expanded items. */ onExpandedItemsChange: PropTypes.func, /** - * Callback fired when the `content` slot of a given tree item is clicked. + * Callback fired when the `content` slot of a given Tree Item is clicked. * @param {React.MouseEvent} event The DOM event that triggered the change. * @param {string} itemId The id of the focused item. */ onItemClick: PropTypes.func, /** - * Callback fired when a tree item is expanded or collapsed. + * Callback fired when a Tree Item is expanded or collapsed. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemId The itemId of the modified item. * @param {array} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed. */ onItemExpansionToggle: PropTypes.func, /** - * Callback fired when a given tree item is focused. + * Callback fired when a given Tree Item is focused. * @param {React.SyntheticEvent | null} event The DOM event that triggered the change. **Warning**: This is a generic event not a focus event. * @param {string} itemId The id of the focused item. */ onItemFocus: PropTypes.func, /** - * Callback fired when a tree item is selected or deselected. + * Callback fired when a Tree Item is selected or deselected. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemId The itemId of the modified item. * @param {array} isSelected `true` if the item has just been selected, `false` if it has just been deselected. */ onItemSelectionToggle: PropTypes.func, /** - * Callback fired when tree items are selected/deselected. + * Callback fired when Tree Items are selected/deselected. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {string[] | string} itemIds The ids of the selected items. * When `multiSelect` is `true`, this is an array of strings; when false (default) a string. @@ -217,6 +217,26 @@ TreeView.propTypes = { * When `multiSelect` is true this takes an array of strings; when false (default) a string. */ selectedItems: PropTypes.any, + /** + * When `selectionPropagation.descendants` is set to `true`. + * + * - Selecting a parent selects all its descendants automatically. + * - Deselecting a parent deselects all its descendants automatically. + * + * When `selectionPropagation.parents` is set to `true`. + * + * - Selecting all the descendants of a parent selects the parent automatically. + * - Deselecting a descendant of a selected parent deselects the parent automatically. + * + * Only works when `multiSelect` is `true`. + * On the , only the expanded items are considered (since the collapsed item are not passed to the Tree View component at all) + * + * @default { parents: false, descendants: false } + */ + selectionPropagation: PropTypes.shape({ + descendants: PropTypes.bool, + parents: PropTypes.bool, + }), /** * The props used for each component slot. */ diff --git a/packages/x-tree-view/src/hooks/index.ts b/packages/x-tree-view/src/hooks/index.ts index 222c64e36056..5b9960c618bf 100644 --- a/packages/x-tree-view/src/hooks/index.ts +++ b/packages/x-tree-view/src/hooks/index.ts @@ -1,2 +1,2 @@ export { useTreeViewApiRef } from './useTreeViewApiRef'; -export { useTreeItem2Utils } from './useTreeItem2Utils'; +export { useTreeItemUtils } from './useTreeItemUtils'; diff --git a/packages/x-tree-view/src/hooks/useTreeItem2Utils/index.ts b/packages/x-tree-view/src/hooks/useTreeItem2Utils/index.ts deleted file mode 100644 index 71c065769d1f..000000000000 --- a/packages/x-tree-view/src/hooks/useTreeItem2Utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useTreeItem2Utils } from './useTreeItem2Utils'; diff --git a/packages/x-tree-view/src/hooks/useTreeItemUtils/index.ts b/packages/x-tree-view/src/hooks/useTreeItemUtils/index.ts new file mode 100644 index 000000000000..215ff43b58cc --- /dev/null +++ b/packages/x-tree-view/src/hooks/useTreeItemUtils/index.ts @@ -0,0 +1 @@ +export { useTreeItemUtils } from './useTreeItemUtils'; diff --git a/packages/x-tree-view/src/hooks/useTreeItem2Utils/useTreeItem2Utils.tsx b/packages/x-tree-view/src/hooks/useTreeItemUtils/useTreeItemUtils.tsx similarity index 82% rename from packages/x-tree-view/src/hooks/useTreeItem2Utils/useTreeItem2Utils.tsx rename to packages/x-tree-view/src/hooks/useTreeItemUtils/useTreeItemUtils.tsx index cac94733ea71..ef03b9510dc3 100644 --- a/packages/x-tree-view/src/hooks/useTreeItem2Utils/useTreeItem2Utils.tsx +++ b/packages/x-tree-view/src/hooks/useTreeItemUtils/useTreeItemUtils.tsx @@ -1,5 +1,6 @@ +'use client'; import * as React from 'react'; -import { MuiCancellableEvent } from '../../internals/models/MuiCancellableEvent'; +import { TreeViewCancellableEvent } from '../../models'; import { useTreeViewContext } from '../../internals/TreeViewProvider'; import { UseTreeViewSelectionSignature } from '../../internals/plugins/useTreeViewSelection'; import { UseTreeViewExpansionSignature } from '../../internals/plugins/useTreeViewExpansion'; @@ -9,11 +10,11 @@ import { UseTreeViewLabelSignature, useTreeViewLabel, } from '../../internals/plugins/useTreeViewLabel'; -import type { UseTreeItem2Status } from '../../useTreeItem2'; +import type { UseTreeItemStatus } from '../../useTreeItem'; import { hasPlugin } from '../../internals/utils/plugins'; import { TreeViewPublicAPI } from '../../internals/models'; -export interface UseTreeItem2Interactions { +export interface UseTreeItemInteractions { handleExpansion: (event: React.MouseEvent) => void; handleSelection: (event: React.MouseEvent) => void; handleCheckboxSelection: (event: React.ChangeEvent) => void; @@ -23,9 +24,9 @@ export interface UseTreeItem2Interactions { } /** - * Plugins that need to be present in the Tree View in order for `useTreeItem2Utils` to work correctly. + * Plugins that need to be present in the Tree View in order for `useTreeItemUtils` to work correctly. */ -type UseTreeItem2UtilsMinimalPlugins = readonly [ +type UseTreeItemUtilsMinimalPlugins = readonly [ UseTreeViewSelectionSignature, UseTreeViewExpansionSignature, UseTreeViewItemsSignature, @@ -33,17 +34,17 @@ type UseTreeItem2UtilsMinimalPlugins = readonly [ ]; /** - * Plugins that `useTreeItem2Utils` can use if they are present, but are not required. + * Plugins that `useTreeItemUtils` can use if they are present, but are not required. */ -export type UseTreeItem2UtilsOptionalPlugins = readonly [UseTreeViewLabelSignature]; +export type UseTreeItemUtilsOptionalPlugins = readonly [UseTreeViewLabelSignature]; -interface UseTreeItem2UtilsReturnValue< - TSignatures extends UseTreeItem2UtilsMinimalPlugins, - TOptionalSignatures extends UseTreeItem2UtilsOptionalPlugins, +interface UseTreeItemUtilsReturnValue< + TSignatures extends UseTreeItemUtilsMinimalPlugins, + TOptionalSignatures extends UseTreeItemUtilsOptionalPlugins, > { - interactions: UseTreeItem2Interactions; - status: UseTreeItem2Status; + interactions: UseTreeItemInteractions; + status: UseTreeItemStatus; /** * The object the allows Tree View manipulation. */ @@ -57,23 +58,23 @@ const isItemExpandable = (reactChildren: React.ReactNode) => { return Boolean(reactChildren); }; -export const useTreeItem2Utils = < - TSignatures extends UseTreeItem2UtilsMinimalPlugins = UseTreeItem2UtilsMinimalPlugins, - TOptionalSignatures extends UseTreeItem2UtilsOptionalPlugins = UseTreeItem2UtilsOptionalPlugins, +export const useTreeItemUtils = < + TSignatures extends UseTreeItemUtilsMinimalPlugins = UseTreeItemUtilsMinimalPlugins, + TOptionalSignatures extends UseTreeItemUtilsOptionalPlugins = UseTreeItemUtilsOptionalPlugins, >({ itemId, children, }: { itemId: string; children: React.ReactNode; -}): UseTreeItem2UtilsReturnValue => { +}): UseTreeItemUtilsReturnValue => { const { instance, selection: { multiSelect }, publicAPI, } = useTreeViewContext(); - const status: UseTreeItem2Status = { + const status: UseTreeItemStatus = { expandable: isItemExpandable(children), expanded: instance.isItemExpanded(itemId), focused: instance.isItemFocused(itemId), @@ -150,7 +151,7 @@ export const useTreeItem2Utils = < }; const handleSaveItemLabel = ( - event: React.SyntheticEvent & MuiCancellableEvent, + event: React.SyntheticEvent & TreeViewCancellableEvent, label: string, ) => { if (!hasPlugin(instance, useTreeViewLabel)) { @@ -179,7 +180,7 @@ export const useTreeItem2Utils = < } }; - const interactions: UseTreeItem2Interactions = { + const interactions: UseTreeItemInteractions = { handleExpansion, handleSelection, handleCheckboxSelection, diff --git a/packages/x-tree-view/src/index.ts b/packages/x-tree-view/src/index.ts index de34fea12793..f927d204612b 100644 --- a/packages/x-tree-view/src/index.ts +++ b/packages/x-tree-view/src/index.ts @@ -5,10 +5,11 @@ export * from './RichTreeView'; // Tree Item export * from './TreeItem'; -export * from './TreeItem2'; -export * from './useTreeItem2'; -export * from './TreeItem2Icon'; -export * from './TreeItem2Provider'; +export * from './useTreeItem'; +export * from './TreeItemIcon'; +export * from './TreeItemProvider'; +export * from './TreeItemDragAndDropOverlay'; +export * from './TreeItemLabelInput'; export { unstable_resetCleanupTracking } from './internals/hooks/useInstanceEventHandler'; diff --git a/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.types.ts b/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.types.ts index bbb2e0dca242..be08aeb5afe4 100644 --- a/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.types.ts +++ b/packages/x-tree-view/src/internals/TreeViewProvider/TreeViewProvider.types.ts @@ -9,9 +9,10 @@ import { TreeViewPublicAPI, } from '../models'; import { TreeViewCorePluginSignatures } from '../corePlugins'; +import type { TreeItemProps } from '../../TreeItem'; -export type TreeViewItemPluginsRunner = ( - props: TProps, +export type TreeViewItemPluginsRunner = ( + props: TreeItemProps, ) => Required; export type TreeViewContextValue< diff --git a/packages/x-tree-view/src/internals/components/RichTreeViewItems.tsx b/packages/x-tree-view/src/internals/components/RichTreeViewItems.tsx index 9fc112bca9a7..af1c4abeb5f2 100644 --- a/packages/x-tree-view/src/internals/components/RichTreeViewItems.tsx +++ b/packages/x-tree-view/src/internals/components/RichTreeViewItems.tsx @@ -1,9 +1,7 @@ import * as React from 'react'; import useSlotProps from '@mui/utils/useSlotProps'; import { SlotComponentProps } from '@mui/utils'; - import { TreeItem, TreeItemProps } from '../../TreeItem'; -import { TreeItem2Props } from '../../TreeItem2'; import { TreeViewItemId } from '../../models'; import { TreeViewItemToRenderProps } from '../plugins/useTreeViewItems'; @@ -17,7 +15,7 @@ export interface RichTreeViewItemsSlots { * Custom component for the item. * @default TreeItem. */ - item?: React.JSXElementConstructor | React.JSXElementConstructor; + item?: React.JSXElementConstructor; } export interface RichTreeViewItemsSlotProps { diff --git a/packages/x-tree-view/src/internals/corePlugins/corePlugins.ts b/packages/x-tree-view/src/internals/corePlugins/corePlugins.ts index f6aa4c84a219..76f050c33c8f 100644 --- a/packages/x-tree-view/src/internals/corePlugins/corePlugins.ts +++ b/packages/x-tree-view/src/internals/corePlugins/corePlugins.ts @@ -5,7 +5,7 @@ import { ConvertPluginsIntoSignatures } from '../models'; /** * Internal plugins that create the tools used by the other plugins. - * These plugins are used by the tree view components. + * These plugins are used by the Tree View components. */ export const TREE_VIEW_CORE_PLUGINS = [ useTreeViewInstanceEvents, diff --git a/packages/x-tree-view/src/internals/corePlugins/useTreeViewId/useTreeViewId.utils.ts b/packages/x-tree-view/src/internals/corePlugins/useTreeViewId/useTreeViewId.utils.ts index 8a8e42ef69ec..f1cffe3debfd 100644 --- a/packages/x-tree-view/src/internals/corePlugins/useTreeViewId/useTreeViewId.utils.ts +++ b/packages/x-tree-view/src/internals/corePlugins/useTreeViewId/useTreeViewId.utils.ts @@ -7,7 +7,7 @@ export const createTreeViewDefaultId = () => { }; /** - * Generate the id attribute (i.e.: the `id` attribute passed to the DOM element) of a tree item. + * Generate the id attribute (i.e.: the `id` attribute passed to the DOM element) of a Tree Item. * If the user explicitly defined an id attribute, it will be returned. * Otherwise, the method creates a unique id for the item based on the Tree View id attribute and the item `itemId` * @param {object} params The parameters to determine the id attribute of the item. diff --git a/packages/x-tree-view/src/internals/index.ts b/packages/x-tree-view/src/internals/index.ts index 6fd119a8ffb9..fa24b5ede3fb 100644 --- a/packages/x-tree-view/src/internals/index.ts +++ b/packages/x-tree-view/src/internals/index.ts @@ -16,8 +16,6 @@ export type { TreeViewInstance, DefaultizedProps, TreeViewItemPlugin, - MuiCancellableEvent, - MuiCancellableEventHandler, } from './models'; // Core plugins diff --git a/packages/x-tree-view/src/internals/models/MuiCancellableEvent.ts b/packages/x-tree-view/src/internals/models/MuiCancellableEvent.ts deleted file mode 100644 index 78596ff9ba9e..000000000000 --- a/packages/x-tree-view/src/internals/models/MuiCancellableEvent.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type MuiCancellableEvent = { - defaultMuiPrevented?: boolean; -}; - -export type MuiCancellableEventHandler = (event: Event & MuiCancellableEvent) => void; diff --git a/packages/x-tree-view/src/internals/models/index.ts b/packages/x-tree-view/src/internals/models/index.ts index 7478186e4ea9..9095cd823af3 100644 --- a/packages/x-tree-view/src/internals/models/index.ts +++ b/packages/x-tree-view/src/internals/models/index.ts @@ -2,4 +2,3 @@ export * from './helpers'; export * from './plugin'; export * from './itemPlugin'; export * from './treeView'; -export * from './MuiCancellableEvent'; diff --git a/packages/x-tree-view/src/internals/models/itemPlugin.ts b/packages/x-tree-view/src/internals/models/itemPlugin.ts index c0858f84d960..64a54e5cc549 100644 --- a/packages/x-tree-view/src/internals/models/itemPlugin.ts +++ b/packages/x-tree-view/src/internals/models/itemPlugin.ts @@ -1,22 +1,22 @@ import * as React from 'react'; import { EventHandlers } from '@mui/utils'; +import type { TreeItemProps } from '../../TreeItem'; import type { - UseTreeItem2ContentSlotOwnProps, - UseTreeItem2DragAndDropOverlaySlotOwnProps, - UseTreeItem2LabelInputSlotOwnProps, - UseTreeItem2RootSlotOwnProps, -} from '../../useTreeItem2'; -import type { UseTreeItem2Interactions } from '../../hooks/useTreeItem2Utils/useTreeItem2Utils'; + UseTreeItemContentSlotOwnProps, + UseTreeItemDragAndDropOverlaySlotOwnProps, + UseTreeItemLabelInputSlotOwnProps, + UseTreeItemRootSlotOwnProps, + UseTreeItemCheckboxSlotOwnProps, + UseTreeItemStatus, +} from '../../useTreeItem'; +import type { UseTreeItemInteractions } from '../../hooks/useTreeItemUtils/useTreeItemUtils'; export interface TreeViewItemPluginSlotPropsEnhancerParams { rootRefObject: React.MutableRefObject; contentRefObject: React.MutableRefObject; externalEventHandlers: EventHandlers; - // TODO v9: Remove "Pick" once the old TreeItem is removed. - interactions: Pick< - UseTreeItem2Interactions, - 'handleSaveItemLabel' | 'handleCancelItemLabelEditing' - >; + interactions: UseTreeItemInteractions; + status: UseTreeItemStatus; } type TreeViewItemPluginSlotPropsEnhancer = ( @@ -24,10 +24,11 @@ type TreeViewItemPluginSlotPropsEnhancer = ( ) => Partial; export interface TreeViewItemPluginSlotPropsEnhancers { - root?: TreeViewItemPluginSlotPropsEnhancer; - content?: TreeViewItemPluginSlotPropsEnhancer; - dragAndDropOverlay?: TreeViewItemPluginSlotPropsEnhancer; - labelInput?: TreeViewItemPluginSlotPropsEnhancer; + root?: TreeViewItemPluginSlotPropsEnhancer; + content?: TreeViewItemPluginSlotPropsEnhancer; + dragAndDropOverlay?: TreeViewItemPluginSlotPropsEnhancer; + labelInput?: TreeViewItemPluginSlotPropsEnhancer; + checkbox?: TreeViewItemPluginSlotPropsEnhancer; } export interface TreeViewItemPluginResponse { @@ -54,6 +55,6 @@ export interface TreeViewItemPluginOptions props: TProps; } -export type TreeViewItemPlugin = ( - options: TreeViewItemPluginOptions, +export type TreeViewItemPlugin = ( + options: TreeViewItemPluginOptions, ) => void | TreeViewItemPluginResponse; diff --git a/packages/x-tree-view/src/internals/models/plugin.ts b/packages/x-tree-view/src/internals/models/plugin.ts index cb5e819e30c9..df0edd46a7fb 100644 --- a/packages/x-tree-view/src/internals/models/plugin.ts +++ b/packages/x-tree-view/src/internals/models/plugin.ts @@ -157,7 +157,7 @@ export type TreeViewPlugin = { getInitialState?: (params: TreeViewUsedDefaultizedParams) => TSignature['state']; models?: TreeViewModelsInitializer; params: Record; - itemPlugin?: TreeViewItemPlugin; + itemPlugin?: TreeViewItemPlugin; /** * Render function used to add React wrappers around the TreeItem. * @param {{ nodeId: TreeViewItemId; children: React.ReactNode; }} params The params of the item. diff --git a/packages/x-tree-view/src/internals/models/treeView.ts b/packages/x-tree-view/src/internals/models/treeView.ts index ad86ffeb39ac..df139e952924 100644 --- a/packages/x-tree-view/src/internals/models/treeView.ts +++ b/packages/x-tree-view/src/internals/models/treeView.ts @@ -9,11 +9,11 @@ export interface TreeViewItemMeta { expandable: boolean; disabled: boolean; /** - * Only defined for `RichTreeView` and `RichTreeViewPro`. + * Only defined for `` and ``. */ depth?: number; /** - * Only defined for `RichTreeView` and `RichTreeViewPro`. + * Only defined for `` and ``. */ label?: string; } diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx index 8019dcba99bb..b355540aa30c 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx @@ -4,390 +4,380 @@ import { spy } from 'sinon'; import { describeTreeView } from 'test/utils/tree-view/describeTreeView'; import { UseTreeViewExpansionSignature } from '@mui/x-tree-view/internals'; import { act, fireEvent } from '@mui/internal-test-utils'; -import { TreeItem2, TreeItem2Props } from '@mui/x-tree-view/TreeItem2'; -import { UseTreeItem2ContentSlotOwnProps } from '@mui/x-tree-view/useTreeItem2'; -import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; +import { TreeItem, TreeItemProps } from '@mui/x-tree-view/TreeItem'; +import { UseTreeItemContentSlotOwnProps } from '@mui/x-tree-view/useTreeItem'; +import { useTreeItemUtils } from '@mui/x-tree-view/hooks'; /** * All tests related to keyboard navigation (e.g.: expanding using "Enter" and "ArrowRight") * are located in the `useTreeViewKeyboardNavigation.test.tsx` file. */ -describeTreeView<[UseTreeViewExpansionSignature]>( - 'useTreeViewExpansion plugin', - ({ render, setup }) => { - describe('model props (expandedItems, defaultExpandedItems, onExpandedItemsChange)', () => { - it('should not expand items when no default state and no control state are defined', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - }); - - expect(view.isItemExpanded('1')).to.equal(false); - expect(view.getAllTreeItemIds()).to.deep.equal(['1', '2']); +describeTreeView<[UseTreeViewExpansionSignature]>('useTreeViewExpansion plugin', ({ render }) => { + describe('model props (expandedItems, defaultExpandedItems, onExpandedItemsChange)', () => { + it('should not expand items when no default state and no control state are defined', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], }); - it('should use the default state when defined', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - defaultExpandedItems: ['1'], - }); + expect(view.isItemExpanded('1')).to.equal(false); + expect(view.getAllTreeItemIds()).to.deep.equal(['1', '2']); + }); - expect(view.isItemExpanded('1')).to.equal(true); - expect(view.getAllTreeItemIds()).to.deep.equal(['1', '1.1', '2']); + it('should use the default state when defined', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + defaultExpandedItems: ['1'], }); - it('should use the controlled state when defined', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - expandedItems: ['1'], - }); + expect(view.isItemExpanded('1')).to.equal(true); + expect(view.getAllTreeItemIds()).to.deep.equal(['1', '1.1', '2']); + }); - expect(view.isItemExpanded('1')).to.equal(true); - expect(view.getItemRoot('1.1')).toBeVisible(); + it('should use the controlled state when defined', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + expandedItems: ['1'], }); - it('should use the controlled state instead of the default state when both are defined', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - expandedItems: ['1'], - defaultExpandedItems: ['2'], - }); + expect(view.isItemExpanded('1')).to.equal(true); + expect(view.getItemRoot('1.1')).toBeVisible(); + }); - expect(view.isItemExpanded('1')).to.equal(true); + it('should use the controlled state instead of the default state when both are defined', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + expandedItems: ['1'], + defaultExpandedItems: ['2'], }); - it('should react to controlled state update', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }], - expandedItems: [], - }); + expect(view.isItemExpanded('1')).to.equal(true); + }); - view.setProps({ expandedItems: ['1'] }); - expect(view.isItemExpanded('1')).to.equal(true); + it('should react to controlled state update', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + expandedItems: [], }); - it('should call the onExpandedItemsChange callback when the model is updated (add expanded item to empty list)', () => { - const onExpandedItemsChange = spy(); - - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }], - onExpandedItemsChange, - }); + view.setProps({ expandedItems: ['1'] }); + expect(view.isItemExpanded('1')).to.equal(true); + }); - fireEvent.click(view.getItemContent('1')); + it('should call the onExpandedItemsChange callback when the model is updated (add expanded item to empty list)', () => { + const onExpandedItemsChange = spy(); - expect(onExpandedItemsChange.callCount).to.equal(1); - expect(onExpandedItemsChange.lastCall.args[1]).to.deep.equal(['1']); + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + onExpandedItemsChange, }); - it('should call the onExpandedItemsChange callback when the model is updated (add expanded item to non-empty list)', () => { - const onExpandedItemsChange = spy(); + fireEvent.click(view.getItemContent('1')); - const view = render({ - items: [ - { id: '1', children: [{ id: '1.1' }] }, - { id: '2', children: [{ id: '2.1' }] }, - ], - onExpandedItemsChange, - defaultExpandedItems: ['1'], - }); + expect(onExpandedItemsChange.callCount).to.equal(1); + expect(onExpandedItemsChange.lastCall.args[1]).to.deep.equal(['1']); + }); - fireEvent.click(view.getItemContent('2')); + it('should call the onExpandedItemsChange callback when the model is updated (add expanded item to non-empty list)', () => { + const onExpandedItemsChange = spy(); - expect(onExpandedItemsChange.callCount).to.equal(1); - expect(onExpandedItemsChange.lastCall.args[1]).to.deep.equal(['2', '1']); + const view = render({ + items: [ + { id: '1', children: [{ id: '1.1' }] }, + { id: '2', children: [{ id: '2.1' }] }, + ], + onExpandedItemsChange, + defaultExpandedItems: ['1'], }); - it('should call the onExpandedItemsChange callback when the model is updated (remove expanded item)', () => { - const onExpandedItemsChange = spy(); + fireEvent.click(view.getItemContent('2')); - const view = render({ - items: [ - { id: '1', children: [{ id: '1.1' }] }, - { id: '2', children: [{ id: '2.1' }] }, - ], - onExpandedItemsChange, - defaultExpandedItems: ['1'], - }); + expect(onExpandedItemsChange.callCount).to.equal(1); + expect(onExpandedItemsChange.lastCall.args[1]).to.deep.equal(['2', '1']); + }); - fireEvent.click(view.getItemContent('1')); + it('should call the onExpandedItemsChange callback when the model is updated (remove expanded item)', () => { + const onExpandedItemsChange = spy(); - expect(onExpandedItemsChange.callCount).to.equal(1); - expect(onExpandedItemsChange.lastCall.args[1]).to.deep.equal([]); + const view = render({ + items: [ + { id: '1', children: [{ id: '1.1' }] }, + { id: '2', children: [{ id: '2.1' }] }, + ], + onExpandedItemsChange, + defaultExpandedItems: ['1'], }); - it('should warn when switching from controlled to uncontrolled', () => { - const view = render({ - items: [{ id: '1' }], - expandedItems: [], - }); + fireEvent.click(view.getItemContent('1')); - expect(() => { - view.setProps({ expandedItems: undefined }); - }).toErrorDev( - 'MUI X: A component is changing the controlled expandedItems state of TreeView to be uncontrolled.', - ); - }); - - it('should warn and not react to update when updating the default state', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - defaultExpandedItems: ['1'], - }); + expect(onExpandedItemsChange.callCount).to.equal(1); + expect(onExpandedItemsChange.lastCall.args[1]).to.deep.equal([]); + }); - expect(() => { - view.setProps({ defaultExpandedItems: ['2'] }); - expect(view.isItemExpanded('1')).to.equal(true); - expect(view.isItemExpanded('2')).to.equal(false); - }).toErrorDev( - 'MUI X: A component is changing the default expandedItems state of an uncontrolled TreeView after being initialized. To suppress this warning opt to use a controlled TreeView.', - ); + it('should warn when switching from controlled to uncontrolled', () => { + const view = render({ + items: [{ id: '1' }], + expandedItems: [], }); + + expect(() => { + view.setProps({ expandedItems: undefined }); + }).toErrorDev( + 'MUI X: A component is changing the controlled expandedItems state of TreeView to be uncontrolled.', + ); }); - describe('item click interaction', () => { - it('should expand collapsed item when clicking on an item content', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - }); + it('should warn and not react to update when updating the default state', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + defaultExpandedItems: ['1'], + }); - expect(view.isItemExpanded('1')).to.equal(false); - fireEvent.click(view.getItemContent('1')); + expect(() => { + view.setProps({ defaultExpandedItems: ['2'] }); expect(view.isItemExpanded('1')).to.equal(true); + expect(view.isItemExpanded('2')).to.equal(false); + }).toErrorDev( + 'MUI X: A component is changing the default expandedItems state of an uncontrolled TreeView after being initialized. To suppress this warning opt to use a controlled TreeView.', + ); + }); + }); + + describe('item click interaction', () => { + it('should expand collapsed item when clicking on an item content', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], }); - it('should collapse expanded item when clicking on an item content', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - defaultExpandedItems: ['1'], - }); + expect(view.isItemExpanded('1')).to.equal(false); + fireEvent.click(view.getItemContent('1')); + expect(view.isItemExpanded('1')).to.equal(true); + }); - expect(view.isItemExpanded('1')).to.equal(true); - fireEvent.click(view.getItemContent('1')); - expect(view.isItemExpanded('1')).to.equal(false); + it('should collapse expanded item when clicking on an item content', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + defaultExpandedItems: ['1'], }); - it('should not expand collapsed item when clicking on a disabled item content', () => { - const view = render({ - items: [{ id: '1', disabled: true, children: [{ id: '1.1' }] }, { id: '2' }], - }); + expect(view.isItemExpanded('1')).to.equal(true); + fireEvent.click(view.getItemContent('1')); + expect(view.isItemExpanded('1')).to.equal(false); + }); - expect(view.isItemExpanded('1')).to.equal(false); - fireEvent.click(view.getItemContent('1')); - expect(view.isItemExpanded('1')).to.equal(false); + it('should not expand collapsed item when clicking on a disabled item content', () => { + const view = render({ + items: [{ id: '1', disabled: true, children: [{ id: '1.1' }] }, { id: '2' }], }); - it('should not collapse expanded item when clicking on a disabled item', () => { - const view = render({ - items: [{ id: '1', disabled: true, children: [{ id: '1.1' }] }, { id: '2' }], - defaultExpandedItems: ['1'], - }); + expect(view.isItemExpanded('1')).to.equal(false); + fireEvent.click(view.getItemContent('1')); + expect(view.isItemExpanded('1')).to.equal(false); + }); - expect(view.isItemExpanded('1')).to.equal(true); - fireEvent.click(view.getItemContent('1')); - expect(view.isItemExpanded('1')).to.equal(true); + it('should not collapse expanded item when clicking on a disabled item', () => { + const view = render({ + items: [{ id: '1', disabled: true, children: [{ id: '1.1' }] }, { id: '2' }], + defaultExpandedItems: ['1'], }); - it('should expand collapsed item when clicking on an item label', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - }); + expect(view.isItemExpanded('1')).to.equal(true); + fireEvent.click(view.getItemContent('1')); + expect(view.isItemExpanded('1')).to.equal(true); + }); - expect(view.isItemExpanded('1')).to.equal(false); - fireEvent.click(view.getItemLabel('1')); - expect(view.isItemExpanded('1')).to.equal(true); + it('should expand collapsed item when clicking on an item label', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], }); - it('should expand collapsed item when clicking on an item icon container', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - }); + expect(view.isItemExpanded('1')).to.equal(false); + fireEvent.click(view.getItemLabel('1')); + expect(view.isItemExpanded('1')).to.equal(true); + }); - expect(view.isItemExpanded('1')).to.equal(false); - fireEvent.click(view.getItemIconContainer('1')); - expect(view.isItemExpanded('1')).to.equal(true); + it('should expand collapsed item when clicking on an item icon container', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], }); - it('should be able to limit the expansion to the icon', function test() { - // This test is not relevant for the TreeItem component. - // We could create the equivalent test for it, - // but it's not worth the effort given the complexity of the old behavior override. - if (!setup.includes('TreeItem2')) { - this.skip(); - } - - const CustomTreeItem = React.forwardRef(function MyTreeItem( - props: TreeItem2Props, - ref: React.Ref, - ) { - const { interactions } = useTreeItem2Utils({ - itemId: props.itemId, - children: props.children, - }); - - const handleContentClick: UseTreeItem2ContentSlotOwnProps['onClick'] = (event) => { - event.defaultMuiPrevented = true; - interactions.handleSelection(event); - }; - - const handleIconContainerClick = (event: React.MouseEvent) => { - interactions.handleExpansion(event); - }; - - return ( - - ); - }); + expect(view.isItemExpanded('1')).to.equal(false); + fireEvent.click(view.getItemIconContainer('1')); + expect(view.isItemExpanded('1')).to.equal(true); + }); - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - slots: { item: CustomTreeItem }, + it('should be able to limit the expansion to the icon', function test() { + const CustomTreeItem = React.forwardRef(function MyTreeItem( + props: TreeItemProps, + ref: React.Ref, + ) { + const { interactions } = useTreeItemUtils({ + itemId: props.itemId, + children: props.children, }); - expect(view.isItemExpanded('1')).to.equal(false); - fireEvent.click(view.getItemContent('1')); - expect(view.isItemExpanded('1')).to.equal(false); - fireEvent.click(view.getItemIconContainer('1')); - expect(view.isItemExpanded('1')).to.equal(true); + const handleContentClick: UseTreeItemContentSlotOwnProps['onClick'] = (event) => { + event.defaultMuiPrevented = true; + interactions.handleSelection(event); + }; + + const handleIconContainerClick = (event: React.MouseEvent) => { + interactions.handleExpansion(event); + }; + + return ( + + ); }); - }); - // The `aria-expanded` attribute is used by the `response.isItemExpanded` method. - // This `describe` only tests basics scenarios, more complex scenarios are tested in this file's other `describe`. - describe('aria-expanded item attribute', () => { - it('should have the attribute `aria-expanded=false` if collapsed', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }], - }); + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + slots: { item: CustomTreeItem }, + }); - expect(view.getItemRoot('1')).to.have.attribute('aria-expanded', 'false'); + expect(view.isItemExpanded('1')).to.equal(false); + fireEvent.click(view.getItemContent('1')); + expect(view.isItemExpanded('1')).to.equal(false); + fireEvent.click(view.getItemIconContainer('1')); + expect(view.isItemExpanded('1')).to.equal(true); + }); + }); + + // The `aria-expanded` attribute is used by the `response.isItemExpanded` method. + // This `describe` only tests basics scenarios, more complex scenarios are tested in this file's other `describe`. + describe('aria-expanded item attribute', () => { + it('should have the attribute `aria-expanded=false` if collapsed', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], }); - it('should have the attribute `aria-expanded=true` if expanded', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }], - defaultExpandedItems: ['1'], - }); + expect(view.getItemRoot('1')).to.have.attribute('aria-expanded', 'false'); + }); - expect(view.getItemRoot('1')).to.have.attribute('aria-expanded', 'true'); + it('should have the attribute `aria-expanded=true` if expanded', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + defaultExpandedItems: ['1'], }); - it('should not have the attribute `aria-expanded` if no children are present', () => { - const view = render({ - items: [{ id: '1' }], - }); + expect(view.getItemRoot('1')).to.have.attribute('aria-expanded', 'true'); + }); - expect(view.getItemRoot('1')).not.to.have.attribute('aria-expanded'); + it('should not have the attribute `aria-expanded` if no children are present', () => { + const view = render({ + items: [{ id: '1' }], }); + + expect(view.getItemRoot('1')).not.to.have.attribute('aria-expanded'); }); + }); - describe('onItemExpansionToggle prop', () => { - it('should call the onItemExpansionToggle callback when expanding an item', () => { - const onItemExpansionToggle = spy(); + describe('onItemExpansionToggle prop', () => { + it('should call the onItemExpansionToggle callback when expanding an item', () => { + const onItemExpansionToggle = spy(); - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }], - onItemExpansionToggle, - }); - - fireEvent.click(view.getItemContent('1')); - expect(onItemExpansionToggle.callCount).to.equal(1); - expect(onItemExpansionToggle.lastCall.args[1]).to.equal('1'); - expect(onItemExpansionToggle.lastCall.args[2]).to.equal(true); + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + onItemExpansionToggle, }); - it('should call the onItemExpansionToggle callback when collapsing an item', () => { - const onItemExpansionToggle = spy(); + fireEvent.click(view.getItemContent('1')); + expect(onItemExpansionToggle.callCount).to.equal(1); + expect(onItemExpansionToggle.lastCall.args[1]).to.equal('1'); + expect(onItemExpansionToggle.lastCall.args[2]).to.equal(true); + }); - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }], - defaultExpandedItems: ['1'], - onItemExpansionToggle, - }); + it('should call the onItemExpansionToggle callback when collapsing an item', () => { + const onItemExpansionToggle = spy(); - fireEvent.click(view.getItemContent('1')); - expect(onItemExpansionToggle.callCount).to.equal(1); - expect(onItemExpansionToggle.lastCall.args[1]).to.equal('1'); - expect(onItemExpansionToggle.lastCall.args[2]).to.equal(false); + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + defaultExpandedItems: ['1'], + onItemExpansionToggle, }); - }); - describe('setItemExpansion api method', () => { - it('should expand a collapsed item when calling the setItemExpansion method with `isExpanded=true`', () => { - const onItemExpansionToggle = spy(); + fireEvent.click(view.getItemContent('1')); + expect(onItemExpansionToggle.callCount).to.equal(1); + expect(onItemExpansionToggle.lastCall.args[1]).to.equal('1'); + expect(onItemExpansionToggle.lastCall.args[2]).to.equal(false); + }); + }); - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }], - onItemExpansionToggle, - }); + describe('setItemExpansion api method', () => { + it('should expand a collapsed item when calling the setItemExpansion method with `isExpanded=true`', () => { + const onItemExpansionToggle = spy(); - act(() => { - view.apiRef.current.setItemExpansion({} as any, '1', true); - }); + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + onItemExpansionToggle, + }); - expect(view.isItemExpanded('1')).to.equal(true); - expect(onItemExpansionToggle.callCount).to.equal(1); - expect(onItemExpansionToggle.lastCall.args[1]).to.equal('1'); - expect(onItemExpansionToggle.lastCall.args[2]).to.equal(true); + act(() => { + view.apiRef.current.setItemExpansion({} as any, '1', true); }); - it('should collapse an expanded item when calling the setItemExpansion method with `isExpanded=false`', () => { - const onItemExpansionToggle = spy(); + expect(view.isItemExpanded('1')).to.equal(true); + expect(onItemExpansionToggle.callCount).to.equal(1); + expect(onItemExpansionToggle.lastCall.args[1]).to.equal('1'); + expect(onItemExpansionToggle.lastCall.args[2]).to.equal(true); + }); - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }], - defaultExpandedItems: ['1'], - onItemExpansionToggle, - }); + it('should collapse an expanded item when calling the setItemExpansion method with `isExpanded=false`', () => { + const onItemExpansionToggle = spy(); - act(() => { - view.apiRef.current.setItemExpansion({} as any, '1', false); - }); + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + defaultExpandedItems: ['1'], + onItemExpansionToggle, + }); - expect(view.isItemExpanded('1')).to.equal(false); - expect(onItemExpansionToggle.callCount).to.equal(1); - expect(onItemExpansionToggle.lastCall.args[1]).to.equal('1'); - expect(onItemExpansionToggle.lastCall.args[2]).to.equal(false); + act(() => { + view.apiRef.current.setItemExpansion({} as any, '1', false); }); - it('should do nothing when calling the setItemExpansion method with `isExpanded=true` on an already expanded item', () => { - const onItemExpansionToggle = spy(); + expect(view.isItemExpanded('1')).to.equal(false); + expect(onItemExpansionToggle.callCount).to.equal(1); + expect(onItemExpansionToggle.lastCall.args[1]).to.equal('1'); + expect(onItemExpansionToggle.lastCall.args[2]).to.equal(false); + }); - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }], - defaultExpandedItems: ['1'], - onItemExpansionToggle, - }); + it('should do nothing when calling the setItemExpansion method with `isExpanded=true` on an already expanded item', () => { + const onItemExpansionToggle = spy(); - act(() => { - view.apiRef.current.setItemExpansion({} as any, '1', true); - }); + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + defaultExpandedItems: ['1'], + onItemExpansionToggle, + }); - expect(view.isItemExpanded('1')).to.equal(true); - expect(onItemExpansionToggle.callCount).to.equal(0); + act(() => { + view.apiRef.current.setItemExpansion({} as any, '1', true); }); - it('should do nothing when calling the setItemExpansion method with `isExpanded=false` on an already collapsed item', () => { - const onItemExpansionToggle = spy(); + expect(view.isItemExpanded('1')).to.equal(true); + expect(onItemExpansionToggle.callCount).to.equal(0); + }); - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }], - onItemExpansionToggle, - }); + it('should do nothing when calling the setItemExpansion method with `isExpanded=false` on an already collapsed item', () => { + const onItemExpansionToggle = spy(); - act(() => { - view.apiRef.current.setItemExpansion({} as any, '1', false); - }); + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + onItemExpansionToggle, + }); - expect(view.isItemExpanded('1')).to.equal(false); - expect(onItemExpansionToggle.callCount).to.equal(0); + act(() => { + view.apiRef.current.setItemExpansion({} as any, '1', false); }); + + expect(view.isItemExpanded('1')).to.equal(false); + expect(onItemExpansionToggle.callCount).to.equal(0); }); - }, -); + }); +}); diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.ts index 88460ccf2fee..504676c31d77 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.types.ts @@ -57,13 +57,13 @@ export interface UseTreeViewExpansionParameters { */ defaultExpandedItems?: string[]; /** - * Callback fired when tree items are expanded/collapsed. + * Callback fired when Tree Items are expanded/collapsed. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemIds The ids of the expanded items. */ onExpandedItemsChange?: (event: React.SyntheticEvent, itemIds: string[]) => void; /** - * Callback fired when a tree item is expanded or collapsed. + * Callback fired when a Tree Item is expanded or collapsed. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemId The itemId of the modified item. * @param {array} isExpanded `true` if the item has just been expanded, `false` if it has just been collapsed. diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts index 96fe7f1b34a3..5e86c40374bd 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.ts @@ -7,7 +7,7 @@ import { UseTreeViewFocusSignature } from './useTreeViewFocus.types'; import { useInstanceEventHandler } from '../../hooks/useInstanceEventHandler'; import { getActiveElement } from '../../utils/utils'; import { getFirstNavigableItem } from '../../utils/tree'; -import { MuiCancellableEvent } from '../../models/MuiCancellableEvent'; +import { TreeViewCancellableEvent } from '../../../models'; import { convertSelectedItemsToArray } from '../useTreeViewSelection/useTreeViewSelection.utils'; const useDefaultFocusableItemId = ( @@ -110,7 +110,7 @@ export const useTreeViewFocus: TreeViewPlugin = ({ const createRootHandleFocus = (otherHandlers: EventHandlers) => - (event: React.FocusEvent & MuiCancellableEvent) => { + (event: React.FocusEvent & TreeViewCancellableEvent) => { otherHandlers.onFocus?.(event); if (event.defaultMuiPrevented) { return; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.ts index 9fb3d1038316..c84b650be8d5 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewFocus/useTreeViewFocus.types.ts @@ -40,7 +40,7 @@ export interface UseTreeViewFocusInstance extends UseTreeViewFocusPublicAPI { export interface UseTreeViewFocusParameters { /** - * Callback fired when a given tree item is focused. + * Callback fired when a given Tree Item is focused. * @param {React.SyntheticEvent | null} event The DOM event that triggered the change. **Warning**: This is a generic event not a focus event. * @param {string} itemId The id of the focused item. */ diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewIcons/useTreeViewIcons.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewIcons/useTreeViewIcons.types.ts index a303f54a8410..19b8b5e96ccd 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewIcons/useTreeViewIcons.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewIcons/useTreeViewIcons.types.ts @@ -19,7 +19,7 @@ interface UseTreeViewIconsSlots { expandIcon?: React.ElementType; /** * The default icon displayed next to an end item. - * This is applied to all tree items and can be overridden by the TreeItem `icon` slot prop. + * This is applied to all Tree Items and can be overridden by the TreeItem `icon` slot prop. */ endIcon?: React.ElementType; } diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.types.ts index 93761ead527e..864fc7c3c5ba 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.types.ts @@ -12,7 +12,7 @@ export interface TreeViewItemToRenderProps { export interface UseTreeViewItemsPublicAPI { /** * Get the item with the given id. - * When used in the `SimpleTreeView`, it returns an object with the `id` and `label` properties. + * When used in the Simple Tree View, it returns an object with the `id` and `label` properties. * @param {string} itemId The id of the item to retrieve. * @returns {R} The item with the given id. */ @@ -119,7 +119,7 @@ export interface UseTreeViewItemsParameters { */ getItemId?: (item: R) => TreeViewItemId; /** - * Callback fired when the `content` slot of a given tree item is clicked. + * Callback fired when the `content` slot of a given Tree Item is clicked. * @param {React.MouseEvent} event The DOM event that triggered the change. * @param {string} itemId The id of the focused item. */ diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.tsx index cdfd84fa5aca..764f22b72802 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewJSXItems/useTreeViewJSXItems.tsx @@ -14,8 +14,6 @@ import { buildSiblingIndexes, TREE_VIEW_ROOT_PARENT_ID, } from '../useTreeViewItems/useTreeViewItems.utils'; -import type { TreeItemProps } from '../../../TreeItem'; -import type { TreeItem2Props } from '../../../TreeItem2'; import { TreeViewItemDepthContext } from '../../TreeViewItemDepthContext'; import { generateTreeItemIdAttribute } from '../../corePlugins/useTreeViewId/useTreeViewId.utils'; @@ -42,7 +40,7 @@ export const useTreeViewJSXItems: TreeViewPlugin = items: { ...prevState.items, itemMetaMap: { ...prevState.items.itemMetaMap, [item.id]: item }, - // For `SimpleTreeView`, we don't have a proper `item` object, so we create a very basic one. + // For Simple Tree View, we don't have a proper `item` object, so we create a very basic one. itemMap: { ...prevState.items.itemMap, [item.id]: { id: item.id, label: item.label } }, }, }; @@ -117,11 +115,7 @@ const isItemExpandable = (reactChildren: React.ReactNode) => { return Boolean(reactChildren); }; -const useTreeViewJSXItemsItemPlugin: TreeViewItemPlugin = ({ - props, - rootRef, - contentRef, -}) => { +const useTreeViewJSXItemsItemPlugin: TreeViewItemPlugin = ({ props, rootRef, contentRef }) => { const { instance, treeId } = useTreeViewContext<[UseTreeViewJSXItemsSignature]>(); const { children, disabled = false, label, itemId, id } = props; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts index 5b2739b6f988..e5f587f75571 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.ts @@ -1,7 +1,8 @@ import * as React from 'react'; import { useRtl } from '@mui/system/RtlProvider'; import useEventCallback from '@mui/utils/useEventCallback'; -import { TreeViewItemMeta, TreeViewPlugin, MuiCancellableEvent } from '../../models'; +import { TreeViewCancellableEvent } from '../../../models'; +import { TreeViewItemMeta, TreeViewPlugin } from '../../models'; import { getFirstNavigableItem, getLastNavigableItem, @@ -85,7 +86,7 @@ export const useTreeViewKeyboardNavigation: TreeViewPlugin< // ARIA specification: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/#keyboardinteraction const handleItemKeyDown = ( - event: React.KeyboardEvent & MuiCancellableEvent, + event: React.KeyboardEvent & TreeViewCancellableEvent, itemId: string, ) => { if (event.defaultMuiPrevented) { diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.ts index 921e874c9e80..deaec508ac61 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.types.ts @@ -1,10 +1,10 @@ import * as React from 'react'; -import { TreeViewPluginSignature, MuiCancellableEvent } from '../../models'; +import { TreeViewPluginSignature } from '../../models'; import { UseTreeViewItemsSignature } from '../useTreeViewItems'; import { UseTreeViewSelectionSignature } from '../useTreeViewSelection'; import { UseTreeViewFocusSignature } from '../useTreeViewFocus'; import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion'; -import { TreeViewItemId } from '../../../models'; +import { TreeViewItemId, TreeViewCancellableEvent } from '../../../models'; import { UseTreeViewLabelSignature } from '../useTreeViewLabel'; export interface UseTreeViewKeyboardNavigationInstance { @@ -18,11 +18,11 @@ export interface UseTreeViewKeyboardNavigationInstance { /** * Callback fired when a key is pressed on an item. * Handles all the keyboard navigation logic. - * @param {React.KeyboardEvent & MuiCancellableEvent} event The keyboard event that triggered the callback. + * @param {React.KeyboardEvent & TreeViewCancellableEvent} event The keyboard event that triggered the callback. * @param {TreeViewItemId} itemId The id of the item that the event was triggered on. */ handleItemKeyDown: ( - event: React.KeyboardEvent & MuiCancellableEvent, + event: React.KeyboardEvent & TreeViewCancellableEvent, itemId: TreeViewItemId, ) => void; } diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.ts index 3a663be337b8..7636ad9b39dd 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.itemPlugin.ts @@ -1,23 +1,24 @@ import * as React from 'react'; import { useTreeViewContext } from '../../TreeViewProvider'; -import { MuiCancellableEvent, TreeViewItemPlugin } from '../../models'; +import { TreeViewCancellableEvent } from '../../../models'; +import { TreeViewItemPlugin } from '../../models'; import { UseTreeViewItemsSignature } from '../useTreeViewItems'; import { - UseTreeItem2LabelInputSlotPropsFromLabelEditing, + UseTreeItemLabelInputSlotPropsFromLabelEditing, UseTreeViewLabelSignature, } from './useTreeViewLabel.types'; -export const useTreeViewLabelItemPlugin: TreeViewItemPlugin = ({ props }) => { +export const useTreeViewLabelItemPlugin: TreeViewItemPlugin = ({ props }) => { const { instance } = useTreeViewContext<[UseTreeViewItemsSignature, UseTreeViewLabelSignature]>(); const { label, itemId } = props; - const [labelInputValue, setLabelInputValue] = React.useState(label); + const [labelInputValue, setLabelInputValue] = React.useState(label as string); const isItemBeingEdited = instance.isItemBeingEdited(itemId); React.useEffect(() => { if (!isItemBeingEdited) { - setLabelInputValue(label); + setLabelInputValue(label as string); } }, [isItemBeingEdited, label]); @@ -26,7 +27,7 @@ export const useTreeViewLabelItemPlugin: TreeViewItemPlugin = ({ props }) = labelInput: ({ externalEventHandlers, interactions, - }): UseTreeItem2LabelInputSlotPropsFromLabelEditing => { + }): UseTreeItemLabelInputSlotPropsFromLabelEditing => { const editable = instance.isItemEditable(itemId); if (!editable) { @@ -34,7 +35,7 @@ export const useTreeViewLabelItemPlugin: TreeViewItemPlugin = ({ props }) = } const handleKeydown = ( - event: React.KeyboardEvent & MuiCancellableEvent, + event: React.KeyboardEvent & TreeViewCancellableEvent, ) => { externalEventHandlers.onKeyDown?.(event); if (event.defaultMuiPrevented) { @@ -49,7 +50,9 @@ export const useTreeViewLabelItemPlugin: TreeViewItemPlugin = ({ props }) = } }; - const handleBlur = (event: React.FocusEvent & MuiCancellableEvent) => { + const handleBlur = ( + event: React.FocusEvent & TreeViewCancellableEvent, + ) => { externalEventHandlers.onBlur?.(event); if (event.defaultMuiPrevented) { return; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.ts index ef43558d8394..460b4667674c 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.ts @@ -91,7 +91,7 @@ useTreeViewLabel.getDefaultizedParams = ({ params, experimentalFeatures }) => { if (params.isItemEditable && !canUseFeature) { warnOnce([ 'MUI X: The label editing feature requires the `labelEditing` experimental feature to be enabled.', - 'You can do it by passing `experimentalFeatures={{ labelEditing: true}}` to the `RichTreeViewPro` component.', + 'You can do it by passing `experimentalFeatures={{ labelEditing: true}}` to the Rich Tree View Pro component.', 'Check the documentation for more details: https://mui.com/x/react-tree-view/rich-tree-view/editing/', ]); } diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.types.ts index 3f23f27ca86d..9ee8c57e6995 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.types.ts @@ -1,7 +1,7 @@ import { DefaultizedProps, TreeViewPluginSignature } from '../../models'; import { TreeViewItemId } from '../../../models'; import { UseTreeViewItemsSignature } from '../useTreeViewItems'; -import { TreeItem2LabelInputProps } from '../../../TreeItem2LabelInput'; +import { TreeItemLabelInputProps } from '../../../TreeItemLabelInput'; export interface UseTreeViewLabelPublicAPI { /** @@ -39,7 +39,7 @@ export interface UseTreeViewLabelInstance extends UseTreeViewLabelPublicAPI { */ isItemEditable: (itemId: TreeViewItemId) => boolean; /** - * Set to `true` if the tree view is editable. + * Set to `true` if the Tree View is editable. */ isTreeViewEditable: boolean; } @@ -82,9 +82,9 @@ export type UseTreeViewLabelSignature = TreeViewPluginSignature<{ dependencies: [UseTreeViewItemsSignature]; }>; -export interface UseTreeItem2LabelInputSlotPropsFromLabelEditing extends TreeItem2LabelInputProps {} +export interface UseTreeItemLabelInputSlotPropsFromLabelEditing extends TreeItemLabelInputProps {} -declare module '@mui/x-tree-view/useTreeItem2' { - interface UseTreeItem2LabelInputSlotOwnProps - extends UseTreeItem2LabelInputSlotPropsFromLabelEditing {} +declare module '@mui/x-tree-view/useTreeItem' { + interface UseTreeItemLabelInputSlotOwnProps + extends UseTreeItemLabelInputSlotPropsFromLabelEditing {} } diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.itemPlugin.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.itemPlugin.ts new file mode 100644 index 000000000000..c2f7d4978cef --- /dev/null +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.itemPlugin.ts @@ -0,0 +1,111 @@ +import * as React from 'react'; +import { + TreeViewItemId, + TreeViewSelectionPropagation, + TreeViewCancellableEvent, +} from '../../../models'; +import { useTreeViewContext } from '../../TreeViewProvider'; +import { TreeViewInstance, TreeViewItemPlugin } from '../../models'; +import { + UseTreeItemCheckboxSlotPropsFromSelection, + UseTreeViewSelectionSignature, +} from './useTreeViewSelection.types'; +import { UseTreeViewItemsSignature } from '../useTreeViewItems'; + +function getCheckboxStatus({ + itemId, + instance, + selectionPropagation, + selected, +}: { + itemId: TreeViewItemId; + instance: TreeViewInstance<[UseTreeViewItemsSignature, UseTreeViewSelectionSignature]>; + selectionPropagation: TreeViewSelectionPropagation; + selected: boolean; +}) { + if (selected) { + return { + indeterminate: false, + checked: true, + }; + } + + const children = instance.getItemOrderedChildrenIds(itemId); + if (children.length === 0) { + return { + indeterminate: false, + checked: false, + }; + } + + let hasSelectedDescendant = false; + let hasUnSelectedDescendant = false; + + const traverseDescendants = (itemToTraverseId: TreeViewItemId) => { + if (itemToTraverseId !== itemId) { + if (instance.isItemSelected(itemToTraverseId)) { + hasSelectedDescendant = true; + } else { + hasUnSelectedDescendant = true; + } + } + + instance.getItemOrderedChildrenIds(itemToTraverseId).forEach(traverseDescendants); + }; + + traverseDescendants(itemId); + + return { + indeterminate: + (hasSelectedDescendant && hasUnSelectedDescendant) || (!hasUnSelectedDescendant && !selected), + checked: selectionPropagation.parents ? hasSelectedDescendant : selected, + }; +} + +export const useTreeViewSelectionItemPlugin: TreeViewItemPlugin = ({ props }) => { + const { itemId } = props; + + const { + instance, + selection: { disableSelection, checkboxSelection, selectionPropagation }, + } = useTreeViewContext<[UseTreeViewItemsSignature, UseTreeViewSelectionSignature]>(); + return { + propsEnhancers: { + checkbox: ({ + externalEventHandlers, + interactions, + status, + }): UseTreeItemCheckboxSlotPropsFromSelection => { + const handleChange = ( + event: React.ChangeEvent & TreeViewCancellableEvent, + ) => { + externalEventHandlers.onChange?.(event); + if (event.defaultMuiPrevented) { + return; + } + + if (disableSelection || status.disabled) { + return; + } + + interactions.handleCheckboxSelection(event); + }; + + const checkboxStatus = getCheckboxStatus({ + instance, + itemId, + selectionPropagation, + selected: status.selected, + }); + + return { + visible: checkboxSelection, + disabled: disableSelection || status.disabled, + tabIndex: -1, + onChange: handleChange, + ...checkboxStatus, + }; + }, + }, + }; +}; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.test.tsx index 448178c94601..c9cc2f20d28c 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.test.tsx @@ -689,6 +689,128 @@ describeTreeView<[UseTreeViewSelectionSignature, UseTreeViewExpansionSignature]> fireEvent.click(view.getItemCheckboxInput('3'), { shiftKey: true }); expect(view.getSelectedTreeItems()).to.deep.equal(['1', '3']); }); + + it('should not select the parent when selecting all the children', () => { + const view = render({ + multiSelect: true, + checkboxSelection: true, + items: [{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }, { id: '2' }], + defaultSelectedItems: ['1.2'], + defaultExpandedItems: ['1'], + }); + + fireEvent.click(view.getItemCheckboxInput('1.1')); + expect(view.getSelectedTreeItems()).to.deep.equal(['1.1', '1.2']); + }); + + it('should set the parent checkbox as indeterminate when some children are selected but the parent is not', () => { + const view = render({ + multiSelect: true, + checkboxSelection: true, + items: [{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }, { id: '2' }], + defaultSelectedItems: ['1.1'], + defaultExpandedItems: ['1'], + }); + + expect(view.getItemCheckboxInput('1').dataset.indeterminate).to.equal('true'); + }); + + it('should not set the parent checkbox as indeterminate when no child is selected and the parent is not either', () => { + const view = render({ + multiSelect: true, + checkboxSelection: true, + items: [{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }, { id: '2' }], + defaultExpandedItems: ['1'], + }); + + expect(view.getItemCheckboxInput('1').dataset.indeterminate).to.equal('false'); + }); + }); + + describe('multi selection with selectionPropagation.descendants = true', () => { + it('should select all the children when selecting a parent', () => { + const view = render({ + multiSelect: true, + checkboxSelection: true, + items: [{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }], + defaultExpandedItems: ['1'], + selectionPropagation: { descendants: true }, + }); + + fireEvent.click(view.getItemCheckboxInput('1')); + expect(view.getSelectedTreeItems()).to.deep.equal(['1', '1.1', '1.2']); + }); + + it('should deselect all the children when deselecting a parent', () => { + const view = render({ + multiSelect: true, + checkboxSelection: true, + items: [{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }], + defaultSelectedItems: ['1', '1.1', '1.2'], + defaultExpandedItems: ['1'], + selectionPropagation: { descendants: true }, + }); + + fireEvent.click(view.getItemCheckboxInput('1')); + expect(view.getSelectedTreeItems()).to.deep.equal([]); + }); + + it('should not select the parent when selecting all the children', () => { + const view = render({ + multiSelect: true, + checkboxSelection: true, + items: [{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }], + defaultSelectedItems: ['1.2'], + defaultExpandedItems: ['1'], + selectionPropagation: { descendants: true }, + }); + + fireEvent.click(view.getItemCheckboxInput('1.1')); + expect(view.getSelectedTreeItems()).to.deep.equal(['1.1', '1.2']); + }); + + it('should not unselect the parent when unselecting a children', () => { + const view = render({ + multiSelect: true, + checkboxSelection: true, + items: [{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }], + defaultSelectedItems: ['1', '1.1', '1.2'], + defaultExpandedItems: ['1'], + selectionPropagation: { descendants: true }, + }); + + fireEvent.click(view.getItemCheckboxInput('1.1')); + expect(view.getSelectedTreeItems()).to.deep.equal(['1', '1.2']); + }); + }); + + describe('multi selection with selectionPropagation.parents = true', () => { + it('should select all the parents when selecting a child', () => { + const view = render({ + multiSelect: true, + checkboxSelection: true, + items: [{ id: '1', children: [{ id: '1.1', children: [{ id: '1.1.1' }] }] }], + defaultExpandedItems: ['1', '1.1'], + selectionPropagation: { parents: true }, + }); + + fireEvent.click(view.getItemCheckboxInput('1.1.1')); + expect(view.getSelectedTreeItems()).to.deep.equal(['1', '1.1', '1.1.1']); + }); + + it('should deselect all the parents when deselecting a child ', () => { + const view = render({ + multiSelect: true, + checkboxSelection: true, + items: [{ id: '1', children: [{ id: '1.1', children: [{ id: '1.1.1' }] }] }], + defaultSelectedItems: ['1', '1.1', '1.1.1'], + defaultExpandedItems: ['1', '1.1'], + selectionPropagation: { parents: true }, + }); + + fireEvent.click(view.getItemCheckboxInput('1.1.1')); + expect(view.getSelectedTreeItems()).to.deep.equal([]); + }); }); }); diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts index c24fad0b6bbe..e2225d707777 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.ts @@ -12,7 +12,13 @@ import { UseTreeViewSelectionInstance, UseTreeViewSelectionSignature, } from './useTreeViewSelection.types'; -import { convertSelectedItemsToArray, getLookupFromArray } from './useTreeViewSelection.utils'; +import { + convertSelectedItemsToArray, + propagateSelection, + getAddedAndRemovedItems, + getLookupFromArray, +} from './useTreeViewSelection.utils'; +import { useTreeViewSelectionItemPlugin } from './useTreeViewSelection.itemPlugin'; export const useTreeViewSelection: TreeViewPlugin = ({ instance, @@ -37,39 +43,58 @@ export const useTreeViewSelection: TreeViewPlugin const setSelectedItems = ( event: React.SyntheticEvent, - newSelectedItems: typeof params.defaultSelectedItems, + newModel: typeof params.defaultSelectedItems, + additionalItemsToPropagate?: TreeViewItemId[], ) => { + let cleanModel: typeof newModel; + + if ( + params.multiSelect && + (params.selectionPropagation.descendants || params.selectionPropagation.parents) + ) { + cleanModel = propagateSelection({ + instance, + selectionPropagation: params.selectionPropagation, + newModel: newModel as string[], + oldModel: models.selectedItems.value as string[], + additionalItemsToPropagate, + }); + } else { + cleanModel = newModel; + } + if (params.onItemSelectionToggle) { if (params.multiSelect) { - const addedItems = (newSelectedItems as string[]).filter( - (itemId) => !instance.isItemSelected(itemId), - ); - const removedItems = (models.selectedItems.value as string[]).filter( - (itemId) => !(newSelectedItems as string[]).includes(itemId), - ); - - addedItems.forEach((itemId) => { - params.onItemSelectionToggle!(event, itemId, true); + const changes = getAddedAndRemovedItems({ + instance, + newModel: cleanModel as string[], + oldModel: models.selectedItems.value as string[], }); - removedItems.forEach((itemId) => { - params.onItemSelectionToggle!(event, itemId, false); - }); - } else if (newSelectedItems !== models.selectedItems.value) { + if (params.onItemSelectionToggle) { + changes.added.forEach((itemId) => { + params.onItemSelectionToggle!(event, itemId, true); + }); + + changes.removed.forEach((itemId) => { + params.onItemSelectionToggle!(event, itemId, false); + }); + } + } else if (params.onItemSelectionToggle && cleanModel !== models.selectedItems.value) { if (models.selectedItems.value != null) { params.onItemSelectionToggle(event, models.selectedItems.value as string, false); } - if (newSelectedItems != null) { - params.onItemSelectionToggle(event, newSelectedItems as string, true); + if (cleanModel != null) { + params.onItemSelectionToggle(event, cleanModel as string, true); } } } if (params.onSelectedItemsChange) { - params.onSelectedItemsChange(event, newSelectedItems); + params.onSelectedItemsChange(event, cleanModel); } - models.selectedItems.setControlledValue(newSelectedItems); + models.selectedItems.setControlledValue(cleanModel); }; const isItemSelected = (itemId: string) => selectedItemsMap.has(itemId); @@ -107,7 +132,13 @@ export const useTreeViewSelection: TreeViewPlugin } } - setSelectedItems(event, newSelected); + setSelectedItems( + event, + newSelected, + // If shouldBeSelected === instance.isItemSelect(itemId), we still want to propagate the select. + // This is useful when the element is in an indeterminate state. + [itemId], + ); lastSelectedItem.current = itemId; lastSelectedRange.current = {}; }; @@ -213,11 +244,14 @@ export const useTreeViewSelection: TreeViewPlugin multiSelect: params.multiSelect, checkboxSelection: params.checkboxSelection, disableSelection: params.disableSelection, + selectionPropagation: params.selectionPropagation, }, }, }; }; +useTreeViewSelection.itemPlugin = useTreeViewSelectionItemPlugin; + useTreeViewSelection.models = { selectedItems: { getDefaultValue: (params) => params.defaultSelectedItems, @@ -233,6 +267,7 @@ useTreeViewSelection.getDefaultizedParams = ({ params }) => ({ checkboxSelection: params.checkboxSelection ?? false, defaultSelectedItems: params.defaultSelectedItems ?? (params.multiSelect ? DEFAULT_SELECTED_ITEMS : null), + selectionPropagation: params.selectionPropagation ?? {}, }); useTreeViewSelection.params = { @@ -243,4 +278,5 @@ useTreeViewSelection.params = { selectedItems: true, onSelectedItemsChange: true, onItemSelectionToggle: true, + selectionPropagation: true, }; diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.ts index aa115aed1549..9a3f0f7557d4 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.types.ts @@ -2,6 +2,7 @@ import * as React from 'react'; import type { DefaultizedProps, TreeViewPluginSignature } from '../../models'; import { UseTreeViewItemsSignature } from '../useTreeViewItems'; import { UseTreeViewExpansionSignature } from '../useTreeViewExpansion'; +import { TreeViewSelectionPropagation, TreeViewCancellableEventHandler } from '../../../models'; export interface UseTreeViewSelectionPublicAPI { /** @@ -90,12 +91,29 @@ export interface UseTreeViewSelectionParameters, only the expanded items are considered (since the collapsed item are not passed to the Tree View component at all) + * + * @default { parents: false, descendants: false } + */ + selectionPropagation?: TreeViewSelectionPropagation; + /** + * Callback fired when Tree Items are selected/deselected. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {string[] | string} itemIds The ids of the selected items. * When `multiSelect` is `true`, this is an array of strings; when false (default) a string. @@ -105,7 +123,7 @@ export interface UseTreeViewSelectionParameters, ) => void; /** - * Callback fired when a tree item is selected or deselected. + * Callback fired when a Tree Item is selected or deselected. * @param {React.SyntheticEvent} event The DOM event that triggered the change. * @param {array} itemId The itemId of the modified item. * @param {array} isSelected `true` if the item has just been selected, `false` if it has just been deselected. @@ -119,13 +137,17 @@ export interface UseTreeViewSelectionParameters = DefaultizedProps< UseTreeViewSelectionParameters, - 'disableSelection' | 'defaultSelectedItems' | 'multiSelect' | 'checkboxSelection' + | 'disableSelection' + | 'defaultSelectedItems' + | 'multiSelect' + | 'checkboxSelection' + | 'selectionPropagation' >; interface UseTreeViewSelectionContextValue { selection: Pick< UseTreeViewSelectionDefaultizedParameters, - 'multiSelect' | 'checkboxSelection' | 'disableSelection' + 'multiSelect' | 'checkboxSelection' | 'disableSelection' | 'selectionPropagation' >; } @@ -142,3 +164,15 @@ export type UseTreeViewSelectionSignature = TreeViewPluginSignature<{ UseTreeViewItemsSignature, ]; }>; + +export interface UseTreeItemCheckboxSlotPropsFromSelection { + visible?: boolean; + checked?: boolean; + disabled?: boolean; + tabIndex?: -1; + onChange?: TreeViewCancellableEventHandler>; +} + +declare module '@mui/x-tree-view/useTreeItem' { + interface UseTreeItemCheckboxSlotOwnProps extends UseTreeItemCheckboxSlotPropsFromSelection {} +} diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.ts b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.ts index bb022e13338c..29913562a4b6 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.ts +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.utils.ts @@ -1,3 +1,8 @@ +import { TreeViewItemId, TreeViewSelectionPropagation } from '../../../models'; +import { TreeViewInstance } from '../../models'; +import { UseTreeViewItemsSignature } from '../useTreeViewItems'; +import { UseTreeViewSelectionSignature } from './useTreeViewSelection.types'; + /** * Transform the `selectedItems` model to be an array if it was a string or null. * @param {string[] | string | null} model The raw model. @@ -16,9 +21,133 @@ export const convertSelectedItemsToArray = (model: string[] | string | null): st }; export const getLookupFromArray = (array: string[]) => { - const lookup: { [itemId: string]: boolean } = {}; + const lookup: { [itemId: string]: true } = {}; array.forEach((itemId) => { lookup[itemId] = true; }); return lookup; }; + +export const getAddedAndRemovedItems = ({ + instance, + oldModel, + newModel, +}: { + instance: TreeViewInstance<[UseTreeViewSelectionSignature]>; + oldModel: TreeViewItemId[]; + newModel: TreeViewItemId[]; +}) => { + const newModelLookup = getLookupFromArray(newModel); + + return { + added: newModel.filter((itemId) => !instance.isItemSelected(itemId)), + removed: oldModel.filter((itemId) => !newModelLookup[itemId]), + }; +}; + +export const propagateSelection = ({ + instance, + selectionPropagation, + newModel, + oldModel, + additionalItemsToPropagate, +}: { + instance: TreeViewInstance<[UseTreeViewItemsSignature, UseTreeViewSelectionSignature]>; + selectionPropagation: TreeViewSelectionPropagation; + newModel: TreeViewItemId[]; + oldModel: TreeViewItemId[]; + additionalItemsToPropagate?: TreeViewItemId[]; +}): string[] => { + if (!selectionPropagation.descendants && !selectionPropagation.parents) { + return newModel; + } + + let shouldRegenerateModel = false; + const newModelLookup = getLookupFromArray(newModel); + + const changes = getAddedAndRemovedItems({ + instance, + newModel, + oldModel, + }); + + additionalItemsToPropagate?.forEach((itemId) => { + if (newModelLookup[itemId]) { + if (!changes.added.includes(itemId)) { + changes.added.push(itemId); + } + } else if (!changes.removed.includes(itemId)) { + changes.removed.push(itemId); + } + }); + + changes.added.forEach((addedItemId) => { + if (selectionPropagation.descendants) { + const selectDescendants = (itemId: TreeViewItemId) => { + if (itemId !== addedItemId) { + shouldRegenerateModel = true; + newModelLookup[itemId] = true; + } + + instance.getItemOrderedChildrenIds(itemId).forEach(selectDescendants); + }; + + selectDescendants(addedItemId); + } + + if (selectionPropagation.parents) { + const checkAllDescendantsSelected = (itemId: TreeViewItemId): boolean => { + if (!newModelLookup[itemId]) { + return false; + } + + const children = instance.getItemOrderedChildrenIds(itemId); + return children.every(checkAllDescendantsSelected); + }; + + const selectParents = (itemId: TreeViewItemId) => { + const parentId = instance.getItemMeta(itemId).parentId; + if (parentId == null) { + return; + } + + const siblings = instance.getItemOrderedChildrenIds(parentId); + if (siblings.every(checkAllDescendantsSelected)) { + shouldRegenerateModel = true; + newModelLookup[parentId] = true; + selectParents(parentId); + } + }; + selectParents(addedItemId); + } + }); + + changes.removed.forEach((removedItemId) => { + if (selectionPropagation.parents) { + let parentId = instance.getItemMeta(removedItemId).parentId; + while (parentId != null) { + if (newModelLookup[parentId]) { + shouldRegenerateModel = true; + delete newModelLookup[parentId]; + } + + parentId = instance.getItemMeta(parentId).parentId; + } + } + + if (selectionPropagation.descendants) { + const deSelectDescendants = (itemId: TreeViewItemId) => { + if (itemId !== removedItemId) { + shouldRegenerateModel = true; + delete newModelLookup[itemId]; + } + + instance.getItemOrderedChildrenIds(itemId).forEach(deSelectDescendants); + }; + + deSelectDescendants(removedItemId); + } + }); + + return shouldRegenerateModel ? Object.keys(newModelLookup) : newModel; +}; diff --git a/packages/x-tree-view/src/models/events.ts b/packages/x-tree-view/src/models/events.ts new file mode 100644 index 000000000000..bddb1e4fea7e --- /dev/null +++ b/packages/x-tree-view/src/models/events.ts @@ -0,0 +1,8 @@ +export type TreeViewCancellableEvent = { + // TODO: Rename `defaultXTreeViewPrevented` + defaultMuiPrevented?: boolean; +}; + +export type TreeViewCancellableEventHandler = ( + event: Event & TreeViewCancellableEvent, +) => void; diff --git a/packages/x-tree-view/src/models/index.ts b/packages/x-tree-view/src/models/index.ts index d3853dec6dd7..460d553fdd5c 100644 --- a/packages/x-tree-view/src/models/index.ts +++ b/packages/x-tree-view/src/models/index.ts @@ -1,4 +1,5 @@ export * from './items'; +export * from './events'; // Utils shared across the X packages export type { PropsFromSlot } from '@mui/x-internals/slots'; diff --git a/packages/x-tree-view/src/models/items.ts b/packages/x-tree-view/src/models/items.ts index f1ef54da13b9..0d15e359d2d6 100644 --- a/packages/x-tree-view/src/models/items.ts +++ b/packages/x-tree-view/src/models/items.ts @@ -10,3 +10,8 @@ export type TreeViewItemsReorderingAction = | 'reorder-below' | 'make-child' | 'move-to-parent'; + +export interface TreeViewSelectionPropagation { + descendants?: boolean; + parents?: boolean; +} diff --git a/packages/x-tree-view/src/themeAugmentation/components.d.ts b/packages/x-tree-view/src/themeAugmentation/components.d.ts index 8dbd4cff0aa8..3c953271789f 100644 --- a/packages/x-tree-view/src/themeAugmentation/components.d.ts +++ b/packages/x-tree-view/src/themeAugmentation/components.d.ts @@ -21,11 +21,6 @@ export interface TreeViewComponents { styleOverrides?: ComponentsOverrides['MuiTreeItem']; variants?: ComponentsVariants['MuiTreeItem']; }; - MuiTreeItem2?: { - defaultProps?: ComponentsProps['MuiTreeItem2']; - styleOverrides?: ComponentsOverrides['MuiTreeItem2']; - variants?: ComponentsVariants['MuiTreeItem2']; - }; } declare module '@mui/material/styles' { diff --git a/packages/x-tree-view/src/themeAugmentation/overrides.d.ts b/packages/x-tree-view/src/themeAugmentation/overrides.d.ts index 6402fef94e92..8cedc3a2308c 100644 --- a/packages/x-tree-view/src/themeAugmentation/overrides.d.ts +++ b/packages/x-tree-view/src/themeAugmentation/overrides.d.ts @@ -9,7 +9,6 @@ export interface TreeViewComponentNameToClassKey { MuiRichTreeView: RichTreeViewClassKey; MuiTreeView: TreeViewClassKey; MuiTreeItem: TreeItemClassKey; - MuiTreeItem2: TreeItemClassKey; } declare module '@mui/material/styles' { diff --git a/packages/x-tree-view/src/themeAugmentation/props.d.ts b/packages/x-tree-view/src/themeAugmentation/props.d.ts index da5ae41f5bd4..9e3b18e3105b 100644 --- a/packages/x-tree-view/src/themeAugmentation/props.d.ts +++ b/packages/x-tree-view/src/themeAugmentation/props.d.ts @@ -1,15 +1,13 @@ -import { TreeItemProps } from '../TreeItem'; import { TreeViewProps } from '../TreeView'; import { SimpleTreeViewProps } from '../SimpleTreeView'; import { RichTreeViewProps } from '../RichTreeView'; -import { TreeItem2Props } from '../TreeItem2'; +import { TreeItemProps } from '../TreeItem'; export interface TreeViewComponentsPropsList { MuiSimpleTreeView: SimpleTreeViewProps; MuiRichTreeView: RichTreeViewProps; MuiTreeView: TreeViewProps; MuiTreeItem: TreeItemProps; - MuiTreeItem2: TreeItem2Props; } declare module '@mui/material/styles' { diff --git a/packages/x-tree-view/src/themeAugmentation/themeAugmentation.spec.ts b/packages/x-tree-view/src/themeAugmentation/themeAugmentation.spec.ts index e90c0a986079..67285be2ce2c 100644 --- a/packages/x-tree-view/src/themeAugmentation/themeAugmentation.spec.ts +++ b/packages/x-tree-view/src/themeAugmentation/themeAugmentation.spec.ts @@ -82,24 +82,5 @@ createTheme({ }, }, }, - MuiTreeItem2: { - defaultProps: { - itemId: '1', - // @ts-expect-error invalid MuiTreeItem2 prop - someRandomProp: true, - }, - styleOverrides: { - root: { - backgroundColor: 'red', - [`.${treeItemClasses.content}`]: { - backgroundColor: 'green', - }, - }, - // @ts-expect-error invalid MuiTreeItem2 class key - main: { - backgroundColor: 'blue', - }, - }, - }, }, }); diff --git a/packages/x-tree-view/src/useTreeItem/index.ts b/packages/x-tree-view/src/useTreeItem/index.ts new file mode 100644 index 000000000000..29daa35442c2 --- /dev/null +++ b/packages/x-tree-view/src/useTreeItem/index.ts @@ -0,0 +1,15 @@ +export { useTreeItem } from './useTreeItem'; + +export type { + UseTreeItemParameters, + UseTreeItemReturnValue, + UseTreeItemStatus, + UseTreeItemRootSlotOwnProps, + UseTreeItemContentSlotOwnProps, + UseTreeItemLabelInputSlotOwnProps, + UseTreeItemLabelSlotOwnProps, + UseTreeItemCheckboxSlotOwnProps, + UseTreeItemIconContainerSlotOwnProps, + UseTreeItemGroupTransitionSlotOwnProps, + UseTreeItemDragAndDropOverlaySlotOwnProps, +} from './useTreeItem.types'; diff --git a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.test.tsx b/packages/x-tree-view/src/useTreeItem/useTreeItem.test.tsx similarity index 87% rename from packages/x-tree-view/src/useTreeItem2/useTreeItem2.test.tsx rename to packages/x-tree-view/src/useTreeItem/useTreeItem.test.tsx index 51e07dcabc5a..2d09699c4b80 100644 --- a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.test.tsx +++ b/packages/x-tree-view/src/useTreeItem/useTreeItem.test.tsx @@ -13,15 +13,8 @@ import { import { treeItemClasses } from '@mui/x-tree-view/TreeItem'; describeTreeView<[UseTreeViewExpansionSignature, UseTreeViewIconsSignature]>( - 'useTreeItem2 hook', - ({ - render, - renderFromJSX, - treeItemComponentName, - TreeItemComponent, - treeViewComponentName, - TreeViewComponent, - }) => { + 'useTreeItem hook', + ({ render, renderFromJSX, TreeItemComponent, treeViewComponentName, TreeViewComponent }) => { describe('role prop', () => { it('should have the role="treeitem" on the root slot', () => { const view = render({ items: [{ id: '1' }] }); @@ -56,7 +49,7 @@ describeTreeView<[UseTreeViewExpansionSignature, UseTreeViewIconsSignature]>( }); fireEvent.click(view.getItemContent('1.1')); - expect(onClick.callCount).to.equal(treeItemComponentName === 'TreeItem' ? 1 : 2); + expect(onClick.callCount).to.equal(2); expect(onClick.lastCall.firstArg.target.parentElement.dataset.testid).to.equal('1.1'); }); @@ -81,20 +74,13 @@ describeTreeView<[UseTreeViewExpansionSignature, UseTreeViewIconsSignature]>( const view = render({ items: [{ id: '1', children: [{ id: '1.1' }] }], defaultExpandedItems: ['1'], - slotProps: - treeItemComponentName === 'TreeItem2' - ? { - item: { - slots: { - label: () => , - }, - }, - } - : { - item: { - label: , - }, - }, + slotProps: { + item: { + slots: { + label: () => , + }, + }, + }, }); const input = view.getItemRoot('1.1').querySelector('.icon-input')!; diff --git a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts b/packages/x-tree-view/src/useTreeItem/useTreeItem.ts similarity index 77% rename from packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts rename to packages/x-tree-view/src/useTreeItem/useTreeItem.ts index 674f7380b39f..0492ddd182a2 100644 --- a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.ts +++ b/packages/x-tree-view/src/useTreeItem/useTreeItem.ts @@ -1,39 +1,38 @@ +'use client'; import * as React from 'react'; import { EventHandlers } from '@mui/utils'; import extractEventHandlers from '@mui/utils/extractEventHandlers'; import useForkRef from '@mui/utils/useForkRef'; +import { TreeViewCancellableEvent } from '../models'; import { - UseTreeItem2Parameters, - UseTreeItem2ReturnValue, - UseTreeItem2RootSlotProps, - UseTreeItem2ContentSlotProps, - UseTreeItem2GroupTransitionSlotProps, - UseTreeItem2LabelSlotProps, + UseTreeItemParameters, + UseTreeItemReturnValue, + UseTreeItemRootSlotProps, + UseTreeItemContentSlotProps, + UseTreeItemGroupTransitionSlotProps, + UseTreeItemLabelSlotProps, UseTreeItemIconContainerSlotProps, - UseTreeItem2CheckboxSlotProps, - UseTreeItem2LabelInputSlotProps, - UseTreeItem2MinimalPlugins, - UseTreeItem2OptionalPlugins, - UseTreeItem2DragAndDropOverlaySlotProps, - UseTreeItem2RootSlotPropsFromUseTreeItem, - UseTreeItem2ContentSlotPropsFromUseTreeItem, -} from './useTreeItem2.types'; + UseTreeItemCheckboxSlotProps, + UseTreeItemLabelInputSlotProps, + UseTreeItemMinimalPlugins, + UseTreeItemOptionalPlugins, + UseTreeItemDragAndDropOverlaySlotProps, + UseTreeItemRootSlotPropsFromUseTreeItem, + UseTreeItemContentSlotPropsFromUseTreeItem, +} from './useTreeItem.types'; import { useTreeViewContext } from '../internals/TreeViewProvider'; -import { - MuiCancellableEvent, - TreeViewItemPluginSlotPropsEnhancerParams, -} from '../internals/models'; -import { useTreeItem2Utils } from '../hooks/useTreeItem2Utils'; +import { TreeViewItemPluginSlotPropsEnhancerParams } from '../internals/models'; +import { useTreeItemUtils } from '../hooks/useTreeItemUtils'; import { TreeViewItemDepthContext } from '../internals/TreeViewItemDepthContext'; import { isTargetInDescendants } from '../internals/utils/tree'; import { generateTreeItemIdAttribute } from '../internals/corePlugins/useTreeViewId/useTreeViewId.utils'; -export const useTreeItem2 = < - TSignatures extends UseTreeItem2MinimalPlugins = UseTreeItem2MinimalPlugins, - TOptionalSignatures extends UseTreeItem2OptionalPlugins = UseTreeItem2OptionalPlugins, +export const useTreeItem = < + TSignatures extends UseTreeItemMinimalPlugins = UseTreeItemMinimalPlugins, + TOptionalSignatures extends UseTreeItemOptionalPlugins = UseTreeItemOptionalPlugins, >( - parameters: UseTreeItem2Parameters, -): UseTreeItem2ReturnValue => { + parameters: UseTreeItemParameters, +): UseTreeItemReturnValue => { const { runItemPlugins, items: { onItemClick, disabledItemsFocusable, indentationAtItemLevel }, @@ -48,7 +47,7 @@ export const useTreeItem2 = < const { id, itemId, label, children, rootRef } = parameters; const { rootRef: pluginRootRef, contentRef, propsEnhancers } = runItemPlugins(parameters); - const { interactions, status } = useTreeItem2Utils({ itemId, children }); + const { interactions, status } = useTreeItemUtils({ itemId, children }); const rootRefObject = React.useRef(null); const contentRefObject = React.useRef(null); const handleRootRef = useForkRef(rootRef, pluginRootRef, rootRefObject)!; @@ -61,11 +60,11 @@ export const useTreeItem2 = < const sharedPropsEnhancerParams: Omit< TreeViewItemPluginSlotPropsEnhancerParams, 'externalEventHandlers' - > = { rootRefObject, contentRefObject, interactions }; + > = { rootRefObject, contentRefObject, interactions, status }; const createRootHandleFocus = (otherHandlers: EventHandlers) => - (event: React.FocusEvent & MuiCancellableEvent) => { + (event: React.FocusEvent & TreeViewCancellableEvent) => { otherHandlers.onFocus?.(event); if (event.defaultMuiPrevented) { return; @@ -79,7 +78,7 @@ export const useTreeItem2 = < const createRootHandleBlur = (otherHandlers: EventHandlers) => - (event: React.FocusEvent & MuiCancellableEvent) => { + (event: React.FocusEvent & TreeViewCancellableEvent) => { otherHandlers.onBlur?.(event); if (event.defaultMuiPrevented) { return; @@ -92,7 +91,7 @@ export const useTreeItem2 = < // when we enter the editing state, we focus the input -> we don't want to remove the focused item from the state if ( status.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 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, rootElement) && @@ -109,7 +108,7 @@ export const useTreeItem2 = < const createRootHandleKeyDown = (otherHandlers: EventHandlers) => - (event: React.KeyboardEvent & MuiCancellableEvent) => { + (event: React.KeyboardEvent & TreeViewCancellableEvent) => { otherHandlers.onKeyDown?.(event); if ( event.defaultMuiPrevented || @@ -122,7 +121,7 @@ export const useTreeItem2 = < }; const createLabelHandleDoubleClick = - (otherHandlers: EventHandlers) => (event: React.MouseEvent & MuiCancellableEvent) => { + (otherHandlers: EventHandlers) => (event: React.MouseEvent & TreeViewCancellableEvent) => { otherHandlers.onDoubleClick?.(event); if (event.defaultMuiPrevented) { return; @@ -131,7 +130,7 @@ export const useTreeItem2 = < }; const createContentHandleClick = - (otherHandlers: EventHandlers) => (event: React.MouseEvent & MuiCancellableEvent) => { + (otherHandlers: EventHandlers) => (event: React.MouseEvent & TreeViewCancellableEvent) => { otherHandlers.onClick?.(event); onItemClick?.(event, itemId); @@ -148,7 +147,7 @@ export const useTreeItem2 = < }; const createContentHandleMouseDown = - (otherHandlers: EventHandlers) => (event: React.MouseEvent & MuiCancellableEvent) => { + (otherHandlers: EventHandlers) => (event: React.MouseEvent & TreeViewCancellableEvent) => { otherHandlers.onMouseDown?.(event); if (event.defaultMuiPrevented) { return; @@ -160,23 +159,8 @@ export const useTreeItem2 = < } }; - const createCheckboxHandleChange = - (otherHandlers: EventHandlers) => - (event: React.ChangeEvent & MuiCancellableEvent) => { - otherHandlers.onChange?.(event); - if (event.defaultMuiPrevented) { - return; - } - - if (disableSelection || status.disabled) { - return; - } - - interactions.handleCheckboxSelection(event); - }; - const createIconContainerHandleClick = - (otherHandlers: EventHandlers) => (event: React.MouseEvent & MuiCancellableEvent) => { + (otherHandlers: EventHandlers) => (event: React.MouseEvent & TreeViewCancellableEvent) => { otherHandlers.onClick?.(event); if (event.defaultMuiPrevented) { return; @@ -188,7 +172,7 @@ export const useTreeItem2 = < const getRootProps = = {}>( externalProps: ExternalProps = {} as ExternalProps, - ): UseTreeItem2RootSlotProps => { + ): UseTreeItemRootSlotProps => { const externalEventHandlers = { ...extractEventHandlers(parameters), ...extractEventHandlers(externalProps), @@ -207,7 +191,7 @@ export const useTreeItem2 = < ariaSelected = false; } - const props: UseTreeItem2RootSlotPropsFromUseTreeItem = { + const props: UseTreeItemRootSlotPropsFromUseTreeItem = { ...externalEventHandlers, ref: handleRootRef, role: 'treeitem', @@ -235,15 +219,15 @@ export const useTreeItem2 = < return { ...props, ...enhancedRootProps, - } as UseTreeItem2RootSlotProps; + } as UseTreeItemRootSlotProps; }; const getContentProps = = {}>( externalProps: ExternalProps = {} as ExternalProps, - ): UseTreeItem2ContentSlotProps => { + ): UseTreeItemContentSlotProps => { const externalEventHandlers = extractEventHandlers(externalProps); - const props: UseTreeItem2ContentSlotPropsFromUseTreeItem = { + const props: UseTreeItemContentSlotPropsFromUseTreeItem = { ...externalEventHandlers, ...externalProps, ref: handleContentRef, @@ -262,34 +246,40 @@ export const useTreeItem2 = < return { ...props, ...enhancedContentProps, - } as UseTreeItem2ContentSlotProps; + } as UseTreeItemContentSlotProps; }; const getCheckboxProps = = {}>( externalProps: ExternalProps = {} as ExternalProps, - ): UseTreeItem2CheckboxSlotProps => { + ): UseTreeItemCheckboxSlotProps => { const externalEventHandlers = extractEventHandlers(externalProps); - return { + const props = { ...externalEventHandlers, - visible: checkboxSelection, ref: checkboxRef, - checked: status.selected, - disabled: disableSelection || status.disabled, - tabIndex: -1, ...externalProps, - onChange: createCheckboxHandleChange(externalEventHandlers), + }; + + const enhancedCheckboxProps = + propsEnhancers.checkbox?.({ + ...sharedPropsEnhancerParams, + externalEventHandlers, + }) ?? {}; + + return { + ...props, + ...enhancedCheckboxProps, }; }; const getLabelProps = = {}>( externalProps: ExternalProps = {} as ExternalProps, - ): UseTreeItem2LabelSlotProps => { + ): UseTreeItemLabelSlotProps => { const externalEventHandlers = { ...extractEventHandlers(externalProps), }; - const props: UseTreeItem2LabelSlotProps = { + const props: UseTreeItemLabelSlotProps = { ...externalEventHandlers, children: label, ...externalProps, @@ -305,21 +295,19 @@ export const useTreeItem2 = < const getLabelInputProps = = {}>( externalProps: ExternalProps = {} as ExternalProps, - ): UseTreeItem2LabelInputSlotProps => { + ): UseTreeItemLabelInputSlotProps => { const externalEventHandlers = extractEventHandlers(externalProps); const enhancedLabelInputProps = propsEnhancers.labelInput?.({ - rootRefObject, - contentRefObject, + ...sharedPropsEnhancerParams, externalEventHandlers, - interactions, }) ?? {}; return { ...externalProps, ...enhancedLabelInputProps, - } as UseTreeItem2LabelInputSlotProps; + } as UseTreeItemLabelInputSlotProps; }; const getIconContainerProps = = {}>( @@ -336,10 +324,10 @@ export const useTreeItem2 = < const getGroupTransitionProps = = {}>( externalProps: ExternalProps = {} as ExternalProps, - ): UseTreeItem2GroupTransitionSlotProps => { + ): UseTreeItemGroupTransitionSlotProps => { const externalEventHandlers = extractEventHandlers(externalProps); - const response: UseTreeItem2GroupTransitionSlotProps = { + const response: UseTreeItemGroupTransitionSlotProps = { ...externalEventHandlers, unmountOnExit: true, component: 'ul', @@ -358,7 +346,7 @@ export const useTreeItem2 = < const getDragAndDropOverlayProps = = {}>( externalProps: ExternalProps = {} as ExternalProps, - ): UseTreeItem2DragAndDropOverlaySlotProps => { + ): UseTreeItemDragAndDropOverlaySlotProps => { const externalEventHandlers = extractEventHandlers(externalProps); const enhancedDragAndDropOverlayProps = @@ -370,7 +358,7 @@ export const useTreeItem2 = < return { ...externalProps, ...enhancedDragAndDropOverlayProps, - } as UseTreeItem2DragAndDropOverlaySlotProps; + } as UseTreeItemDragAndDropOverlaySlotProps; }; return { diff --git a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.types.ts b/packages/x-tree-view/src/useTreeItem/useTreeItem.types.ts similarity index 55% rename from packages/x-tree-view/src/useTreeItem2/useTreeItem2.types.ts rename to packages/x-tree-view/src/useTreeItem/useTreeItem.types.ts index 68080b4f4f73..07a29ab799f7 100644 --- a/packages/x-tree-view/src/useTreeItem2/useTreeItem2.types.ts +++ b/packages/x-tree-view/src/useTreeItem/useTreeItem.types.ts @@ -1,6 +1,6 @@ import * as React from 'react'; -import { TreeViewItemId } from '../models'; -import { TreeViewPublicAPI, MuiCancellableEventHandler } from '../internals/models'; +import { TreeViewItemId, TreeViewCancellableEventHandler } from '../models'; +import { TreeViewPublicAPI } from '../internals/models'; import { UseTreeViewSelectionSignature } from '../internals/plugins/useTreeViewSelection'; import { UseTreeViewItemsSignature } from '../internals/plugins/useTreeViewItems'; import { UseTreeViewFocusSignature } from '../internals/plugins/useTreeViewFocus'; @@ -8,7 +8,7 @@ import { UseTreeViewKeyboardNavigationSignature } from '../internals/plugins/use import { UseTreeViewLabelSignature } from '../internals/plugins/useTreeViewLabel'; import { UseTreeViewExpansionSignature } from '../internals/plugins/useTreeViewExpansion'; -export interface UseTreeItem2Parameters { +export interface UseTreeItemParameters { /** * The id attribute of the item. If not provided, it will be generated. */ @@ -34,16 +34,16 @@ export interface UseTreeItem2Parameters { children?: React.ReactNode; } -export interface UseTreeItem2RootSlotPropsFromUseTreeItem { +export interface UseTreeItemRootSlotPropsFromUseTreeItem { role: 'treeitem'; tabIndex: 0 | -1; id: string; 'aria-expanded': React.AriaAttributes['aria-expanded']; 'aria-selected': React.AriaAttributes['aria-selected']; 'aria-disabled': React.AriaAttributes['aria-disabled']; - onFocus: MuiCancellableEventHandler>; - onBlur: MuiCancellableEventHandler>; - onKeyDown: MuiCancellableEventHandler>; + onFocus: TreeViewCancellableEventHandler>; + onBlur: TreeViewCancellableEventHandler>; + onKeyDown: TreeViewCancellableEventHandler>; ref: React.RefCallback; /** * Only defined when the `indentationAtItemLevel` experimental feature is enabled. @@ -51,65 +51,60 @@ export interface UseTreeItem2RootSlotPropsFromUseTreeItem { style?: React.CSSProperties; } -export interface UseTreeItem2RootSlotOwnProps extends UseTreeItem2RootSlotPropsFromUseTreeItem {} +export interface UseTreeItemRootSlotOwnProps extends UseTreeItemRootSlotPropsFromUseTreeItem {} -export type UseTreeItem2RootSlotProps = ExternalProps & - UseTreeItem2RootSlotOwnProps; +export type UseTreeItemRootSlotProps = ExternalProps & + UseTreeItemRootSlotOwnProps; -export interface UseTreeItem2ContentSlotPropsFromUseTreeItem { - onClick: MuiCancellableEventHandler; - onMouseDown: MuiCancellableEventHandler; +export interface UseTreeItemContentSlotPropsFromUseTreeItem { + onClick: TreeViewCancellableEventHandler; + onMouseDown: TreeViewCancellableEventHandler; ref: React.RefCallback | null; - status: UseTreeItem2Status; + status: UseTreeItemStatus; /** * Only defined when the `indentationAtItemLevel` experimental feature is enabled. */ indentationAtItemLevel?: true; } -export interface UseTreeItem2ContentSlotOwnProps - extends UseTreeItem2ContentSlotPropsFromUseTreeItem {} +export interface UseTreeItemContentSlotOwnProps + extends UseTreeItemContentSlotPropsFromUseTreeItem {} -export type UseTreeItem2ContentSlotProps = ExternalProps & - UseTreeItem2ContentSlotOwnProps; +export type UseTreeItemContentSlotProps = ExternalProps & + UseTreeItemContentSlotOwnProps; -export interface UseTreeItem2IconContainerSlotOwnProps { - onClick: MuiCancellableEventHandler; +export interface UseTreeItemIconContainerSlotOwnProps { + onClick: TreeViewCancellableEventHandler; } export type UseTreeItemIconContainerSlotProps = ExternalProps & - UseTreeItem2IconContainerSlotOwnProps; + UseTreeItemIconContainerSlotOwnProps; -export interface UseTreeItem2LabelSlotOwnProps { +export interface UseTreeItemLabelSlotOwnProps { children: React.ReactNode; - onDoubleClick: MuiCancellableEventHandler; + onDoubleClick: TreeViewCancellableEventHandler; /** * Only defined when the `isItemEditable` experimental feature is enabled. */ editable?: boolean; } -export type UseTreeItem2LabelSlotProps = ExternalProps & - UseTreeItem2LabelSlotOwnProps; +export type UseTreeItemLabelSlotProps = ExternalProps & + UseTreeItemLabelSlotOwnProps; -export interface UseTreeItem2LabelInputSlotOwnProps {} +export interface UseTreeItemLabelInputSlotOwnProps {} -export type UseTreeItem2LabelInputSlotProps = ExternalProps & - UseTreeItem2LabelInputSlotOwnProps; +export type UseTreeItemLabelInputSlotProps = ExternalProps & + UseTreeItemLabelInputSlotOwnProps; -export interface UseTreeItem2CheckboxSlotOwnProps { - visible: boolean; - checked: boolean; - onChange: MuiCancellableEventHandler>; - disabled: boolean; +export interface UseTreeItemCheckboxSlotOwnProps { ref: React.RefObject; - tabIndex: -1; } -export type UseTreeItem2CheckboxSlotProps = ExternalProps & - UseTreeItem2CheckboxSlotOwnProps; +export type UseTreeItemCheckboxSlotProps = ExternalProps & + UseTreeItemCheckboxSlotOwnProps; -export interface UseTreeItem2GroupTransitionSlotOwnProps { +export interface UseTreeItemGroupTransitionSlotOwnProps { unmountOnExit: boolean; in: boolean; component: 'ul'; @@ -121,15 +116,15 @@ export interface UseTreeItem2GroupTransitionSlotOwnProps { indentationAtItemLevel?: true; } -export type UseTreeItem2GroupTransitionSlotProps = ExternalProps & - UseTreeItem2GroupTransitionSlotOwnProps; +export type UseTreeItemGroupTransitionSlotProps = ExternalProps & + UseTreeItemGroupTransitionSlotOwnProps; -export interface UseTreeItem2DragAndDropOverlaySlotOwnProps {} +export interface UseTreeItemDragAndDropOverlaySlotOwnProps {} -export type UseTreeItem2DragAndDropOverlaySlotProps = ExternalProps & - UseTreeItem2DragAndDropOverlaySlotOwnProps; +export type UseTreeItemDragAndDropOverlaySlotProps = ExternalProps & + UseTreeItemDragAndDropOverlaySlotOwnProps; -export interface UseTreeItem2Status { +export interface UseTreeItemStatus { expandable: boolean; expanded: boolean; focused: boolean; @@ -139,50 +134,50 @@ export interface UseTreeItem2Status { editable: boolean; } -export interface UseTreeItem2ReturnValue< - TSignatures extends UseTreeItem2MinimalPlugins, - TOptionalSignatures extends UseTreeItem2OptionalPlugins, +export interface UseTreeItemReturnValue< + TSignatures extends UseTreeItemMinimalPlugins, + TOptionalSignatures extends UseTreeItemOptionalPlugins, > { /** * Resolver for the root slot's props. * @param {ExternalProps} externalProps Additional props for the root slot. - * @returns {UseTreeItem2RootSlotProps} Props that should be spread on the root slot. + * @returns {UseTreeItemRootSlotProps} Props that should be spread on the root slot. */ getRootProps: = {}>( externalProps?: ExternalProps, - ) => UseTreeItem2RootSlotProps; + ) => UseTreeItemRootSlotProps; /** * Resolver for the content slot's props. * @param {ExternalProps} externalProps Additional props for the content slot. - * @returns {UseTreeItem2ContentSlotProps} Props that should be spread on the content slot. + * @returns {UseTreeItemContentSlotProps} Props that should be spread on the content slot. */ getContentProps: = {}>( externalProps?: ExternalProps, - ) => UseTreeItem2ContentSlotProps; + ) => UseTreeItemContentSlotProps; /** * Resolver for the label slot's props. * @param {ExternalProps} externalProps Additional props for the label slot. - * @returns {UseTreeItem2LabelSlotProps} Props that should be spread on the label slot. + * @returns {UseTreeItemLabelSlotProps} Props that should be spread on the label slot. */ getLabelProps: = {}>( externalProps?: ExternalProps, - ) => UseTreeItem2LabelSlotProps; + ) => UseTreeItemLabelSlotProps; /** * Resolver for the labelInput slot's props. * @param {ExternalProps} externalProps Additional props for the labelInput slot. - * @returns {UseTreeItem2LabelInputSlotProps} Props that should be spread on the labelInput slot. + * @returns {UseTreeItemLabelInputSlotProps} Props that should be spread on the labelInput slot. */ getLabelInputProps: = {}>( externalProps?: ExternalProps, - ) => UseTreeItem2LabelInputSlotProps; + ) => UseTreeItemLabelInputSlotProps; /** * Resolver for the checkbox slot's props. * @param {ExternalProps} externalProps Additional props for the checkbox slot. - * @returns {UseTreeItem2CheckboxSlotProps} Props that should be spread on the checkbox slot. + * @returns {UseTreeItemCheckboxSlotProps} Props that should be spread on the checkbox slot. */ getCheckboxProps: = {}>( externalProps?: ExternalProps, - ) => UseTreeItem2CheckboxSlotProps; + ) => UseTreeItemCheckboxSlotProps; /** * Resolver for the iconContainer slot's props. * @param {ExternalProps} externalProps Additional props for the iconContainer slot. @@ -194,20 +189,20 @@ export interface UseTreeItem2ReturnValue< /** * Resolver for the GroupTransition slot's props. * @param {ExternalProps} externalProps Additional props for the GroupTransition slot. - * @returns {UseTreeItem2GroupTransitionSlotProps} Props that should be spread on the GroupTransition slot. + * @returns {UseTreeItemGroupTransitionSlotProps} Props that should be spread on the GroupTransition slot. */ getGroupTransitionProps: = {}>( externalProps?: ExternalProps, - ) => UseTreeItem2GroupTransitionSlotProps; + ) => UseTreeItemGroupTransitionSlotProps; /** * Resolver for the DragAndDropOverlay slot's props. - * Warning: This slot is only useful when using the `RichTreeViewPro` component. + * Warning: This slot is only useful when using the `` component. * @param {ExternalProps} externalProps Additional props for the DragAndDropOverlay slot. - * @returns {UseTreeItem2DragAndDropOverlaySlotProps} Props that should be spread on the DragAndDropOverlay slot. + * @returns {UseTreeItemDragAndDropOverlaySlotProps} Props that should be spread on the DragAndDropOverlay slot. */ getDragAndDropOverlayProps: = {}>( externalProps?: ExternalProps, - ) => UseTreeItem2DragAndDropOverlaySlotProps; + ) => UseTreeItemDragAndDropOverlaySlotProps; /** * A ref to the component's root DOM element. */ @@ -215,7 +210,7 @@ export interface UseTreeItem2ReturnValue< /** * Current status of the item. */ - status: UseTreeItem2Status; + status: UseTreeItemStatus; /** * The object the allows Tree View manipulation. */ @@ -223,9 +218,9 @@ export interface UseTreeItem2ReturnValue< } /** - * Plugins that need to be present in the Tree View in order for `useTreeItem2` to work correctly. + * Plugins that need to be present in the Tree View in order for `UseTreeItem` to work correctly. */ -export type UseTreeItem2MinimalPlugins = readonly [ +export type UseTreeItemMinimalPlugins = readonly [ UseTreeViewSelectionSignature, UseTreeViewExpansionSignature, UseTreeViewItemsSignature, @@ -235,6 +230,6 @@ export type UseTreeItem2MinimalPlugins = readonly [ ]; /** - * Plugins that `useTreeItem2` can use if they are present, but are not required. + * Plugins that `UseTreeItem` can use if they are present, but are not required. */ -export type UseTreeItem2OptionalPlugins = readonly []; +export type UseTreeItemOptionalPlugins = readonly []; diff --git a/packages/x-tree-view/src/useTreeItem2/index.ts b/packages/x-tree-view/src/useTreeItem2/index.ts deleted file mode 100644 index 58154d6640e8..000000000000 --- a/packages/x-tree-view/src/useTreeItem2/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useTreeItem2 as useTreeItem2Export } from './useTreeItem2'; - -export const useTreeItem2 = useTreeItem2Export; -// eslint-disable-next-line @typescript-eslint/naming-convention -export const unstable_useTreeItem2 = useTreeItem2Export; - -export type { - UseTreeItem2Parameters, - UseTreeItem2ReturnValue, - UseTreeItem2Status, - UseTreeItem2RootSlotOwnProps, - UseTreeItem2ContentSlotOwnProps, - UseTreeItem2LabelInputSlotOwnProps, - UseTreeItem2LabelSlotOwnProps, - UseTreeItem2CheckboxSlotOwnProps, - UseTreeItem2IconContainerSlotOwnProps, - UseTreeItem2GroupTransitionSlotOwnProps, - UseTreeItem2DragAndDropOverlaySlotOwnProps, -} from './useTreeItem2.types'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57785408e6f7..20f5d2afcc95 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: overrides: react-is: ^18.3.1 - '@types/node': ^20.16.11 + '@types/node': ^20.16.14 patchedDependencies: babel-plugin-replace-imports@1.0.2: @@ -99,8 +99,8 @@ importers: specifier: ^5.16.7 version: 5.16.7(@emotion/react@11.13.3(@types/react@18.3.4)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.4)(react@18.3.1))(@types/react@18.3.4)(react@18.3.1))(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/monorepo': - specifier: github:mui/material-ui#010de4505361345951824d905d1508d6f258ba67 - version: https://codeload.github.com/mui/material-ui/tar.gz/010de4505361345951824d905d1508d6f258ba67(encoding@0.1.13) + specifier: github:mui/material-ui#ca637077e091b7f1f6e591ded5a453edcc784204 + version: https://codeload.github.com/mui/material-ui/tar.gz/ca637077e091b7f1f6e591ded5a453edcc784204(encoding@0.1.13) '@mui/utils': specifier: ^5.16.6 version: 5.16.6(@types/react@18.3.4)(react@18.3.1) @@ -135,14 +135,14 @@ importers: specifier: ^6.3.8 version: 6.3.8 '@types/lodash': - specifier: ^4.17.10 - version: 4.17.10 + specifier: ^4.17.12 + version: 4.17.12 '@types/mocha': specifier: ^10.0.9 version: 10.0.9 '@types/node': - specifier: ^20.16.11 - version: 20.16.11 + specifier: ^20.16.14 + version: 20.16.14 '@types/react': specifier: ^18.3.4 version: 18.3.4 @@ -171,8 +171,8 @@ importers: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.47) axe-core: - specifier: 4.10.0 - version: 4.10.0 + specifier: 4.10.1 + version: 4.10.1 babel-loader: specifier: ^9.2.1 version: 9.2.1(@babel/core@7.25.8)(webpack@5.95.0) @@ -229,7 +229,7 @@ importers: version: 8.57.1 eslint-config-airbnb: specifier: ^19.0.4 - version: 19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@5.0.0(eslint@8.57.1))(eslint-plugin-react@7.37.1(eslint@8.57.1))(eslint@8.57.1) + version: 19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.1(eslint@8.57.1))(eslint-plugin-react-hooks@5.0.0(eslint@8.57.1))(eslint-plugin-react@7.37.1(eslint@8.57.1))(eslint@8.57.1) eslint-config-airbnb-typescript: specifier: ^18.0.0 version: 18.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1) @@ -246,11 +246,11 @@ importers: specifier: ^2.31.0 version: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-webpack@0.13.9)(eslint@8.57.1) eslint-plugin-jsdoc: - specifier: ^50.4.1 - version: 50.4.1(eslint@8.57.1) + specifier: ^50.4.3 + version: 50.4.3(eslint@8.57.1) eslint-plugin-jsx-a11y: - specifier: ^6.10.0 - version: 6.10.0(eslint@8.57.1) + specifier: ^6.10.1 + version: 6.10.1(eslint@8.57.1) eslint-plugin-material-ui: specifier: workspace:^ version: link:packages/eslint-plugin-material-ui @@ -270,8 +270,8 @@ importers: specifier: ^5.0.0 version: 5.0.0(eslint@8.57.1) eslint-plugin-testing-library: - specifier: ^6.3.0 - version: 6.3.0(eslint@8.57.1)(typescript@5.6.3) + specifier: ^6.4.0 + version: 6.4.0(eslint@8.57.1)(typescript@5.6.3) fast-glob: specifier: ^3.3.2 version: 3.3.2 @@ -288,8 +288,8 @@ importers: specifier: ^14.0.2 version: 14.0.2 html-webpack-plugin: - specifier: ^5.6.0 - version: 5.6.0(webpack@5.95.0) + specifier: ^5.6.2 + version: 5.6.2(webpack@5.95.0) jsdom: specifier: 24.1.3 version: 24.1.3 @@ -363,8 +363,8 @@ importers: specifier: ^6.0.1 version: 6.0.1 serve: - specifier: ^14.2.3 - version: 14.2.3 + specifier: ^14.2.4 + version: 14.2.4 sinon: specifier: ^18.0.1 version: 18.0.1 @@ -600,8 +600,8 @@ importers: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) react-hook-form: - specifier: ^7.53.0 - version: 7.53.0(react@18.3.1) + specifier: ^7.53.1 + version: 7.53.1(react@18.3.1) react-is: specifier: ^18.3.1 version: 18.3.1 @@ -620,6 +620,9 @@ importers: recast: specifier: ^0.23.9 version: 0.23.9 + rifm: + specifier: 0.12.1 + version: 0.12.1(react@18.3.1) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -667,8 +670,8 @@ importers: specifier: ^0.0.20 version: 0.0.20 '@types/lodash': - specifier: ^4.17.10 - version: 4.17.10 + specifier: ^4.17.12 + version: 4.17.12 '@types/luxon': specifier: ^3.4.2 version: 3.4.2 @@ -697,8 +700,8 @@ importers: specifier: ^1.25.0 version: 1.25.0 serve: - specifier: ^14.2.3 - version: 14.2.3 + specifier: ^14.2.4 + version: 14.2.4 packages/eslint-plugin-material-ui: devDependencies: @@ -725,8 +728,8 @@ importers: specifier: ^10.0.9 version: 10.0.9 '@types/node': - specifier: ^20.16.11 - version: 20.16.11 + specifier: ^20.16.14 + version: 20.16.14 packages/x-charts: dependencies: @@ -937,8 +940,8 @@ importers: specifier: ^4.1.0 version: 4.1.0 execa: - specifier: ^9.4.0 - version: 9.4.0 + specifier: ^9.4.1 + version: 9.4.1 internmap: specifier: ^2.0.3 version: 2.0.3 @@ -1620,7 +1623,7 @@ importers: devDependencies: '@codspeed/vitest-plugin': specifier: ^3.1.1 - version: 3.1.1(vite@5.3.4(@types/node@20.16.11)(terser@5.27.0))(vitest@2.1.2) + version: 3.1.1(vite@5.3.4(@types/node@20.16.14)(terser@5.27.0))(vitest@2.1.3) '@emotion/react': specifier: ^11.13.3 version: 11.13.3(@types/react@18.3.4)(react@18.3.1) @@ -1631,8 +1634,8 @@ importers: specifier: workspace:* version: link:../../packages/x-charts-pro/build '@testing-library/jest-dom': - specifier: ^6.5.0 - version: 6.5.0 + specifier: ^6.6.2 + version: 6.6.2 '@testing-library/react': specifier: ^16.0.1 version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1640,14 +1643,14 @@ importers: specifier: ^14.5.2 version: 14.5.2(@testing-library/dom@10.4.0) '@vitejs/plugin-react': - specifier: ^4.3.2 - version: 4.3.2(vite@5.3.4(@types/node@20.16.11)(terser@5.27.0)) + specifier: ^4.3.3 + version: 4.3.3(vite@5.3.4(@types/node@20.16.14)(terser@5.27.0)) '@vitejs/plugin-react-swc': specifier: ^3.7.1 - version: 3.7.1(@swc/helpers@0.5.5)(vite@5.3.4(@types/node@20.16.11)(terser@5.27.0)) + version: 3.7.1(@swc/helpers@0.5.5)(vite@5.3.4(@types/node@20.16.14)(terser@5.27.0)) '@vitest/ui': - specifier: 2.1.2 - version: 2.1.2(vitest@2.1.2) + specifier: 2.1.3 + version: 2.1.3(vitest@2.1.3) jsdom: specifier: ^24.1.3 version: 24.1.3 @@ -1658,8 +1661,8 @@ importers: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) vitest: - specifier: 2.1.2 - version: 2.1.2(@types/node@20.16.11)(@vitest/ui@2.1.2)(jsdom@24.1.3)(terser@5.27.0) + specifier: 2.1.3 + version: 2.1.3(@types/node@20.16.14)(@vitest/ui@2.1.3)(jsdom@24.1.3)(terser@5.27.0) packages: @@ -3143,10 +3146,10 @@ packages: '@types/react': optional: true - '@mui/monorepo@https://codeload.github.com/mui/material-ui/tar.gz/010de4505361345951824d905d1508d6f258ba67': - resolution: {tarball: https://codeload.github.com/mui/material-ui/tar.gz/010de4505361345951824d905d1508d6f258ba67} - version: 6.1.4 - engines: {pnpm: 9.12.1} + '@mui/monorepo@https://codeload.github.com/mui/material-ui/tar.gz/ca637077e091b7f1f6e591ded5a453edcc784204': + resolution: {tarball: https://codeload.github.com/mui/material-ui/tar.gz/ca637077e091b7f1f6e591ded5a453edcc784204} + version: 6.1.5 + engines: {pnpm: 9.12.2} '@mui/private-theming@5.16.6': resolution: {integrity: sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==} @@ -3935,8 +3938,8 @@ packages: resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} engines: {node: '>=18'} - '@testing-library/jest-dom@6.5.0': - resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} + '@testing-library/jest-dom@6.6.2': + resolution: {integrity: sha512-P6GJD4yqc9jZLbe98j/EkyQDTPgqftohZF5FBkHY5BUERZmcf4HeO2k0XaefEg329ux2p21i1A1DmyQ1kKw2Jw==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} '@testing-library/react@16.0.1': @@ -4101,8 +4104,8 @@ packages: '@types/karma@6.3.8': resolution: {integrity: sha512-+QGoOPhb1f6Oli8pG+hxdnGDzVhIrpsHaFSJ4UJg15Xj+QBtluKELkJY+L4Li532HmT3l5K5o1FoUZHRQeOOaQ==} - '@types/lodash@4.17.10': - resolution: {integrity: sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==} + '@types/lodash@4.17.12': + resolution: {integrity: sha512-sviUmCE8AYdaF/KIHLDJBQgeYzPBI0vf/17NaYehBJfYD1j6/L95Slh07NlyK2iNyBNaEkb3En2jRt+a8y3xZQ==} '@types/luxon@3.4.2': resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} @@ -4134,8 +4137,8 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/node@20.16.11': - resolution: {integrity: sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==} + '@types/node@20.16.14': + resolution: {integrity: sha512-vtgGzjxLF7QT88qRHtXMzCWpAAmwonE7fwgVjFtXosUva2oSpnIEc3gNO9P7uIfOxKnii2f79/xtOnfreYtDaA==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -4311,19 +4314,19 @@ packages: peerDependencies: vite: ^4 || ^5 - '@vitejs/plugin-react@4.3.2': - resolution: {integrity: sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg==} + '@vitejs/plugin-react@4.3.3': + resolution: {integrity: sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 - '@vitest/expect@2.1.2': - resolution: {integrity: sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==} + '@vitest/expect@2.1.3': + resolution: {integrity: sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==} - '@vitest/mocker@2.1.2': - resolution: {integrity: sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==} + '@vitest/mocker@2.1.3': + resolution: {integrity: sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==} peerDependencies: - '@vitest/spy': 2.1.2 + '@vitest/spy': 2.1.3 msw: ^2.3.5 vite: ^5.0.0 peerDependenciesMeta: @@ -4332,25 +4335,25 @@ packages: vite: optional: true - '@vitest/pretty-format@2.1.2': - resolution: {integrity: sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==} + '@vitest/pretty-format@2.1.3': + resolution: {integrity: sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==} - '@vitest/runner@2.1.2': - resolution: {integrity: sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==} + '@vitest/runner@2.1.3': + resolution: {integrity: sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==} - '@vitest/snapshot@2.1.2': - resolution: {integrity: sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==} + '@vitest/snapshot@2.1.3': + resolution: {integrity: sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==} - '@vitest/spy@2.1.2': - resolution: {integrity: sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==} + '@vitest/spy@2.1.3': + resolution: {integrity: sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==} - '@vitest/ui@2.1.2': - resolution: {integrity: sha512-92gcNzkDnmxOxyHzQrQYRsoV9Q0Aay0r4QMLnV+B+lbqlUWa8nDg9ivyLV5mMVTtGirHsYUGGh/zbIA55gBZqA==} + '@vitest/ui@2.1.3': + resolution: {integrity: sha512-2XwTrHVJw3t9NYES26LQUYy51ZB8W4bRPgqUH2Eyda3kIuOlYw1ZdPNU22qcVlUVx4WKgECFQOSXuopsczuVjQ==} peerDependencies: - vitest: 2.1.2 + vitest: 2.1.3 - '@vitest/utils@2.1.2': - resolution: {integrity: sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==} + '@vitest/utils@2.1.3': + resolution: {integrity: sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==} '@webassemblyjs/ast@1.12.1': resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} @@ -4609,12 +4612,13 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - aria-query@5.1.3: - resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} - aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + array-buffer-byte-length@1.0.1: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} @@ -4728,8 +4732,8 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axe-core@4.10.0: - resolution: {integrity: sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==} + axe-core@4.10.1: + resolution: {integrity: sha512-qPC9o+kD8Tir0lzNGLeghbOrWMr3ZJpaRlCIb6Uobt/7N4FiEDvqUMnxzCHRHmg8vOg14kr5gVNyScRmbMaJ9g==} engines: {node: '>=4'} axios@1.7.5: @@ -5550,10 +5554,6 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} - deep-equal@2.2.3: - resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} - engines: {node: '>= 0.4'} - deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -5792,8 +5792,8 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - es-iterator-helpers@1.0.19: - resolution: {integrity: sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==} + es-iterator-helpers@1.1.0: + resolution: {integrity: sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw==} engines: {node: '>= 0.4'} es-module-lexer@1.5.4: @@ -5927,14 +5927,14 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-jsdoc@50.4.1: - resolution: {integrity: sha512-OXIq+JJQPCLAKL473/esioFOwbXyRE5MAQ4HbZjcp3e+K3zdxt2uDpGs3FR+WezUXNStzEtTfgx15T+JFrVwBA==} + eslint-plugin-jsdoc@50.4.3: + resolution: {integrity: sha512-uWtwFxGRv6B8sU63HZM5dAGDhgsatb+LONwmILZJhdRALLOkCX2HFZhdL/Kw2ls8SQMAVEfK+LmnEfxInRN8HA==} engines: {node: '>=18'} peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - eslint-plugin-jsx-a11y@6.10.0: - resolution: {integrity: sha512-ySOHvXX8eSN6zz8Bywacm7CvGNhUtdjvqfQDVe6020TUK34Cywkw7m0KsCCk1Qtm9G1FayfTN1/7mMYnYO2Bhg==} + eslint-plugin-jsx-a11y@6.10.1: + resolution: {integrity: sha512-zHByM9WTUMnfsDTafGXRiqxp6lFtNoSOWBY6FonVRn3A+BUwN1L/tdBXT40BcBJi0cZjOGTXZ0eD/rTG9fEJ0g==} engines: {node: '>=4.0'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 @@ -5977,11 +5977,11 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - eslint-plugin-testing-library@6.3.0: - resolution: {integrity: sha512-GYcEErTt6EGwE0bPDY+4aehfEBpB2gDBFKohir8jlATSUvzStEyzCx8QWB/14xeKc/AwyXkzScSzMHnFojkWrA==} + eslint-plugin-testing-library@6.4.0: + resolution: {integrity: sha512-yeWF+YgCgvNyPNI9UKnG0FjeE2sk93N/3lsKqcmR8dSfeXJwFT5irnWo7NjLf152HkRzfoFjh3LsBUrhvFz4eA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} peerDependencies: - eslint: ^7.5.0 || ^8.0.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} @@ -6093,8 +6093,8 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - execa@9.4.0: - resolution: {integrity: sha512-yKHlle2YGxZE842MERVIplWwNH5VYmqqcPFgtnlU//K8gxuFFXu0pwd/CrfXTumFpeEiufsP7+opT/bPJa1yVw==} + execa@9.4.1: + resolution: {integrity: sha512-5eo/BRqZm3GYce+1jqX/tJ7duA2AnE39i88fuedNFUV8XxGxUpF3aWkBRfbUcjV49gCkvS/pzc0YrCPhaIewdg==} engines: {node: ^18.19.0 || >=20.5.0} expand-tilde@2.0.2: @@ -6142,9 +6142,6 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-url-parser@1.1.3: - resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} - fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} @@ -6485,8 +6482,8 @@ packages: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} - globalthis@1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} globby@11.1.0: @@ -6628,8 +6625,8 @@ packages: resolution: {integrity: sha512-QY6S+hZ0f5m1WT8WffYN+Hg+xm/w5I8XeUcAq/ZYP5wVC8xbKi4Whhru3FtrAebD5EhBW8rmFzkDI6eCAuFe2w==} hasBin: true - html-webpack-plugin@5.6.0: - resolution: {integrity: sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==} + html-webpack-plugin@5.6.2: + resolution: {integrity: sha512-q7xp/FO9RGBVoTKNItkdX1jKLscLFkgn/dLVFNYbHVbfHLBk6DYW5nsQ8kCzIWcgKP/kUBocetjvav6lD8YfCQ==} engines: {node: '>=10.13.0'} peerDependencies: '@rspack/core': 0.x || 1.x @@ -7049,8 +7046,9 @@ packages: iterate-value@1.0.2: resolution: {integrity: sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==} - iterator.prototype@1.1.2: - resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} + iterator.prototype@1.1.3: + resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==} + engines: {node: '>= 0.4'} jackspeak@2.3.6: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} @@ -8060,10 +8058,6 @@ packages: object-inspect@1.13.1: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} - object-is@1.1.5: - resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} - engines: {node: '>= 0.4'} - object-keys@0.4.0: resolution: {integrity: sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==} @@ -8373,8 +8367,8 @@ packages: path-to-regexp@0.1.10: resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} - path-to-regexp@2.2.1: - resolution: {integrity: sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==} + path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} path-to-regexp@6.2.1: resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} @@ -8621,9 +8615,6 @@ packages: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} - punycode@1.4.1: - resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -8678,8 +8669,8 @@ packages: peerDependencies: react: ^18.3.1 - react-hook-form@7.53.0: - resolution: {integrity: sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==} + react-hook-form@7.53.1: + resolution: {integrity: sha512-6aiQeBda4zjcuaugWvim9WsGqisoUk+etmFEsSUMm451/Ic8L/UAb7sRtMj3V+Hdzm6mMjU1VhiSzYUZeBm0Vg==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 @@ -8922,6 +8913,11 @@ packages: rfdc@1.3.1: resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} + rifm@0.12.1: + resolution: {integrity: sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg==} + peerDependencies: + react: '>=16.8' + rimraf@2.6.3: resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -9038,15 +9034,15 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - serve-handler@6.1.5: - resolution: {integrity: sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==} + serve-handler@6.1.6: + resolution: {integrity: sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==} serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} - serve@14.2.3: - resolution: {integrity: sha512-VqUFMC7K3LDGeGnJM9h56D3XGKb6KGgOw0cVNtA26yYXHCcpxf3xwCTUaQoWlVS7i8Jdh3GjQkOB23qsXyjoyQ==} + serve@14.2.4: + resolution: {integrity: sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==} engines: {node: '>= 14'} hasBin: true @@ -9256,8 +9252,9 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string.prototype.includes@2.0.0: - resolution: {integrity: sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==} + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} string.prototype.matchall@4.0.11: resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} @@ -9842,8 +9839,8 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-node@2.1.2: - resolution: {integrity: sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==} + vite-node@2.1.3: + resolution: {integrity: sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -9852,7 +9849,7 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: - '@types/node': ^20.16.11 + '@types/node': ^20.16.14 less: '*' lightningcss: ^1.21.0 sass: '*' @@ -9875,15 +9872,15 @@ packages: terser: optional: true - vitest@2.1.2: - resolution: {integrity: sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==} + vitest@2.1.3: + resolution: {integrity: sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/node': ^20.16.11 - '@vitest/browser': 2.1.2 - '@vitest/ui': 2.1.2 + '@types/node': ^20.16.14 + '@vitest/browser': 2.1.3 + '@vitest/ui': 2.1.3 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -11185,11 +11182,11 @@ snapshots: transitivePeerDependencies: - debug - '@codspeed/vitest-plugin@3.1.1(vite@5.3.4(@types/node@20.16.11)(terser@5.27.0))(vitest@2.1.2)': + '@codspeed/vitest-plugin@3.1.1(vite@5.3.4(@types/node@20.16.14)(terser@5.27.0))(vitest@2.1.3)': dependencies: '@codspeed/core': 3.1.1 - vite: 5.3.4(@types/node@20.16.11)(terser@5.27.0) - vitest: 2.1.2(@types/node@20.16.11)(@vitest/ui@2.1.2)(jsdom@24.1.3)(terser@5.27.0) + vite: 5.3.4(@types/node@20.16.14)(terser@5.27.0) + vitest: 2.1.3(@types/node@20.16.14)(@vitest/ui@2.1.3)(jsdom@24.1.3)(terser@5.27.0) transitivePeerDependencies: - debug @@ -11488,7 +11485,7 @@ snapshots: '@fast-csv/format@4.3.5': dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 lodash.escaperegexp: 4.1.2 lodash.isboolean: 3.0.3 lodash.isequal: 4.5.0 @@ -11497,7 +11494,7 @@ snapshots: '@fast-csv/parse@4.3.6': dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 lodash.escaperegexp: 4.1.2 lodash.groupby: 4.6.0 lodash.isfunction: 3.0.9 @@ -11940,12 +11937,12 @@ snapshots: '@emotion/styled': 11.13.0(@emotion/react@11.13.3(@types/react@18.3.4)(react@18.3.1))(@types/react@18.3.4)(react@18.3.1) '@types/react': 18.3.4 - '@mui/monorepo@https://codeload.github.com/mui/material-ui/tar.gz/010de4505361345951824d905d1508d6f258ba67(encoding@0.1.13)': + '@mui/monorepo@https://codeload.github.com/mui/material-ui/tar.gz/ca637077e091b7f1f6e591ded5a453edcc784204(encoding@0.1.13)': dependencies: '@googleapis/sheets': 9.3.1(encoding@0.1.13) '@netlify/functions': 2.8.2 '@slack/bolt': 3.22.0 - execa: 9.4.0 + execa: 9.4.1 google-auth-library: 9.14.2(encoding@0.1.13) transitivePeerDependencies: - bufferutil @@ -12736,18 +12733,18 @@ snapshots: '@slack/logger@3.0.0': dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@slack/logger@4.0.0': dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@slack/oauth@2.6.3': dependencies: '@slack/logger': 3.0.0 '@slack/web-api': 6.13.0 '@types/jsonwebtoken': 8.5.9 - '@types/node': 20.16.11 + '@types/node': 20.16.14 jsonwebtoken: 9.0.2 lodash.isstring: 4.0.1 transitivePeerDependencies: @@ -12757,7 +12754,7 @@ snapshots: dependencies: '@slack/logger': 3.0.0 '@slack/web-api': 6.13.0 - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@types/ws': 7.4.7 eventemitter3: 5.0.1 finity: 0.5.4 @@ -12774,7 +12771,7 @@ snapshots: '@slack/logger': 3.0.0 '@slack/types': 2.13.0 '@types/is-stream': 1.1.0 - '@types/node': 20.16.11 + '@types/node': 20.16.14 axios: 1.7.5(debug@4.3.6) eventemitter3: 3.1.2 form-data: 2.5.1 @@ -12858,10 +12855,10 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.5.0': + '@testing-library/jest-dom@6.6.2': dependencies: '@adobe/css-tools': 4.4.0 - aria-query: 5.3.0 + aria-query: 5.3.2 chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 @@ -12917,7 +12914,7 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@types/chai-dom@1.11.3': dependencies: @@ -12929,13 +12926,13 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@types/cookie@0.4.1': {} '@types/cors@2.8.17': dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@types/d3-array@3.2.1': {} @@ -12980,7 +12977,7 @@ snapshots: '@types/express-serve-static-core@4.17.42': dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@types/qs': 6.9.11 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -12997,7 +12994,7 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@types/gtag.js@0.0.20': {} @@ -13009,7 +13006,7 @@ snapshots: '@types/is-stream@1.1.0': dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@types/istanbul-lib-coverage@2.0.6': {} @@ -13024,20 +13021,20 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@types/jsonwebtoken@8.5.9': dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@types/karma@6.3.8': dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 log4js: 6.9.1 transitivePeerDependencies: - supports-color - '@types/lodash@4.17.10': {} + '@types/lodash@4.17.12': {} '@types/luxon@3.4.2': {} @@ -13065,7 +13062,7 @@ snapshots: '@types/ms@0.7.34': {} - '@types/node@20.16.11': + '@types/node@20.16.14': dependencies: undici-types: 6.19.8 @@ -13118,13 +13115,13 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@types/serve-static@1.15.5': dependencies: '@types/http-errors': 2.0.4 '@types/mime': 3.0.4 - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@types/sinon@17.0.3': dependencies: @@ -13142,7 +13139,7 @@ snapshots: '@types/webpack-bundle-analyzer@4.7.0(@swc/core@1.7.35(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.95.0))': dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 tapable: 2.2.1 webpack: 5.95.0(@swc/core@1.7.35(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack@5.95.0)) transitivePeerDependencies: @@ -13153,7 +13150,7 @@ snapshots: '@types/ws@7.4.7': dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 '@types/yargs-parser@21.0.3': {} @@ -13285,72 +13282,72 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react-swc@3.7.1(@swc/helpers@0.5.5)(vite@5.3.4(@types/node@20.16.11)(terser@5.27.0))': + '@vitejs/plugin-react-swc@3.7.1(@swc/helpers@0.5.5)(vite@5.3.4(@types/node@20.16.14)(terser@5.27.0))': dependencies: '@swc/core': 1.7.35(@swc/helpers@0.5.5) - vite: 5.3.4(@types/node@20.16.11)(terser@5.27.0) + vite: 5.3.4(@types/node@20.16.14)(terser@5.27.0) transitivePeerDependencies: - '@swc/helpers' - '@vitejs/plugin-react@4.3.2(vite@5.3.4(@types/node@20.16.11)(terser@5.27.0))': + '@vitejs/plugin-react@4.3.3(vite@5.3.4(@types/node@20.16.14)(terser@5.27.0))': dependencies: '@babel/core': 7.25.8 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.8) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.8) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.3.4(@types/node@20.16.11)(terser@5.27.0) + vite: 5.3.4(@types/node@20.16.14)(terser@5.27.0) transitivePeerDependencies: - supports-color - '@vitest/expect@2.1.2': + '@vitest/expect@2.1.3': dependencies: - '@vitest/spy': 2.1.2 - '@vitest/utils': 2.1.2 + '@vitest/spy': 2.1.3 + '@vitest/utils': 2.1.3 chai: 5.1.1 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(vite@5.3.4(@types/node@20.16.11)(terser@5.27.0))': + '@vitest/mocker@2.1.3(@vitest/spy@2.1.3)(vite@5.3.4(@types/node@20.16.14)(terser@5.27.0))': dependencies: - '@vitest/spy': 2.1.2 + '@vitest/spy': 2.1.3 estree-walker: 3.0.3 magic-string: 0.30.11 optionalDependencies: - vite: 5.3.4(@types/node@20.16.11)(terser@5.27.0) + vite: 5.3.4(@types/node@20.16.14)(terser@5.27.0) - '@vitest/pretty-format@2.1.2': + '@vitest/pretty-format@2.1.3': dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.1.2': + '@vitest/runner@2.1.3': dependencies: - '@vitest/utils': 2.1.2 + '@vitest/utils': 2.1.3 pathe: 1.1.2 - '@vitest/snapshot@2.1.2': + '@vitest/snapshot@2.1.3': dependencies: - '@vitest/pretty-format': 2.1.2 + '@vitest/pretty-format': 2.1.3 magic-string: 0.30.11 pathe: 1.1.2 - '@vitest/spy@2.1.2': + '@vitest/spy@2.1.3': dependencies: tinyspy: 3.0.0 - '@vitest/ui@2.1.2(vitest@2.1.2)': + '@vitest/ui@2.1.3(vitest@2.1.3)': dependencies: - '@vitest/utils': 2.1.2 + '@vitest/utils': 2.1.3 fflate: 0.8.2 flatted: 3.3.1 pathe: 1.1.2 sirv: 2.0.4 tinyglobby: 0.2.6 tinyrainbow: 1.2.0 - vitest: 2.1.2(@types/node@20.16.11)(@vitest/ui@2.1.2)(jsdom@24.1.3)(terser@5.27.0) + vitest: 2.1.3(@types/node@20.16.14)(@vitest/ui@2.1.3)(jsdom@24.1.3)(terser@5.27.0) - '@vitest/utils@2.1.2': + '@vitest/utils@2.1.3': dependencies: - '@vitest/pretty-format': 2.1.2 + '@vitest/pretty-format': 2.1.3 loupe: 3.1.1 tinyrainbow: 1.2.0 @@ -13650,14 +13647,12 @@ snapshots: argparse@2.0.1: {} - aria-query@5.1.3: - dependencies: - deep-equal: 2.2.3 - aria-query@5.3.0: dependencies: dequal: 2.0.3 + aria-query@5.3.2: {} + array-buffer-byte-length@1.0.1: dependencies: call-bind: 1.0.7 @@ -13795,7 +13790,7 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 - axe-core@4.10.0: {} + axe-core@4.10.1: {} axios@1.7.5(debug@4.3.6): dependencies: @@ -14715,27 +14710,6 @@ snapshots: deep-eql@5.0.2: {} - deep-equal@2.2.3: - dependencies: - array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 - es-get-iterator: 1.1.3 - get-intrinsic: 1.2.4 - is-arguments: 1.1.1 - is-array-buffer: 3.0.4 - is-date-object: 1.0.5 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.3 - isarray: 2.0.5 - object-is: 1.1.5 - object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.2 - side-channel: 1.0.6 - which-boxed-primitive: 1.0.2 - which-collection: 1.0.1 - which-typed-array: 1.1.15 - deep-extend@0.6.0: {} deep-is@0.1.4: {} @@ -14900,7 +14874,7 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 20.16.11 + '@types/node': 20.16.14 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 @@ -14961,7 +14935,7 @@ snapshots: function.prototype.name: 1.1.6 get-intrinsic: 1.2.4 get-symbol-description: 1.0.2 - globalthis: 1.0.3 + globalthis: 1.0.4 gopd: 1.0.1 has-property-descriptors: 1.0.2 has-proto: 1.0.3 @@ -15013,7 +14987,7 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.0.0 - es-iterator-helpers@1.0.19: + es-iterator-helpers@1.1.0: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 @@ -15022,12 +14996,12 @@ snapshots: es-set-tostringtag: 2.0.3 function-bind: 1.1.2 get-intrinsic: 1.2.4 - globalthis: 1.0.3 + globalthis: 1.0.4 has-property-descriptors: 1.0.2 has-proto: 1.0.3 has-symbols: 1.0.3 internal-slot: 1.0.7 - iterator.prototype: 1.1.2 + iterator.prototype: 1.1.3 safe-array-concat: 1.1.2 es-module-lexer@1.5.4: {} @@ -15144,12 +15118,12 @@ snapshots: transitivePeerDependencies: - eslint-plugin-import - eslint-config-airbnb@19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1))(eslint-plugin-react-hooks@5.0.0(eslint@8.57.1))(eslint-plugin-react@7.37.1(eslint@8.57.1))(eslint@8.57.1): + eslint-config-airbnb@19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.1(eslint@8.57.1))(eslint-plugin-react-hooks@5.0.0(eslint@8.57.1))(eslint-plugin-react@7.37.1(eslint@8.57.1))(eslint@8.57.1): dependencies: eslint: 8.57.1 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-webpack@0.13.9)(eslint@8.57.1) - eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) + eslint-plugin-jsx-a11y: 6.10.1(eslint@8.57.1) eslint-plugin-react: 7.37.1(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0(eslint@8.57.1) object.assign: 4.1.5 @@ -15232,7 +15206,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsdoc@50.4.1(eslint@8.57.1): + eslint-plugin-jsdoc@50.4.3(eslint@8.57.1): dependencies: '@es-joy/jsdoccomment': 0.49.0 are-docs-informative: 0.0.2 @@ -15249,17 +15223,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-jsx-a11y@6.10.0(eslint@8.57.1): + eslint-plugin-jsx-a11y@6.10.1(eslint@8.57.1): dependencies: - aria-query: 5.1.3 + aria-query: 5.3.2 array-includes: 3.1.8 array.prototype.flatmap: 1.3.2 ast-types-flow: 0.0.8 - axe-core: 4.10.0 + axe-core: 4.10.1 axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - es-iterator-helpers: 1.0.19 + es-iterator-helpers: 1.1.0 eslint: 8.57.1 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -15267,7 +15241,7 @@ snapshots: minimatch: 3.1.2 object.fromentries: 2.0.8 safe-regex-test: 1.0.3 - string.prototype.includes: 2.0.0 + string.prototype.includes: 2.0.1 eslint-plugin-mocha@10.5.0(eslint@8.57.1): dependencies: @@ -15309,7 +15283,7 @@ snapshots: array.prototype.flatmap: 1.3.2 array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 - es-iterator-helpers: 1.0.19 + es-iterator-helpers: 1.1.0 eslint: 8.57.1 estraverse: 5.3.0 hasown: 2.0.2 @@ -15324,7 +15298,7 @@ snapshots: string.prototype.matchall: 4.0.11 string.prototype.repeat: 1.0.0 - eslint-plugin-testing-library@6.3.0(eslint@8.57.1)(typescript@5.6.3): + eslint-plugin-testing-library@6.4.0(eslint@8.57.1)(typescript@5.6.3): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 @@ -15486,7 +15460,7 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - execa@9.4.0: + execa@9.4.1: dependencies: '@sindresorhus/merge-streams': 4.0.0 cross-spawn: 7.0.3 @@ -15578,10 +15552,6 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-url-parser@1.1.3: - dependencies: - punycode: 1.4.1 - fastest-levenshtein@1.0.16: {} fastq@1.17.0: @@ -15965,9 +15935,10 @@ snapshots: dependencies: type-fest: 0.20.2 - globalthis@1.0.3: + globalthis@1.0.4: dependencies: define-properties: 1.2.1 + gopd: 1.0.1 globby@11.1.0: dependencies: @@ -16142,7 +16113,7 @@ snapshots: readable-stream: 1.0.34 through2: 0.4.2 - html-webpack-plugin@5.6.0(webpack@5.95.0): + html-webpack-plugin@5.6.2(webpack@5.95.0): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -16554,7 +16525,7 @@ snapshots: es-get-iterator: 1.1.3 iterate-iterator: 1.0.2 - iterator.prototype@1.1.2: + iterator.prototype@1.1.3: dependencies: define-properties: 1.2.1 get-intrinsic: 1.2.4 @@ -16594,7 +16565,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -17943,11 +17914,6 @@ snapshots: object-inspect@1.13.1: {} - object-is@1.1.5: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - object-keys@0.4.0: {} object-keys@1.1.1: {} @@ -18290,7 +18256,7 @@ snapshots: path-to-regexp@0.1.10: {} - path-to-regexp@2.2.1: {} + path-to-regexp@3.3.0: {} path-to-regexp@6.2.1: {} @@ -18497,8 +18463,6 @@ snapshots: punycode.js@2.3.1: {} - punycode@1.4.1: {} - punycode@2.3.1: {} qjobs@1.2.0: {} @@ -18558,7 +18522,7 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 - react-hook-form@7.53.0(react@18.3.1): + react-hook-form@7.53.1(react@18.3.1): dependencies: react: 18.3.1 @@ -18704,7 +18668,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.3 get-intrinsic: 1.2.4 - globalthis: 1.0.3 + globalthis: 1.0.4 which-builtin-type: 1.1.3 regenerate-unicode-properties@10.2.0: @@ -18839,6 +18803,10 @@ snapshots: rfdc@1.3.1: {} + rifm@0.12.1(react@18.3.1): + dependencies: + react: 18.3.1 + rimraf@2.6.3: dependencies: glob: 7.2.3 @@ -18980,15 +18948,14 @@ snapshots: dependencies: randombytes: 2.1.0 - serve-handler@6.1.5: + serve-handler@6.1.6: dependencies: bytes: 3.0.0 content-disposition: 0.5.2 - fast-url-parser: 1.1.3 mime-types: 2.1.18 minimatch: 3.1.2 path-is-inside: 1.0.2 - path-to-regexp: 2.2.1 + path-to-regexp: 3.3.0 range-parser: 1.2.0 serve-static@1.16.2: @@ -19000,7 +18967,7 @@ snapshots: transitivePeerDependencies: - supports-color - serve@14.2.3: + serve@14.2.4: dependencies: '@zeit/schemas': 2.36.0 ajv: 8.12.0 @@ -19011,7 +18978,7 @@ snapshots: clipboardy: 3.0.0 compression: 1.7.4 is-port-reachable: 4.0.0 - serve-handler: 6.1.5 + serve-handler: 6.1.6 update-check: 1.5.4 transitivePeerDependencies: - supports-color @@ -19281,8 +19248,9 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 - string.prototype.includes@2.0.0: + string.prototype.includes@2.0.1: dependencies: + call-bind: 1.0.7 define-properties: 1.2.1 es-abstract: 1.23.3 @@ -19864,12 +19832,12 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-node@2.1.2(@types/node@20.16.11)(terser@5.27.0): + vite-node@2.1.3(@types/node@20.16.14)(terser@5.27.0): dependencies: cac: 6.7.14 debug: 4.3.6(supports-color@8.1.1) pathe: 1.1.2 - vite: 5.3.4(@types/node@20.16.11)(terser@5.27.0) + vite: 5.3.4(@types/node@20.16.14)(terser@5.27.0) transitivePeerDependencies: - '@types/node' - less @@ -19880,25 +19848,25 @@ snapshots: - supports-color - terser - vite@5.3.4(@types/node@20.16.11)(terser@5.27.0): + vite@5.3.4(@types/node@20.16.14)(terser@5.27.0): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.18.1 optionalDependencies: - '@types/node': 20.16.11 + '@types/node': 20.16.14 fsevents: 2.3.3 terser: 5.27.0 - vitest@2.1.2(@types/node@20.16.11)(@vitest/ui@2.1.2)(jsdom@24.1.3)(terser@5.27.0): + vitest@2.1.3(@types/node@20.16.14)(@vitest/ui@2.1.3)(jsdom@24.1.3)(terser@5.27.0): dependencies: - '@vitest/expect': 2.1.2 - '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(vite@5.3.4(@types/node@20.16.11)(terser@5.27.0)) - '@vitest/pretty-format': 2.1.2 - '@vitest/runner': 2.1.2 - '@vitest/snapshot': 2.1.2 - '@vitest/spy': 2.1.2 - '@vitest/utils': 2.1.2 + '@vitest/expect': 2.1.3 + '@vitest/mocker': 2.1.3(@vitest/spy@2.1.3)(vite@5.3.4(@types/node@20.16.14)(terser@5.27.0)) + '@vitest/pretty-format': 2.1.3 + '@vitest/runner': 2.1.3 + '@vitest/snapshot': 2.1.3 + '@vitest/spy': 2.1.3 + '@vitest/utils': 2.1.3 chai: 5.1.1 debug: 4.3.6(supports-color@8.1.1) magic-string: 0.30.11 @@ -19908,12 +19876,12 @@ snapshots: tinyexec: 0.3.0 tinypool: 1.0.0 tinyrainbow: 1.2.0 - vite: 5.3.4(@types/node@20.16.11)(terser@5.27.0) - vite-node: 2.1.2(@types/node@20.16.11)(terser@5.27.0) + vite: 5.3.4(@types/node@20.16.14)(terser@5.27.0) + vite-node: 2.1.3(@types/node@20.16.14)(terser@5.27.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.16.11 - '@vitest/ui': 2.1.2(vitest@2.1.2) + '@types/node': 20.16.14 + '@vitest/ui': 2.1.3(vitest@2.1.3) jsdom: 24.1.3 transitivePeerDependencies: - less diff --git a/scripts/buildApiDocs/chartsSettings/index.ts b/scripts/buildApiDocs/chartsSettings/index.ts index 90479273d2a4..30c812c5182d 100644 --- a/scripts/buildApiDocs/chartsSettings/index.ts +++ b/scripts/buildApiDocs/chartsSettings/index.ts @@ -9,7 +9,7 @@ type PageType = { pathname: string; title: string; plan?: 'community' | 'pro' | export const projectChartsSettings: ProjectSettings = { output: { - apiManifestPath: path.join(process.cwd(), 'docs/data/charts-component-api-pages.ts'), + apiManifestPath: path.join(process.cwd(), 'docs/data/chartsApiPages.ts'), }, onWritingManifestFile: ( builds: PromiseSettledResult[], @@ -33,8 +33,8 @@ export const projectChartsSettings: ProjectSettings = { return `import type { MuiPage } from 'docs/src/MuiPage'; -const apiPages: MuiPage[] = ${JSON.stringify(pages, null, 2)}; -export default apiPages; +const chartsApiPages: MuiPage[] = ${JSON.stringify(pages, null, 2)}; +export default chartsApiPages; `; }, typeScriptProjects: [ diff --git a/scripts/buildApiDocs/gridSettings/index.ts b/scripts/buildApiDocs/gridSettings/index.ts index b5890318ade6..2da34675345f 100644 --- a/scripts/buildApiDocs/gridSettings/index.ts +++ b/scripts/buildApiDocs/gridSettings/index.ts @@ -9,7 +9,7 @@ type PageType = { pathname: string; title: string; plan?: 'community' | 'pro' | export const projectGridSettings: ProjectSettings = { output: { - apiManifestPath: path.join(process.cwd(), 'docs/data/data-grid-component-api-pages.ts'), + apiManifestPath: path.join(process.cwd(), 'docs/data/dataGridApiPages.ts'), }, onWritingManifestFile: ( builds: PromiseSettledResult[], @@ -33,8 +33,8 @@ export const projectGridSettings: ProjectSettings = { return `import type { MuiPage } from 'docs/src/MuiPage'; -const apiPages: MuiPage[] = ${JSON.stringify(pages, null, 2)}; -export default apiPages; +const dataGridApiPages: MuiPage[] = ${JSON.stringify(pages, null, 2)}; +export default dataGridApiPages; `; }, typeScriptProjects: [ diff --git a/scripts/buildApiDocs/pickersSettings/index.ts b/scripts/buildApiDocs/pickersSettings/index.ts index e34ef1e9057e..f40aa1ca068b 100644 --- a/scripts/buildApiDocs/pickersSettings/index.ts +++ b/scripts/buildApiDocs/pickersSettings/index.ts @@ -9,7 +9,7 @@ type PageType = { pathname: string; title: string; plan?: 'community' | 'pro' | export const projectPickersSettings: ProjectSettings = { output: { - apiManifestPath: path.join(process.cwd(), 'docs/data/date-pickers-component-api-pages.ts'), + apiManifestPath: path.join(process.cwd(), 'docs/data/datePickersApiPages.ts'), }, onWritingManifestFile: ( builds: PromiseSettledResult[], @@ -33,8 +33,8 @@ export const projectPickersSettings: ProjectSettings = { return `import type { MuiPage } from 'docs/src/MuiPage'; -const apiPages: MuiPage[] = ${JSON.stringify(pages, null, 2)}; -export default apiPages; +const datePickersApiPages: MuiPage[] = ${JSON.stringify(pages, null, 2)}; +export default datePickersApiPages; `; }, typeScriptProjects: [ diff --git a/scripts/buildApiDocs/treeViewSettings/index.ts b/scripts/buildApiDocs/treeViewSettings/index.ts index d0202935db0e..d2dda4d0879f 100644 --- a/scripts/buildApiDocs/treeViewSettings/index.ts +++ b/scripts/buildApiDocs/treeViewSettings/index.ts @@ -9,7 +9,7 @@ type PageType = { pathname: string; title: string; plan?: 'community' | 'pro' | export const projectTreeSettings: ProjectSettings = { output: { - apiManifestPath: path.join(process.cwd(), 'docs/data/tree-view-component-api-pages.ts'), + apiManifestPath: path.join(process.cwd(), 'docs/data/treeViewApiPages.ts'), }, onWritingManifestFile: ( builds: PromiseSettledResult[], @@ -33,8 +33,8 @@ export const projectTreeSettings: ProjectSettings = { return `import type { MuiPage } from 'docs/src/MuiPage'; -const apiPages: MuiPage[] = ${JSON.stringify(pages, null, 2)}; -export default apiPages; +const treeViewApiPages: MuiPage[] = ${JSON.stringify(pages, null, 2)}; +export default treeViewApiPages; `; }, typeScriptProjects: [ diff --git a/scripts/useMaterialUIv6.mjs b/scripts/useMaterialUIv6.mjs index d6d0c4b4966a..07f2aed4e3ab 100644 --- a/scripts/useMaterialUIv6.mjs +++ b/scripts/useMaterialUIv6.mjs @@ -2,7 +2,14 @@ import childProcess from 'child_process'; const pnpmUpdate = childProcess.spawnSync( 'pnpm', - ['update', '-r', '@mui/material@6.x', '@mui/system@6.x', '@mui/icons-material@6.x'], + [ + 'update', + '-r', + '@mui/material@6.x', + '@mui/system@6.x', + '@mui/icons-material@6.x', + '@mui/utils@6.x', + ], { shell: true, stdio: ['inherit', 'inherit', 'inherit'], diff --git a/scripts/x-charts-pro.exports.json b/scripts/x-charts-pro.exports.json index 22db3a452177..28020ea97fb9 100644 --- a/scripts/x-charts-pro.exports.json +++ b/scripts/x-charts-pro.exports.json @@ -61,6 +61,7 @@ { "name": "ChartsAxisClasses", "kind": "Interface" }, { "name": "ChartsAxisClassKey", "kind": "TypeAlias" }, { "name": "ChartsAxisContentProps", "kind": "TypeAlias" }, + { "name": "ChartsAxisData", "kind": "TypeAlias" }, { "name": "ChartsAxisHighlight", "kind": "Function" }, { "name": "chartsAxisHighlightClasses", "kind": "Variable" }, { "name": "ChartsAxisHighlightClasses", "kind": "Interface" }, diff --git a/scripts/x-charts.exports.json b/scripts/x-charts.exports.json index e95e714b9698..312af2248e5a 100644 --- a/scripts/x-charts.exports.json +++ b/scripts/x-charts.exports.json @@ -59,6 +59,7 @@ { "name": "ChartsAxisClasses", "kind": "Interface" }, { "name": "ChartsAxisClassKey", "kind": "TypeAlias" }, { "name": "ChartsAxisContentProps", "kind": "TypeAlias" }, + { "name": "ChartsAxisData", "kind": "TypeAlias" }, { "name": "ChartsAxisHighlight", "kind": "Function" }, { "name": "chartsAxisHighlightClasses", "kind": "Variable" }, { "name": "ChartsAxisHighlightClasses", "kind": "Interface" }, diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 5c536da99e51..8a0a612168f7 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -1,11 +1,15 @@ [ + { "name": "BaseBadgePropsOverrides", "kind": "Interface" }, { "name": "BaseButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseCheckboxPropsOverrides", "kind": "Interface" }, { "name": "BaseChipPropsOverrides", "kind": "Interface" }, + { "name": "BaseDividerPropsOverrides", "kind": "Interface" }, { "name": "BaseFormControlPropsOverrides", "kind": "Interface" }, { "name": "BaseIconButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseInputAdornmentPropsOverrides", "kind": "Interface" }, { "name": "BaseInputLabelPropsOverrides", "kind": "Interface" }, + { "name": "BaseMenuItemPropsOverrides", "kind": "Interface" }, + { "name": "BaseMenuListPropsOverrides", "kind": "Interface" }, { "name": "BasePopperPropsOverrides", "kind": "Interface" }, { "name": "BaseSelectOptionPropsOverrides", "kind": "Interface" }, { "name": "BaseSelectPropsOverrides", "kind": "Interface" }, @@ -70,15 +74,15 @@ { "name": "GRID_DATETIME_COL_DEF", "kind": "Variable" }, { "name": "GRID_DEFAULT_LOCALE_TEXT", "kind": "Variable" }, { "name": "GRID_DETAIL_PANEL_TOGGLE_COL_DEF", "kind": "Variable" }, - { "name": "GRID_DETAIL_PANEL_TOGGLE_FIELD", "kind": "Variable" }, + { "name": "GRID_DETAIL_PANEL_TOGGLE_FIELD", "kind": "ImportSpecifier" }, { "name": "GRID_EXPERIMENTAL_ENABLED", "kind": "Variable" }, { "name": "GRID_NUMERIC_COL_DEF", "kind": "Variable" }, { "name": "GRID_REORDER_COL_DEF", "kind": "Variable" }, { "name": "GRID_ROOT_GROUP_ID", "kind": "Variable" }, - { "name": "GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD", "kind": "Variable" }, + { "name": "GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD", "kind": "ImportSpecifier" }, { "name": "GRID_SINGLE_SELECT_COL_DEF", "kind": "Variable" }, { "name": "GRID_STRING_COL_DEF", "kind": "Variable" }, - { "name": "GRID_TREE_DATA_GROUPING_FIELD", "kind": "Variable" }, + { "name": "GRID_TREE_DATA_GROUPING_FIELD", "kind": "ImportSpecifier" }, { "name": "GridActionsCell", "kind": "Function" }, { "name": "GridActionsCellItem", "kind": "Variable" }, { "name": "GridActionsCellItemProps", "kind": "TypeAlias" }, @@ -634,7 +638,7 @@ { "name": "gridVisibleRowsLookupSelector", "kind": "Variable" }, { "name": "GridWorkspacesIcon", "kind": "Variable" }, { "name": "isAutogeneratedRow", "kind": "Variable" }, - { "name": "isGroupingColumn", "kind": "Variable" }, + { "name": "isGroupingColumn", "kind": "ImportSpecifier" }, { "name": "isLeaf", "kind": "Function" }, { "name": "LicenseInfo", "kind": "Class" }, { "name": "LoadingOverlayPropsOverrides", "kind": "Interface" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index dcb194f6eac7..20dd708a55fd 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -1,11 +1,15 @@ [ + { "name": "BaseBadgePropsOverrides", "kind": "Interface" }, { "name": "BaseButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseCheckboxPropsOverrides", "kind": "Interface" }, { "name": "BaseChipPropsOverrides", "kind": "Interface" }, + { "name": "BaseDividerPropsOverrides", "kind": "Interface" }, { "name": "BaseFormControlPropsOverrides", "kind": "Interface" }, { "name": "BaseIconButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseInputAdornmentPropsOverrides", "kind": "Interface" }, { "name": "BaseInputLabelPropsOverrides", "kind": "Interface" }, + { "name": "BaseMenuItemPropsOverrides", "kind": "Interface" }, + { "name": "BaseMenuListPropsOverrides", "kind": "Interface" }, { "name": "BasePopperPropsOverrides", "kind": "Interface" }, { "name": "BaseSelectOptionPropsOverrides", "kind": "Interface" }, { "name": "BaseSelectPropsOverrides", "kind": "Interface" }, @@ -65,14 +69,14 @@ { "name": "GRID_DATETIME_COL_DEF", "kind": "Variable" }, { "name": "GRID_DEFAULT_LOCALE_TEXT", "kind": "Variable" }, { "name": "GRID_DETAIL_PANEL_TOGGLE_COL_DEF", "kind": "Variable" }, - { "name": "GRID_DETAIL_PANEL_TOGGLE_FIELD", "kind": "Variable" }, + { "name": "GRID_DETAIL_PANEL_TOGGLE_FIELD", "kind": "ImportSpecifier" }, { "name": "GRID_EXPERIMENTAL_ENABLED", "kind": "Variable" }, { "name": "GRID_NUMERIC_COL_DEF", "kind": "Variable" }, { "name": "GRID_REORDER_COL_DEF", "kind": "Variable" }, { "name": "GRID_ROOT_GROUP_ID", "kind": "Variable" }, { "name": "GRID_SINGLE_SELECT_COL_DEF", "kind": "Variable" }, { "name": "GRID_STRING_COL_DEF", "kind": "Variable" }, - { "name": "GRID_TREE_DATA_GROUPING_FIELD", "kind": "Variable" }, + { "name": "GRID_TREE_DATA_GROUPING_FIELD", "kind": "ImportSpecifier" }, { "name": "GridActionsCell", "kind": "Function" }, { "name": "GridActionsCellItem", "kind": "Variable" }, { "name": "GridActionsCellItemProps", "kind": "TypeAlias" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index cc85a19301e4..14665ebf14a0 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -1,11 +1,15 @@ [ + { "name": "BaseBadgePropsOverrides", "kind": "Interface" }, { "name": "BaseButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseCheckboxPropsOverrides", "kind": "Interface" }, { "name": "BaseChipPropsOverrides", "kind": "Interface" }, + { "name": "BaseDividerPropsOverrides", "kind": "Interface" }, { "name": "BaseFormControlPropsOverrides", "kind": "Interface" }, { "name": "BaseIconButtonPropsOverrides", "kind": "Interface" }, { "name": "BaseInputAdornmentPropsOverrides", "kind": "Interface" }, { "name": "BaseInputLabelPropsOverrides", "kind": "Interface" }, + { "name": "BaseMenuItemPropsOverrides", "kind": "Interface" }, + { "name": "BaseMenuListPropsOverrides", "kind": "Interface" }, { "name": "BasePopperPropsOverrides", "kind": "Interface" }, { "name": "BaseSelectOptionPropsOverrides", "kind": "Interface" }, { "name": "BaseSelectPropsOverrides", "kind": "Interface" }, diff --git a/scripts/x-date-pickers-pro.exports.json b/scripts/x-date-pickers-pro.exports.json index 7bce2723c04e..01c128f4ce6c 100644 --- a/scripts/x-date-pickers-pro.exports.json +++ b/scripts/x-date-pickers-pro.exports.json @@ -409,10 +409,8 @@ { "name": "UseClearableFieldResponse", "kind": "TypeAlias" }, { "name": "UseClearableFieldSlotProps", "kind": "Interface" }, { "name": "UseClearableFieldSlots", "kind": "Interface" }, - { "name": "UseDateFieldComponentProps", "kind": "TypeAlias" }, { "name": "UseDateFieldProps", "kind": "Interface" }, { "name": "UseDateRangeFieldProps", "kind": "Interface" }, - { "name": "UseDateTimeFieldComponentProps", "kind": "TypeAlias" }, { "name": "UseDateTimeFieldProps", "kind": "Interface" }, { "name": "UseMultiInputDateRangeFieldComponentProps", "kind": "TypeAlias" }, { "name": "UseMultiInputDateRangeFieldProps", "kind": "Interface" }, @@ -428,7 +426,6 @@ { "name": "UseSingleInputDateTimeRangeFieldProps", "kind": "Interface" }, { "name": "UseSingleInputTimeRangeFieldProps", "kind": "Interface" }, { "name": "useSplitFieldProps", "kind": "Variable" }, - { "name": "UseTimeFieldComponentProps", "kind": "TypeAlias" }, { "name": "UseTimeFieldProps", "kind": "Interface" }, { "name": "useValidation", "kind": "Function" }, { "name": "validateDate", "kind": "Variable" }, diff --git a/scripts/x-date-pickers.exports.json b/scripts/x-date-pickers.exports.json index df1cacc6fcfe..fb6dbbf52091 100644 --- a/scripts/x-date-pickers.exports.json +++ b/scripts/x-date-pickers.exports.json @@ -301,16 +301,13 @@ { "name": "UseClearableFieldResponse", "kind": "TypeAlias" }, { "name": "UseClearableFieldSlotProps", "kind": "Interface" }, { "name": "UseClearableFieldSlots", "kind": "Interface" }, - { "name": "UseDateFieldComponentProps", "kind": "TypeAlias" }, { "name": "UseDateFieldProps", "kind": "Interface" }, - { "name": "UseDateTimeFieldComponentProps", "kind": "TypeAlias" }, { "name": "UseDateTimeFieldProps", "kind": "Interface" }, { "name": "useParsedFormat", "kind": "Variable" }, { "name": "usePickerLayout", "kind": "ExportAssignment" }, { "name": "usePickersContext", "kind": "Variable" }, { "name": "usePickersTranslations", "kind": "Variable" }, { "name": "useSplitFieldProps", "kind": "Variable" }, - { "name": "UseTimeFieldComponentProps", "kind": "TypeAlias" }, { "name": "UseTimeFieldProps", "kind": "Interface" }, { "name": "useValidation", "kind": "Function" }, { "name": "validateDate", "kind": "Variable" }, diff --git a/scripts/x-tree-view-pro.exports.json b/scripts/x-tree-view-pro.exports.json index aa48d50761f1..1a133d166da0 100644 --- a/scripts/x-tree-view-pro.exports.json +++ b/scripts/x-tree-view-pro.exports.json @@ -24,33 +24,32 @@ { "name": "SimpleTreeViewSlots", "kind": "Interface" }, { "name": "SingleSelectTreeViewProps", "kind": "TypeAlias" }, { "name": "TreeItem", "kind": "Variable" }, - { "name": "TreeItem2", "kind": "Variable" }, - { "name": "TreeItem2Checkbox", "kind": "Variable" }, - { "name": "TreeItem2Content", "kind": "Variable" }, - { "name": "TreeItem2GroupTransition", "kind": "Variable" }, - { "name": "TreeItem2Icon", "kind": "Function" }, - { "name": "TreeItem2IconContainer", "kind": "Variable" }, - { "name": "TreeItem2IconProps", "kind": "Interface" }, - { "name": "TreeItem2IconSlotProps", "kind": "Interface" }, - { "name": "TreeItem2IconSlots", "kind": "Interface" }, - { "name": "TreeItem2Label", "kind": "Variable" }, - { "name": "TreeItem2Props", "kind": "Interface" }, - { "name": "TreeItem2Provider", "kind": "Function" }, - { "name": "TreeItem2ProviderProps", "kind": "Interface" }, - { "name": "TreeItem2Root", "kind": "Variable" }, - { "name": "TreeItem2SlotProps", "kind": "Interface" }, - { "name": "TreeItem2Slots", "kind": "Interface" }, + { "name": "TreeItemCheckbox", "kind": "Variable" }, { "name": "treeItemClasses", "kind": "Variable" }, { "name": "TreeItemClasses", "kind": "Interface" }, { "name": "TreeItemClassKey", "kind": "TypeAlias" }, { "name": "TreeItemContent", "kind": "Variable" }, - { "name": "TreeItemContentClassKey", "kind": "TypeAlias" }, - { "name": "TreeItemContentProps", "kind": "Interface" }, + { "name": "TreeItemDragAndDropOverlay", "kind": "Function" }, + { "name": "TreeItemDragAndDropOverlayProps", "kind": "Interface" }, + { "name": "TreeItemGroupTransition", "kind": "Variable" }, + { "name": "TreeItemIcon", "kind": "Function" }, + { "name": "TreeItemIconContainer", "kind": "Variable" }, + { "name": "TreeItemIconProps", "kind": "Interface" }, + { "name": "TreeItemIconSlotProps", "kind": "Interface" }, + { "name": "TreeItemIconSlots", "kind": "Interface" }, + { "name": "TreeItemLabel", "kind": "Variable" }, + { "name": "TreeItemLabelInput", "kind": "Variable" }, + { "name": "TreeItemLabelInputProps", "kind": "Interface" }, { "name": "TreeItemProps", "kind": "Interface" }, + { "name": "TreeItemProvider", "kind": "Function" }, + { "name": "TreeItemProviderProps", "kind": "Interface" }, + { "name": "TreeItemRoot", "kind": "Variable" }, { "name": "TreeItemSlotProps", "kind": "Interface" }, { "name": "TreeItemSlots", "kind": "Interface" }, { "name": "TreeView", "kind": "Variable" }, { "name": "TreeViewBaseItem", "kind": "TypeAlias" }, + { "name": "TreeViewCancellableEvent", "kind": "TypeAlias" }, + { "name": "TreeViewCancellableEventHandler", "kind": "TypeAlias" }, { "name": "treeViewClasses", "kind": "Variable" }, { "name": "TreeViewClasses", "kind": "Interface" }, { "name": "TreeViewClassKey", "kind": "TypeAlias" }, @@ -59,23 +58,22 @@ { "name": "TreeViewItemId", "kind": "TypeAlias" }, { "name": "TreeViewItemsReorderingAction", "kind": "TypeAlias" }, { "name": "TreeViewProps", "kind": "Interface" }, + { "name": "TreeViewSelectionPropagation", "kind": "Interface" }, { "name": "TreeViewSlotProps", "kind": "Interface" }, { "name": "TreeViewSlots", "kind": "Interface" }, { "name": "unstable_resetCleanupTracking", "kind": "Variable" }, - { "name": "unstable_useTreeItem2", "kind": "Variable" }, - { "name": "useTreeItem2", "kind": "Variable" }, - { "name": "UseTreeItem2CheckboxSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2ContentSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2DragAndDropOverlaySlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2GroupTransitionSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2IconContainerSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2LabelInputSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2LabelSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2Parameters", "kind": "Interface" }, - { "name": "UseTreeItem2ReturnValue", "kind": "Interface" }, - { "name": "UseTreeItem2RootSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2Status", "kind": "Interface" }, - { "name": "useTreeItem2Utils", "kind": "Variable" }, - { "name": "useTreeItemState", "kind": "Function" }, + { "name": "useTreeItem", "kind": "Variable" }, + { "name": "UseTreeItemCheckboxSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemContentSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemDragAndDropOverlaySlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemGroupTransitionSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemIconContainerSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemLabelInputSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemLabelSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemParameters", "kind": "Interface" }, + { "name": "UseTreeItemReturnValue", "kind": "Interface" }, + { "name": "UseTreeItemRootSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemStatus", "kind": "Interface" }, + { "name": "useTreeItemUtils", "kind": "Variable" }, { "name": "useTreeViewApiRef", "kind": "Variable" } ] diff --git a/scripts/x-tree-view.exports.json b/scripts/x-tree-view.exports.json index ad00319179cf..01fac9eb0846 100644 --- a/scripts/x-tree-view.exports.json +++ b/scripts/x-tree-view.exports.json @@ -28,33 +28,32 @@ { "name": "SimpleTreeViewSlots", "kind": "Interface" }, { "name": "SingleSelectTreeViewProps", "kind": "TypeAlias" }, { "name": "TreeItem", "kind": "Variable" }, - { "name": "TreeItem2", "kind": "Variable" }, - { "name": "TreeItem2Checkbox", "kind": "Variable" }, - { "name": "TreeItem2Content", "kind": "Variable" }, - { "name": "TreeItem2GroupTransition", "kind": "Variable" }, - { "name": "TreeItem2Icon", "kind": "Function" }, - { "name": "TreeItem2IconContainer", "kind": "Variable" }, - { "name": "TreeItem2IconProps", "kind": "Interface" }, - { "name": "TreeItem2IconSlotProps", "kind": "Interface" }, - { "name": "TreeItem2IconSlots", "kind": "Interface" }, - { "name": "TreeItem2Label", "kind": "Variable" }, - { "name": "TreeItem2Props", "kind": "Interface" }, - { "name": "TreeItem2Provider", "kind": "Function" }, - { "name": "TreeItem2ProviderProps", "kind": "Interface" }, - { "name": "TreeItem2Root", "kind": "Variable" }, - { "name": "TreeItem2SlotProps", "kind": "Interface" }, - { "name": "TreeItem2Slots", "kind": "Interface" }, + { "name": "TreeItemCheckbox", "kind": "Variable" }, { "name": "treeItemClasses", "kind": "Variable" }, { "name": "TreeItemClasses", "kind": "Interface" }, { "name": "TreeItemClassKey", "kind": "TypeAlias" }, { "name": "TreeItemContent", "kind": "Variable" }, - { "name": "TreeItemContentClassKey", "kind": "TypeAlias" }, - { "name": "TreeItemContentProps", "kind": "Interface" }, + { "name": "TreeItemDragAndDropOverlay", "kind": "Function" }, + { "name": "TreeItemDragAndDropOverlayProps", "kind": "Interface" }, + { "name": "TreeItemGroupTransition", "kind": "Variable" }, + { "name": "TreeItemIcon", "kind": "Function" }, + { "name": "TreeItemIconContainer", "kind": "Variable" }, + { "name": "TreeItemIconProps", "kind": "Interface" }, + { "name": "TreeItemIconSlotProps", "kind": "Interface" }, + { "name": "TreeItemIconSlots", "kind": "Interface" }, + { "name": "TreeItemLabel", "kind": "Variable" }, + { "name": "TreeItemLabelInput", "kind": "Variable" }, + { "name": "TreeItemLabelInputProps", "kind": "Interface" }, { "name": "TreeItemProps", "kind": "Interface" }, + { "name": "TreeItemProvider", "kind": "Function" }, + { "name": "TreeItemProviderProps", "kind": "Interface" }, + { "name": "TreeItemRoot", "kind": "Variable" }, { "name": "TreeItemSlotProps", "kind": "Interface" }, { "name": "TreeItemSlots", "kind": "Interface" }, { "name": "TreeView", "kind": "Variable" }, { "name": "TreeViewBaseItem", "kind": "TypeAlias" }, + { "name": "TreeViewCancellableEvent", "kind": "TypeAlias" }, + { "name": "TreeViewCancellableEventHandler", "kind": "TypeAlias" }, { "name": "treeViewClasses", "kind": "Variable" }, { "name": "TreeViewClasses", "kind": "Interface" }, { "name": "TreeViewClassKey", "kind": "TypeAlias" }, @@ -63,23 +62,22 @@ { "name": "TreeViewItemId", "kind": "TypeAlias" }, { "name": "TreeViewItemsReorderingAction", "kind": "TypeAlias" }, { "name": "TreeViewProps", "kind": "Interface" }, + { "name": "TreeViewSelectionPropagation", "kind": "Interface" }, { "name": "TreeViewSlotProps", "kind": "Interface" }, { "name": "TreeViewSlots", "kind": "Interface" }, { "name": "unstable_resetCleanupTracking", "kind": "Variable" }, - { "name": "unstable_useTreeItem2", "kind": "Variable" }, - { "name": "useTreeItem2", "kind": "Variable" }, - { "name": "UseTreeItem2CheckboxSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2ContentSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2DragAndDropOverlaySlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2GroupTransitionSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2IconContainerSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2LabelInputSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2LabelSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2Parameters", "kind": "Interface" }, - { "name": "UseTreeItem2ReturnValue", "kind": "Interface" }, - { "name": "UseTreeItem2RootSlotOwnProps", "kind": "Interface" }, - { "name": "UseTreeItem2Status", "kind": "Interface" }, - { "name": "useTreeItem2Utils", "kind": "Variable" }, - { "name": "useTreeItemState", "kind": "Function" }, + { "name": "useTreeItem", "kind": "Variable" }, + { "name": "UseTreeItemCheckboxSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemContentSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemDragAndDropOverlaySlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemGroupTransitionSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemIconContainerSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemLabelInputSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemLabelSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemParameters", "kind": "Interface" }, + { "name": "UseTreeItemReturnValue", "kind": "Interface" }, + { "name": "UseTreeItemRootSlotOwnProps", "kind": "Interface" }, + { "name": "UseTreeItemStatus", "kind": "Interface" }, + { "name": "useTreeItemUtils", "kind": "Variable" }, { "name": "useTreeViewApiRef", "kind": "Variable" } ] diff --git a/test/e2e/fixtures/DatePicker/BasicDesktopDatePicker.tsx b/test/e2e/fixtures/DatePicker/BasicDesktopDatePicker.tsx index 992dd6244777..cdf3be6e2d0f 100644 --- a/test/e2e/fixtures/DatePicker/BasicDesktopDatePicker.tsx +++ b/test/e2e/fixtures/DatePicker/BasicDesktopDatePicker.tsx @@ -7,7 +7,6 @@ export default function BasicDesktopDatePicker() { return ( ); diff --git a/test/e2e/fixtures/DatePicker/BasicDesktopDateRangePicker.tsx b/test/e2e/fixtures/DatePicker/BasicDesktopDateRangePicker.tsx index e8823aaa3910..6d9b2d52874f 100644 --- a/test/e2e/fixtures/DatePicker/BasicDesktopDateRangePicker.tsx +++ b/test/e2e/fixtures/DatePicker/BasicDesktopDateRangePicker.tsx @@ -6,7 +6,7 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; export default function BasicDesktopDateRangePicker() { return ( - + ); } diff --git a/test/e2e/fixtures/DatePicker/BasicDesktopDateTimePicker.tsx b/test/e2e/fixtures/DatePicker/BasicDesktopDateTimePicker.tsx index f1f8dc403abf..13861f2069cd 100644 --- a/test/e2e/fixtures/DatePicker/BasicDesktopDateTimePicker.tsx +++ b/test/e2e/fixtures/DatePicker/BasicDesktopDateTimePicker.tsx @@ -6,7 +6,7 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; export default function BasicDesktopDateTimePicker() { return ( - + ); } diff --git a/test/e2e/fixtures/DatePicker/BasicMobileDatePicker.tsx b/test/e2e/fixtures/DatePicker/BasicMobileDatePicker.tsx index 7b869f691654..67d126d5eea1 100644 --- a/test/e2e/fixtures/DatePicker/BasicMobileDatePicker.tsx +++ b/test/e2e/fixtures/DatePicker/BasicMobileDatePicker.tsx @@ -6,7 +6,7 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; export default function BasicMobileDatePicker() { return ( - + ); } diff --git a/test/e2e/fixtures/DatePicker/DesktopDatePickerFormV7.tsx b/test/e2e/fixtures/DatePicker/DesktopDatePickerFormNonAccessibleDOMStructure.tsx similarity index 88% rename from test/e2e/fixtures/DatePicker/DesktopDatePickerFormV7.tsx rename to test/e2e/fixtures/DatePicker/DesktopDatePickerFormNonAccessibleDOMStructure.tsx index 83cbc0032ad8..357302cc37b2 100644 --- a/test/e2e/fixtures/DatePicker/DesktopDatePickerFormV7.tsx +++ b/test/e2e/fixtures/DatePicker/DesktopDatePickerFormNonAccessibleDOMStructure.tsx @@ -4,7 +4,7 @@ import { DesktopDatePicker } from '@mui/x-date-pickers/DesktopDatePicker'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -export default function DesktopDatePickerFormV7() { +export default function DesktopDatePickerFormNonAccessibleDOMStructure() { const [submittedDate, setSubmittedDate] = React.useState(undefined); const submitHandler: React.FormEventHandler = (event) => { event.preventDefault(); @@ -19,7 +19,7 @@ export default function DesktopDatePickerFormV7() { label="Desktop Date Picker" name="startDate" defaultValue={dayjs('2022-04-17')} - enableAccessibleFieldDOMStructure + enableAccessibleFieldDOMStructure={false} /> {submittedDate && } diff --git a/test/e2e/fixtures/DatePicker/MobileDatePickerV6WithClearAction.tsx b/test/e2e/fixtures/DatePicker/MobileDatePickerWithClearActionNonAccessibleDOMStructure.tsx similarity index 81% rename from test/e2e/fixtures/DatePicker/MobileDatePickerV6WithClearAction.tsx rename to test/e2e/fixtures/DatePicker/MobileDatePickerWithClearActionNonAccessibleDOMStructure.tsx index b78b026bdb6f..60be9bdcaf52 100644 --- a/test/e2e/fixtures/DatePicker/MobileDatePickerV6WithClearAction.tsx +++ b/test/e2e/fixtures/DatePicker/MobileDatePickerWithClearActionNonAccessibleDOMStructure.tsx @@ -4,7 +4,7 @@ import { MobileDatePicker } from '@mui/x-date-pickers/MobileDatePicker'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -export default function MobileDatePickerV6WithClearAction() { +export default function MobileDatePickerWithClearActionNonAccessibleDOMStructure() { return ( ); diff --git a/test/e2e/fixtures/DatePicker/ReadonlyDesktopDateRangePickerSingleV6.tsx b/test/e2e/fixtures/DatePicker/ReadonlyDesktopDateRangePickerSingle.tsx similarity index 89% rename from test/e2e/fixtures/DatePicker/ReadonlyDesktopDateRangePickerSingleV6.tsx rename to test/e2e/fixtures/DatePicker/ReadonlyDesktopDateRangePickerSingle.tsx index 816f5326c378..5d64567e7ff1 100644 --- a/test/e2e/fixtures/DatePicker/ReadonlyDesktopDateRangePickerSingleV6.tsx +++ b/test/e2e/fixtures/DatePicker/ReadonlyDesktopDateRangePickerSingle.tsx @@ -4,7 +4,7 @@ import { SingleInputDateRangeField } from '@mui/x-date-pickers-pro/SingleInputDa import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -export default function ReadonlyDesktopDateRangePickerSingleV6() { +export default function ReadonlyDesktopDateRangePickerSingle() { return ( ); diff --git a/test/e2e/fixtures/DatePicker/SingleDesktopDateRangePickerWithTZ.tsx b/test/e2e/fixtures/DatePicker/SingleDesktopDateRangePickerWithTZ.tsx index 0f920bf2aa9b..8eb717e0c3e2 100644 --- a/test/e2e/fixtures/DatePicker/SingleDesktopDateRangePickerWithTZ.tsx +++ b/test/e2e/fixtures/DatePicker/SingleDesktopDateRangePickerWithTZ.tsx @@ -14,7 +14,6 @@ export default function BasicDesktopDateRangePicker() { return ( diff --git a/test/e2e/index.test.ts b/test/e2e/index.test.ts index 558914be9364..f9145efca503 100644 --- a/test/e2e/index.test.ts +++ b/test/e2e/index.test.ts @@ -622,7 +622,8 @@ async function initializeEnvironment( // assertion for: https://github.com/mui/mui-x/issues/12652 it('should allow field editing after opening and closing the picker', async () => { - await renderFixture('DatePicker/BasicClearableDesktopDatePicker'); + await renderFixture('DatePicker/BasicDesktopDatePicker'); + // open picker await page.getByRole('button').click(); await page.waitForSelector('[role="dialog"]', { state: 'attached' }); @@ -630,11 +631,13 @@ async function initializeEnvironment( await page.getByRole('button', { name: 'Choose date' }).click(); await page.waitForSelector('[role="dialog"]', { state: 'detached' }); - // click on the input to focus it - await page.getByRole('textbox').click(); + await page.locator(`.${pickersSectionListClasses.root}`).click(); + await page.getByRole(`spinbutton`, { name: 'Month' }).fill('04'); + await page.getByRole(`spinbutton`, { name: 'Day' }).fill('11'); + await page.getByRole(`spinbutton`, { name: 'Year' }).fill('2022'); - // test that the input value is set after focus - expect(await page.getByRole('textbox').inputValue()).to.equal('MM/DD/YYYY'); + const input = page.getByRole('textbox', { includeHidden: true }); + expect(await input.inputValue()).to.equal('04/11/2022'); }); it('should allow filling in a value and clearing a value', async () => { @@ -726,8 +729,8 @@ async function initializeEnvironment( expect(await page.evaluate(() => document.activeElement?.textContent)).to.equal('MM'); }); - it('should focus the first field section after clearing a value in v6 input', async () => { - await renderFixture('DatePicker/BasicClearableDesktopDatePicker'); + it('should focus the first field section after clearing a value with the non-accessible DOM structure', async () => { + await renderFixture('DatePicker/BasicDesktopDatePickerNonAccessibleDOMStructure'); const textbox = page.getByRole('textbox'); // locator.fill('2') does not work reliably for this case in all browsers @@ -752,7 +755,7 @@ async function initializeEnvironment( }); it('should submit a form when clicking "Enter" key', async () => { - await renderFixture('DatePicker/DesktopDatePickerForm'); + await renderFixture('DatePicker/DesktopDatePickerFormNonAccessibleDOMStructure'); const textbox = page.getByRole('textbox'); await textbox.focus(); @@ -814,8 +817,10 @@ async function initializeEnvironment( ); }); - it('should have consistent `placeholder` and `value` behavior', async () => { - await renderFixture('DatePicker/MobileDatePickerV6WithClearAction'); + it('should have consistent `placeholder` and `value` behavior in the non-accessible DOM structure', async () => { + await renderFixture( + 'DatePicker/MobileDatePickerWithClearActionNonAccessibleDOMStructure', + ); const input = page.getByRole('textbox'); @@ -990,13 +995,13 @@ async function initializeEnvironment( await page.waitForSelector('[role="tooltip"]', { state: 'detached' }); }); - it('should have the same selection process when "readOnly" with single input v7 field', async () => { + it('should have the same selection process when "readOnly" with single input field with an accessible DOM structure', async () => { // firefox in CI is not happy with this test if (browserType.name() === 'firefox') { return; } - await renderFixture('DatePicker/ReadonlyDesktopDateRangePickerSingleV7'); + await renderFixture('DatePicker/ReadonlyDesktopDateRangePickerSingle'); await page.locator(`.${pickersSectionListClasses.root}`).first().click(); @@ -1014,13 +1019,15 @@ async function initializeEnvironment( ); }); - it('should have the same selection process when "readOnly" with single input v6 field', async () => { + it('should have the same selection process when "readOnly" with single input field with a non-accessible DOM structure', async () => { // firefox in CI is not happy with this test if (browserType.name() === 'firefox') { return; } - await renderFixture('DatePicker/ReadonlyDesktopDateRangePickerSingleV6'); + await renderFixture( + 'DatePicker/ReadonlyDesktopDateRangePickerSingleNonAccessibleDOMStructure', + ); await page.getByRole('textbox').click(); diff --git a/test/performance-charts/package.json b/test/performance-charts/package.json index fe5e946b9981..8fa196176d91 100644 --- a/test/performance-charts/package.json +++ b/test/performance-charts/package.json @@ -11,15 +11,15 @@ "@emotion/react": "^11.13.3", "@mui/x-charts": "workspace:*", "@mui/x-charts-pro": "workspace:*", - "@testing-library/jest-dom": "^6.5.0", + "@testing-library/jest-dom": "^6.6.2", "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^14.5.2", - "@vitejs/plugin-react": "^4.3.2", + "@vitejs/plugin-react": "^4.3.3", "@vitejs/plugin-react-swc": "^3.7.1", - "@vitest/ui": "2.1.2", + "@vitest/ui": "2.1.3", "jsdom": "^24.1.3", "react": "^18.3.1", "react-dom": "^18.3.1", - "vitest": "2.1.2" + "vitest": "2.1.3" } } diff --git a/test/regressions/data-grid/DataGridPinnedColumns.js b/test/regressions/data-grid/DataGridPinnedColumns.js new file mode 100644 index 000000000000..743f70a7386f --- /dev/null +++ b/test/regressions/data-grid/DataGridPinnedColumns.js @@ -0,0 +1,62 @@ +import * as React from 'react'; +import DeleteIcon from '@mui/icons-material/Delete'; +import EditIcon from '@mui/icons-material/Edit'; +import { DataGridPro, GridActionsCellItem } from '@mui/x-data-grid-pro'; +import { + randomCreatedDate, + randomTraderName, + randomEmail, + randomUpdatedDate, +} from '@mui/x-data-grid-generator'; + +const columns = [ + { field: 'name', headerName: 'Name', width: 160, editable: true }, + { field: 'email', headerName: 'Email', width: 200, editable: true }, + { field: 'age', headerName: 'Age', type: 'number', editable: true }, + { + field: 'dateCreated', + headerName: 'Date Created', + type: 'date', + width: 180, + editable: true, + }, + { + field: 'lastLogin', + headerName: 'Last Login', + type: 'dateTime', + width: 220, + editable: true, + }, + { + field: 'actions', + type: 'actions', + width: 100, + getActions: () => [ + } label="Edit" />, + } label="Delete" />, + ], + }, +]; + +const rows = [ + { + id: 1, + name: randomTraderName(), + email: randomEmail(), + age: 25, + dateCreated: randomCreatedDate(), + lastLogin: randomUpdatedDate(), + }, +]; + +export default function BasicColumnPinning() { + return ( +
+ +
+ ); +} diff --git a/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx b/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx index c25c09224a9a..3d0c49c30f1a 100644 --- a/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx +++ b/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx @@ -29,7 +29,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu describe('text field keyboard:', () => { it('should not accept end date prior to start state', () => { const onErrorMock = spy(); - render(); + render(); expect(onErrorMock.callCount).to.equal(0); act(() => { @@ -53,7 +53,6 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu const onErrorMock = spy(); const { setProps } = render( adapterToUse.isAfter(date, adapterToUse.date('2018-03-11'))} />, @@ -100,7 +99,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu it('should apply disablePast', function test() { const onErrorMock = spy(); const now = adapterToUse.date(); - render(); + render(); let past: null | typeof now = null; if (withDate) { @@ -140,9 +139,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu it('should apply disableFuture', function test() { const onErrorMock = spy(); const now = adapterToUse.date(); - render( - , - ); + render(); let future: null | typeof now = null; @@ -187,13 +184,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu } const onErrorMock = spy(); - render( - , - ); + render(); act(() => { [adapterToUse.date('2018-03-09'), adapterToUse.date('2018-03-10')].forEach( @@ -230,13 +221,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu } const onErrorMock = spy(); - render( - , - ); + render(); act(() => { [adapterToUse.date('2018-03-15'), adapterToUse.date('2018-03-17')].forEach( @@ -266,11 +251,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu const onErrorMock = spy(); render( - , + , ); act(() => { @@ -310,11 +291,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu const onErrorMock = spy(); render( - , + , ); act(() => { diff --git a/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx b/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx index 7d78327e23ab..3bc1182de0b7 100644 --- a/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx +++ b/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx @@ -30,7 +30,6 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( const onErrorMock = spy(); render( , @@ -66,7 +64,6 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( const onErrorMock = spy(); const { setProps } = render( adapterToUse.isAfter(date, adapterToUse.date('2018-03-10'))} @@ -113,7 +110,6 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( const onErrorMock = spy(); const { setProps } = render( @@ -160,7 +156,6 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( const onErrorMock = spy(); const { setProps } = render( @@ -206,7 +201,7 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( let now; function WithFakeTimer(props) { now = adapterToUse.date(); - return ; + return ; } const { setProps } = render(); @@ -244,7 +239,7 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( let now; function WithFakeTimer(props) { now = adapterToUse.date(); - return ; + return ; } const { setProps } = render(); @@ -286,7 +281,6 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( const onErrorMock = spy(); const { setProps } = render( @@ -55,7 +54,6 @@ export const testTextFieldValidation: DescribeValidationTestSuite = (ElementToTe const onErrorMock = spy(); const { setProps } = render( adapterToUse.getYear(date) === 2018} @@ -82,7 +80,6 @@ export const testTextFieldValidation: DescribeValidationTestSuite = (ElementToTe const onErrorMock = spy(); const { setProps } = render( adapterToUse.getMonth(date) === 2} value={adapterToUse.date('2018-03-12')} @@ -113,7 +110,6 @@ export const testTextFieldValidation: DescribeValidationTestSuite = (ElementToTe const onErrorMock = spy(); const { setProps } = render( { let comparingValue = adapterToUse.getHours(value); @@ -165,7 +161,7 @@ export const testTextFieldValidation: DescribeValidationTestSuite = (ElementToTe let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); - return ; + return ; } const onErrorMock = spy(); @@ -198,7 +194,7 @@ export const testTextFieldValidation: DescribeValidationTestSuite = (ElementToTe let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); - return ; + return ; } const onErrorMock = spy(); @@ -229,7 +225,6 @@ export const testTextFieldValidation: DescribeValidationTestSuite = (ElementToTe const onErrorMock = spy(); const { setProps } = render( = (
external label
= ( render( = ( render( = ( render( = ( render( = ( render( = ( render( describe('Picker open / close lifecycle', () => { it('should not open on mount if `props.open` is false', () => { - render(); + render(); expect(screen.queryByRole(viewWrapperRole)).to.equal(null); }); it('should open on mount if `prop.open` is true', () => { - render(); + render(); expect(screen.queryByRole(viewWrapperRole)).toBeVisible(); }); it('should not open when `prop.disabled` is true ', () => { const onOpen = spy(); - render(); + render(); openPicker(pickerParams); expect(onOpen.callCount).to.equal(0); @@ -41,7 +41,7 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite it('should not open when `prop.readOnly` is true ', () => { const onOpen = spy(); - render(); + render(); openPicker(pickerParams); expect(onOpen.callCount).to.equal(0); @@ -298,7 +298,6 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite render( render( const onAccept = spy(); const onClose = spy(); - render( - , - ); + render(); // Dismiss the picker fireEvent.keyDown(document.body, { key: 'Escape' }); diff --git a/test/utils/pickers/describeValue/testShortcuts.tsx b/test/utils/pickers/describeValue/testShortcuts.tsx index ae14f1ddc801..1e77be81334a 100644 --- a/test/utils/pickers/describeValue/testShortcuts.tsx +++ b/test/utils/pickers/describeValue/testShortcuts.tsx @@ -28,7 +28,6 @@ export const testShortcuts: DescribeValueTestSuite = (ElementToTe render( = (ElementToTe render( = (ElementToTe render( ({ selectedSection, ...props }) => { - // Test with v7 input + // Test with accessible DOM structure const v7Response = renderWithProps({ ...props, enableAccessibleFieldDOMStructure: true, @@ -244,7 +244,7 @@ export const buildFieldInteractions =

({ expectFieldValueV7(v7Response.getSectionsContainer(), expectedValue); v7Response.unmount(); - // Test with v6 input + // Test with non-accessible DOM structure const v6Response = renderWithProps({ ...props, enableAccessibleFieldDOMStructure: false, @@ -263,7 +263,7 @@ export const buildFieldInteractions =

({ ...props }) => { if (!skipV7) { - // Test with v7 input + // Test with accessible DOM structure const v7Response = renderWithProps({ ...props, enableAccessibleFieldDOMStructure: true, @@ -280,7 +280,7 @@ export const buildFieldInteractions =

({ v7Response.unmount(); } - // Test with v6 input + // Test with non-accessible DOM structure const v6Response = renderWithProps({ ...props, enableAccessibleFieldDOMStructure: false, diff --git a/test/utils/pickers/openPicker.ts b/test/utils/pickers/openPicker.ts index 229510842e61..b34715ce5f8b 100644 --- a/test/utils/pickers/openPicker.ts +++ b/test/utils/pickers/openPicker.ts @@ -6,7 +6,6 @@ export type OpenPickerParams = | { type: 'date' | 'date-time' | 'time'; variant: 'mobile' | 'desktop'; - click?: (element: Element) => Promise; } | { type: 'date-range' | 'date-time-range'; @@ -16,32 +15,29 @@ export type OpenPickerParams = * @default false */ isSingleInput?: boolean; - click?: (element: Element) => Promise; }; -export const openPicker = async (params: OpenPickerParams) => { +export const openPicker = (params: OpenPickerParams) => { const isRangeType = params.type === 'date-range' || params.type === 'date-time-range'; const fieldSectionsContainer = getFieldSectionsContainer( isRangeType && !params.isSingleInput && params.initialFocus === 'end' ? 1 : 0, ); - const { click = fireEvent.click } = params; - if (isRangeType) { - await click(fieldSectionsContainer); + fireEvent.click(fieldSectionsContainer); if (params.isSingleInput && params.initialFocus === 'end') { const sections = fieldSectionsContainer.querySelectorAll( `.${pickersInputBaseClasses.sectionsContainer}`, ); - await click(sections[sections.length - 1]); + fireEvent.click(sections[sections.length - 1]); } return true; } if (params.variant === 'mobile') { - await click(fieldSectionsContainer); + fireEvent.click(fieldSectionsContainer); return true; } @@ -51,6 +47,6 @@ export const openPicker = async (params: OpenPickerParams) => { ? screen.getByLabelText(/choose time/i) : screen.getByLabelText(/choose date/i); - await click(target); + fireEvent.click(target); return true; }; diff --git a/test/utils/tree-view/describeTreeView/describeTreeView.tsx b/test/utils/tree-view/describeTreeView/describeTreeView.tsx index ce3368a259da..4d7d28078f17 100644 --- a/test/utils/tree-view/describeTreeView/describeTreeView.tsx +++ b/test/utils/tree-view/describeTreeView/describeTreeView.tsx @@ -5,7 +5,6 @@ import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro'; import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; import { TreeItem, treeItemClasses } from '@mui/x-tree-view/TreeItem'; -import { TreeItem2 } from '@mui/x-tree-view/TreeItem2'; import { TreeViewBaseItem } from '@mui/x-tree-view/models'; import { TreeViewAnyPluginSignature, TreeViewPublicAPI } from '@mui/x-tree-view/internals/models'; import { MuiRenderResult } from '@mui/internal-test-utils/createRenderer'; @@ -114,10 +113,7 @@ const innerDescribeTreeView = return getUtils(result); }; - const createRendererForComponentWithItemsProp = ( - TreeViewComponent: typeof RichTreeView, - TreeItemComponent: typeof TreeItem | typeof TreeItem2, - ) => { + const createRendererForComponentWithItemsProp = (TreeViewComponent: typeof RichTreeView) => { const objectRenderer: DescribeTreeViewRenderer = ({ items: rawItems, withErrorBoundary, @@ -131,7 +127,7 @@ const innerDescribeTreeView = @@ -172,10 +168,7 @@ const innerDescribeTreeView = }; }; - const createRenderersForComponentWithJSXItems = ( - TreeViewComponent: typeof SimpleTreeView, - TreeItemComponent: typeof TreeItem | typeof TreeItem2, - ) => { + const createRenderersForComponentWithJSXItems = (TreeViewComponent: typeof SimpleTreeView) => { const objectRenderer: DescribeTreeViewRenderer = ({ items: rawItems, withErrorBoundary, @@ -184,7 +177,7 @@ const innerDescribeTreeView = ...other }) => { const items = rawItems as readonly DescribeTreeViewItem[]; - const Item = slots?.item ?? TreeItemComponent; + const Item = slots?.item ?? TreeItem; const apiRef = { current: undefined }; const renderItem = (item: DescribeTreeViewItem) => ( @@ -223,71 +216,32 @@ const innerDescribeTreeView = }; describe(message, () => { - describe('RichTreeView + TreeItem', () => { + describe('RichTreeView', () => { testRunner({ - ...createRendererForComponentWithItemsProp(RichTreeView, TreeItem), - setup: 'RichTreeView + TreeItem', + ...createRendererForComponentWithItemsProp(RichTreeView), treeViewComponentName: 'RichTreeView', - treeItemComponentName: 'TreeItem', TreeViewComponent: RichTreeView, TreeItemComponent: TreeItem, }); }); - describe('RichTreeView + TreeItem2', () => { + describe('RichTreeViewPro', () => { testRunner({ - ...createRendererForComponentWithItemsProp(RichTreeView, TreeItem2), - setup: 'RichTreeView + TreeItem2', - treeViewComponentName: 'RichTreeView', - treeItemComponentName: 'TreeItem2', - TreeViewComponent: RichTreeView, - TreeItemComponent: TreeItem2, - }); - }); - - describe('RichTreeViewPro + TreeItem', () => { - testRunner({ - ...createRendererForComponentWithItemsProp(RichTreeViewPro, TreeItem), - setup: 'RichTreeViewPro + TreeItem', + ...createRendererForComponentWithItemsProp(RichTreeViewPro), treeViewComponentName: 'RichTreeViewPro', - treeItemComponentName: 'TreeItem', TreeViewComponent: RichTreeViewPro, TreeItemComponent: TreeItem, }); }); - describe('RichTreeViewPro + TreeItem2', () => { - testRunner({ - ...createRendererForComponentWithItemsProp(RichTreeViewPro, TreeItem2), - setup: 'RichTreeViewPro + TreeItem2', - treeViewComponentName: 'RichTreeViewPro', - treeItemComponentName: 'TreeItem2', - TreeViewComponent: RichTreeViewPro, - TreeItemComponent: TreeItem2, - }); - }); - - describe('SimpleTreeView + TreeItem', () => { + describe('SimpleTreeView', () => { testRunner({ - ...createRenderersForComponentWithJSXItems(SimpleTreeView, TreeItem), - setup: 'SimpleTreeView + TreeItem', + ...createRenderersForComponentWithJSXItems(SimpleTreeView), treeViewComponentName: 'SimpleTreeView', - treeItemComponentName: 'TreeItem', TreeViewComponent: SimpleTreeView, TreeItemComponent: TreeItem, }); }); - - describe('SimpleTreeView + TreeItem2', () => { - testRunner({ - ...createRenderersForComponentWithJSXItems(SimpleTreeView, TreeItem2), - setup: 'SimpleTreeView + TreeItem2', - treeViewComponentName: 'SimpleTreeView', - treeItemComponentName: 'TreeItem2', - TreeViewComponent: SimpleTreeView, - TreeItemComponent: TreeItem2, - }); - }); }); }; @@ -303,13 +257,10 @@ type DescribeTreeView = { }; /** - * Describe tests for the Tree View that will be executed with the following setups: - * - RichTreeView + TreeItem - * - RichTreeView + TreeItem2 - * - RichTreeViewPro + TreeItem - * - RichTreeViewPro + TreeItem2 - * - SimpleTreeView + TreeItem - * - SimpleTreeView + TreeItem2 + * Describe tests for the Tree View that will be executed with the following Tree View components: + * - RichTreeView + * - RichTreeViewPro + * - SimpleTreeView * * Is used as follows: * @@ -325,7 +276,7 @@ type DescribeTreeView = { * ``` * * Several things to note: - * - The `render` function takes an array of items, even for `SimpleTreeView` + * - The `render` function takes an array of items, even for Simple Tree View * - Except for `items`, all the other properties passed to `render` will be forwarded to the Tree View as props * - If an item has no label, its `id` will be used as the label */ diff --git a/test/utils/tree-view/describeTreeView/describeTreeView.types.ts b/test/utils/tree-view/describeTreeView/describeTreeView.types.ts index b470f885df76..20996106b13b 100644 --- a/test/utils/tree-view/describeTreeView/describeTreeView.types.ts +++ b/test/utils/tree-view/describeTreeView/describeTreeView.types.ts @@ -7,7 +7,6 @@ import { } from '@mui/x-tree-view/internals/models'; import { TreeViewItemId } from '@mui/x-tree-view/models'; import { TreeItemProps } from '@mui/x-tree-view/TreeItem'; -import { TreeItem2Props } from '@mui/x-tree-view/TreeItem2'; export type DescribeTreeViewTestRunner = ( params: DescribeTreeViewTestRunnerParams, @@ -129,10 +128,10 @@ export type DescribeTreeViewRenderer, 'slots' | 'slotProps'> & { slots?: MergeSignaturesProperty & { - item?: React.ElementType; + item?: React.ElementType; }; slotProps?: MergeSignaturesProperty & { - item?: Partial | Partial; + item?: Partial; }; experimentalFeatures?: TreeViewExperimentalFeatures; }, @@ -143,7 +142,6 @@ export type DescribeTreeViewJSXRenderer = ( ) => DescribeTreeViewRendererUtils; type TreeViewComponentName = 'RichTreeView' | 'RichTreeViewPro' | 'SimpleTreeView'; -type TreeItemComponentName = 'TreeItem' | 'TreeItem2'; interface DescribeTreeViewTestRunnerParams { /** @@ -178,9 +176,7 @@ interface DescribeTreeViewTestRunnerParams; TreeItemComponent: React.ElementType; } diff --git a/test/utils/tree-view/fakeContextValue.ts b/test/utils/tree-view/fakeContextValue.ts index eabcda86ac5d..649fb121c4b2 100644 --- a/test/utils/tree-view/fakeContextValue.ts +++ b/test/utils/tree-view/fakeContextValue.ts @@ -41,6 +41,7 @@ export const getFakeContextValue = ( multiSelect: false, checkboxSelection: features.checkboxSelection ?? false, disableSelection: false, + selectionPropagation: {}, }, treeId: 'mui-tree-view-1', rootRef: {